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
| Tier | Use it for |
|---|---|
| Message | Feedback about THIS section/form: validation summaries, contextual notices. Dismissable via close button. |
| Banner | Whole-page state: scan failed, degraded environment. -critical only for must-see safety states. |
| Toast | Async events unrelated to what's on screen: deploy finished, budget warning. Sticky (duration: 0) for failures. |
Examples
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
| Class | Effect |
|---|---|
.soma-message | Base inline banner (info tint). Variants: -neutral, -success, -warning, -error. Stacked messages get a top margin (dropped on the first). |
.soma-message-content / -title / -close | Body wrapper, bold title paragraph, dismiss button. |
.soma-message-solid | Saturated "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
| Member | Description |
|---|---|
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
| Member | Description |
|---|---|
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.title | Optional semibold heading line, inserted as text. |
opts.body | Plain-text body paragraph. |
opts.bodyHtml | Markup opt-in for the body (content you control only). Wins over body when both are set. |
opts.closeable | true adds the dismiss button, with a localised label (Soma.i18n key message.dismiss). Default false. |
opts.context | Element 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));