Status Bar
Singleton floating area for short, non-critical, transient feedback.
status-bar is for transient feedback, not persistent application status. For
persistent status, metrics, or operational state, compose regular content
primitives such as cards, badges, and clusters.
A status bar is a single live region, not a stacked toaster. New messages
replace the previous one. Keep one element in the HTML, styled with
.status-bar and carrying the [data-status][role="status"] contract that the
runtime targets, empty by default.
- JavaScript writes the text content and toggles
.is-open, the state class the CSS animates. The element is never hidden, so it stays available as a live region. - Use it for transient status (
Saved.,Reconnected.,Copied.). Critical, persistent, or actionable information belongs in.alert, inline messages, or dialogs. - Intents:
danger,success,warning,neutral. The default (no intent) is a neutral dark pill. - On mobile, the runtime keeps the bar above the software keyboard by tracking
visualViewportwhile a message is showing. - A message from an open modal dialog temporarily moves the bar inside that dialog so it remains visible and announced. The bar returns after its exit.
clear() closes the bar, then removes its text and intent after the exit
transition. The outgoing message remains readable in the DOM until then.
A new message replaces it safely, even during exit. Passing an empty message
also clears the bar.
The runtime auto-wires the status bar to Actual's form validation: a form that
fails to submit shows its data-validation-message in the status bar with the
danger intent. No target in the DOM means the call is a no-op, so the markup
stays optional.
<form class="stack needs-validation" data-enhance="validation" data-validation-message="Please check the highlighted fields.">
<label class="field">
<span class="field-label">Email</span>
<input class="input" type="email" name="email" required
aria-describedby="sb-email-error" />
<span class="field-error" id="sb-email-error" role="alert">Enter a valid email.</span>
</label>
<div class="cluster form-actions">
<button class="btn primary" type="submit">Submit</button>
</div>
</form>
Submitting the empty form blocks submission and shows data-validation-message
in the status bar below — no JavaScript required beyond the runtime.
<div class="cluster">
<button class="btn" type="button" commandfor="sb-status" command="--status"
aria-controls="sb-status"
data-status-message="Saved." data-status-intent="success">Show success</button>
<button class="btn" type="button" commandfor="sb-status" command="--status"
aria-controls="sb-status"
data-status-message="Could not save." data-status-intent="danger"
data-status-duration="6000">Show danger</button>
<button class="btn outline" type="button" commandfor="sb-status" command="--status-clear"
aria-controls="sb-status">Clear</button>
</div>
<div class="status-bar" data-status id="sb-status" role="status" aria-live="polite" aria-atomic="true"></div>
No script required beyond the runtime: command="--status" reads its message
from data-status-message (plus optional data-status-intent /
data-status-duration), command="--status-clear" empties the bar.
commandfor must match the status bar's own id.
For dynamic messages — a fetch response, a computed value — dispatch the same event the commands use under the hood:
document.dispatchEvent(new CustomEvent("actual:status", {
bubbles: true,
detail: { message: "Saved.", intent: "success" },
}));
Dispatch from the control or region that produced the feedback when it lives inside a dialog; bubbling lets the runtime infer the active surface:
saveButton.dispatchEvent(new CustomEvent("actual:status", {
bubbles: true,
detail: { message: "Saved.", intent: "success" },
}));
Code importing status() directly passes the same origin as source:
status("Saved.", { intent: "success", source: saveButton });
Validation and declarative status commands propagate their source automatically.
CSS hooks
--status-bg— bar background.--status-fg— bar text color.
Prefer an intent class over setting these directly.