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.