Messages

Summary

Messages sit inline in the content flow, next to what they describe. They are tinted per severity, optionally titled and closeable, with a -solid variant for chrome surfaces and a Soma.message.create() API for dynamic feedback. Page-level notices are Banners; transient notifications are Toasts.

When to use

TierUse it for
MessageFeedback about THIS section/form: validation summaries, contextual notices. Dismissable via close button.
BannerWhole-page state: scan failed, degraded environment. -critical only for must-see safety states.
ToastAsync events unrelated to what's on screen: deploy finished, budget warning. Sticky (duration: 0) for failures.

Examples

Info (default)

Info

Default message look — the info tint.

No variant class needed; icon info.

Neutral

Heads-up

The grey tint for notices with no severity.

-neutral: informational, not semantic.

Success

Saved

Your changes are live.

-success, icon check.

Warning

Certificate expiring

Renew within 14 days.

-warning, icon triangle-alert.

Error

Build failed

Step “test” exited with code 1.

-error, icon circle-alert.

Closeable

Auto-init wires the close button:

Closeable, no title — click the ×.

No JS wiring needed on your side. soma-message-dismiss fires before removal.

Solid variants

The loud fill, for chrome surfaces:

Info solid

Neutral solid

Success solid

Warning solid

Error solid

-solid combines with every variant — saturated fills that register at a glance; inline messages stay subtle by default.

Dynamic

Soma.message.create(), live:

Each click appends a closeable message into the container above. The success one gets role="status", the error one role="alert", so screen readers announce them. Dismissed so far: 0 (counted via on('dismiss')).

Live regions

Screen readers only announce live-region content that appears, so static messages, present at page load, get no role, while Soma.message.create() adds one: role="alert" (interrupts) for error and warning, role="status" (queues politely) for info and success. This mirrors the toast and banner conventions.

HTML

The minimal message — the tint, a semantic icon, and the content region:

<div class="soma-message soma-message-warning">
  <span class="soma-icon soma-icon-triangle-alert"></span>
  <div class="soma-message-content">
    <p>Certificate expires in 14 days.</p>
  </div>
</div>

The full form has a semibold title line, body paragraphs, and the close button auto-init binds on page load:

<div class="soma-message soma-message-success">
  <span class="soma-icon soma-icon-check"></span>
  <div class="soma-message-content">
    <p class="soma-message-title">Saved</p>
    <p>Your changes are live.</p>
  </div>
  <button class="soma-message-close" aria-label="Dismiss">
    <span class="soma-icon soma-icon-close"></span>
  </button>
</div>

The loud fill for chrome surfaces. Add -solid to any variant:

<div class="soma-message soma-message-error soma-message-solid">
  <span class="soma-icon soma-icon-circle-alert"></span>
  <div class="soma-message-content">
    <p class="soma-message-title">Build failed</p>
  </div>
</div>

Recommended icon per variant: info/neutral → info, success → check, warning → triangle-alert, error → circle-alert.

CSS classes

ClassEffect
.soma-messageBase inline banner (info tint). Variants: -neutral, -success, -warning, -error. Stacked messages get a top margin (dropped on the first).
.soma-message-content / -title / -closeBody wrapper, bold title paragraph, dismiss button.
.soma-message-solidSaturated "loud" fill for any message variant, for chrome surfaces where the type must register at a glance; inline messages stay subtle by default.

JavaScript

Instances

MemberDescription
Soma.message(elOrSelector)Get or create the singleton instance for a .soma-message element / selector. Throws when nothing matches or the class is missing. Auto-init binds every message on the page at DOMContentLoaded. A click on its .soma-message-close dismisses it.
.dismiss()Dispatch soma-message-dismiss, then remove the message from the DOM.
.on('dismiss', fn)Listen for dismissal (a plain addEventListener for soma-message-dismiss on the element).
.off('dismiss', fn)Remove a listener. Pass the same function reference given to .on().

All methods return the instance, so calls chain. The soma-message-dismiss CustomEvent bubbles and is dispatched before removal — e.target is still in the DOM, so listeners can read data off the element.

Soma.message.create options

MemberDescription
Soma.message.create(opts)Build a message dynamically and return its instance (whose .el is the element). Adds the live-region role static markup doesn't need.
opts.type'info' (default) | 'success' | 'warning' | 'error'. Unknown values throw. Picks the icon, tint, and role.
opts.titleOptional semibold heading line, inserted as text.
opts.bodyPlain-text body paragraph.
opts.bodyHtmlMarkup opt-in for the body (content you control only). Wins over body when both are set.
opts.closeabletrue adds the dismiss button, with a localised label (Soma.i18n key message.dismiss). Default false.
opts.contextElement or selector to append the message into; a selector that matches nothing throws. Omit it to place the returned instance's .el yourself.

Create a message into a container:

Soma.message.create({
  type: 'success',
  title: 'Saved',
  body: 'Your changes are live.',
  closeable: true,
  context: '#form-feedback',
});

Without context, place the element yourself, e.g. right before the row it concerns:

const m = Soma.message.create({
  type: 'warning',
  body: 'This row has unsaved edits.',
  closeable: true,
});
row.before(m.el);

Listen with a named handler so it can be removed again with .off():

const onDismiss = () => refreshCounts();
const m = Soma.message('#quota-notice');

m.on('dismiss', onDismiss);
// later, if the notice's dismissal should stop mattering:
m.off('dismiss', onDismiss);

Delegated listening: the event bubbles, so one document-level listener sees every message on the page:

document.addEventListener('soma-message-dismiss', (e) => {
  console.log('dismissed:', e.target.querySelector('.soma-message-content').textContent.trim());
});

Validation summary on submit — the classic integration; the alert role makes screen readers announce it:

form.addEventListener('submit', (e) => {
  const errors = validate(form);
  if (errors.length) {
    e.preventDefault();
    form.querySelectorAll('.soma-message').forEach((el) => el.remove());
    Soma.message.create({
      type: 'error',
      title: `${errors.length} problem(s) to fix`,
      body: errors.join(' '),
      closeable: true,
      context: form,
    });
  }
});

Bind a message rendered after page load (auto-init runs once, at DOMContentLoaded) — the call is a get-or-create singleton, safe after any DOM update:

container.insertAdjacentHTML('beforeend', serverRenderedMessageHtml);
Soma.message(container.querySelector('.soma-message'));  // close button now works

The live examples above, exactly as this page wires them:

const dismissedEl = document.getElementById('msg-dismissed');
let dismissed = 0;
const count = () => { dismissedEl.textContent = String(++dismissed); };

document.getElementById('msg-create').addEventListener('click', () =>
  Soma.message.create({
    type: 'success',
    title: 'Saved',
    body: 'Your changes are live.',
    closeable: true,
    context: '#msg-target',
  }).on('dismiss', count));
document.getElementById('msg-create-error').addEventListener('click', () =>
  Soma.message.create({
    type: 'error',
    title: 'Build failed',
    body: 'Step "test" exited with code 1.',
    closeable: true,
    context: '#msg-target',
  }).on('dismiss', count));