Mercurial
diff design_system/NOTIFICATIONS.md @ 252:7a7581f040e8
[ui] Add scoped notification component
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 11:57:16 -0700 |
| parents | |
| children | fdf3816959cb |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/design_system/NOTIFICATIONS.md Tue Aug 04 11:57:16 2026 -0700 @@ -0,0 +1,97 @@ +# Zenbu UI notifications + +`zen-notifications` is a light-DOM, framework-free notification scope. Its +stack is fixed to the bottom-right by default and styled only with `--zen-*` +tokens. + +```html +<zen-notifications aria-label="Notifications"> + <main id="application"></main> +</zen-notifications> +``` + +## Notify + +Descendants dispatch a bubbling, non-composed event: + +```js +source.dispatchEvent(new CustomEvent("zen-notify", { + bubbles: true, + composed: false, + detail: { + version: 1, + id: "repository-removed", + tone: "success", + message: "Repository removed", + description: "The local checkout was preserved.", + announcement: "polite", + durationMs: 5000, + action: { + token: "opaque-action-token", + label: "Undo", + }, + }, +})); +``` + +Fields: + +| Field | Values | +| --- | --- | +| `version` | must be `1` | +| `id` | stable non-empty string | +| `tone` | `info`, `success`, `warning`, or `error` | +| `message` | required text | +| `description` | optional text | +| `announcement` | `none`, `polite`, or `assertive` | +| `durationMs` | 250–120000; defaults to 5000 | +| `persistent` | disables timeout when `true` | +| `action` | opaque token and visible label | + +The same ID updates and moves the existing record instead of creating a +duplicate. An ID is announced once during its current lifetime. + +## Dismiss and actions + +Dismiss through the public method or a scoped event: + +```js +notifications.dismiss("repository-removed"); + +source.dispatchEvent(new CustomEvent("zen-dismiss-notification", { + bubbles: true, + composed: false, + detail: { version: 1, id: "repository-removed" }, +})); +``` + +Action buttons dispatch `zen-notification-action` with +`{ version, id, token }`. The opaque token remains in private component state +and is never written into DOM attributes or source markup. + +Removal dispatches `zen-notification-removed` with `{ version, id, reason }`. + +## Bounds and timing + +- Three records are rendered. +- Twenty additional records may remain queued. +- Overflow evicts the oldest finite record; all-persistent queues reject new + records. +- Queued records do not count down. +- Finite timers pause while hovered, while focus remains inside, and while the + document is hidden. +- Disconnecting clears records, timers, announcements, and internal DOM. + +## Accessibility + +- Polite and assertive announcements use separate persistent live regions. +- Announcement priority is explicit and independent of tone. +- Announcement messages are serialized so bursts remain observable. +- Actions and dismissal use native buttons. +- New notifications never move focus. +- Keyed rendering preserves focused controls when unrelated records change. +- Forced-colors and reduced-motion modes are supported by component CSS. + +Events with `composed: true` are rejected. This keeps nested application scopes +isolated; adapters crossing a ShadowRoot must validate and redispatch a new +non-composed event deliberately.