Notification-center component
components specs/components/notification-center.kmd
The in-app notification inbox: a header trigger (a bell + an unread counter badge) that opens a panel listing past notifications — grouped into sections/tabs, each item read or unread, with mark-all-read and an empty state. It is the PERSISTENT counterpart to the transient snackbar: a snackbar flashes once and is gone; the center keeps the history so a user who missed the flash can still find it. gov.br ships a header `notification` dropdown; KDS had snackbars but no inbox.
When this spec applies
Primary triggers
- Render a notification center / inbox
All triggers
- Add a header notification bell + inbox panel (past notifications, unread count)
- Let a user review/mark-read notifications they missed
- gov.br notification-center parity (GVB-08)
Specification body
Notification center
Design-forward spec. Unlike the sibling gov.br-pack specs, this one is written ahead of its Kroma widget — the in-app inbox component does not exist yet (
engines/sdk/kroma/src/notify_center.rsis the desktop daemon's history store + DND, kroma#152, not this UI). The widget is tracked as kroma#312 and MUST conform to this contract.
R1 — What it is, and what it is NOT
The notification center is the persistent notification surface: an inbox of
notifications the app has raised, reachable any time from a header trigger. It is the
counterpart to the snackbar (snackbars.kmd), which is transient — a snackbar
appears briefly, is not stackable history, and auto-dismisses. A notification worth
keeping is shown as a snackbar and recorded in the center; an ephemeral confirmation
is a snackbar only. The center is also distinct from the OS desktop-notification daemon
(notify_center.rs): in a desktop Koder app the center MAY render that daemon's
history store as its data source; a web/mobile app supplies its own.
R2 — Anatomy
- Trigger — a bell icon button in the top bar (
topbar-placement.kmd) carrying an unread counter badge (badges.kmd) — the count of unread items, hidden at 0, capped (e.g.9+). Activating it toggles the panel. - Panel — a dropdown/sheet anchored to the trigger, holding:
- an optional section/tab row (e.g.
All/Unread, or by category/urgency), - a list of notification items (newest first),
- a mark-all-read action,
- and, when empty, an empty state.
- an optional section/tab row (e.g.
R3 — Notification item
Each row shows: an optional category/app icon, the summary (title), an optional
body snippet, and a relative timestamp. Its data mirrors the store's
HistoryEntry (id, app_name, summary, body, urgency, ts_unix, category). An unread
item is visually distinguished (a leading dot / accent rail / weight) from a read
one. Opening/activating an item marks it read and MAY route to its target.
R4 — Read/unread state
Read/unread is the center's own UI-layer state — the desktop HistoryEntry store
does not carry a read flag (it is a log). The center tracks which ids are read,
derives the unread counter (R2) from it, and mark-all-read clears every unread in
the current view. Read state persists across sessions (alongside the history).
R5 — Ordering & urgency
Items list newest-first. Urgency (freedesktop 0 low / 1 normal / 2 critical, per
the store) MAY raise a critical item's prominence (accent/pin) but does not reorder
past the newest-first rule within a section. A Do-Not-Disturb mode (the daemon's
is_dnd, desktop) suppresses the transient snackbar but the item is still recorded
and appears in the center — the center is how you catch up on what DND muted.
R6 — Accessibility
- The trigger is a labelled button; its accessible name includes the unread count ("Notifications, 3 unread").
- New notifications announce via an ARIA live region (
politefor normal,assertive/alert only for critical) — the transient-announce path, separate from the panel. - The open panel is a focus-managed dropdown (like the account menu): Escape closes, focus is trapped while open, list items are arrow-navigable, Enter/Space activates.
- Each item's read/unread state and the counter are exposed to AT.
R7 — gov.br parity
Satisfies the gov.br notification header dropdown (govbr-ds-parity, GVB-08): bell +
counter trigger, a panel listing items, mark-all-read, read/unread.
Não-escopo
- The transient toast/snackbar surface —
snackbars.kmd. - The desktop notification daemon (delivery, DND enforcement, freedesktop protocol)
—
notify_center.rs/kroma-notifyd; the center is a reader of its history, not the daemon. - The Kroma widget implementation — kroma#312 (this spec is its contract).
Requirements (testable)
Requirement: Header trigger with unread counter badge {#req-notification-center-trigger-badge}
The notification center SHALL be reachable from a bell icon button in the top bar carrying an unread counter badge; the badge SHALL show the count of unread items, SHALL be hidden when the count is zero, and SHALL cap the displayed count (e.g. 9+). Activating the trigger SHALL toggle the panel open and closed.
Scenario: Badge reflects unread count and hides at zero
- GIVEN a notification center with 3 unread items
- WHEN the trigger bell is rendered
- THEN the counter badge reads "3"
- WHEN every item is marked read so the unread count reaches 0
- THEN the counter badge is hidden
Scenario: Badge caps the displayed count
- GIVEN a notification center with 12 unread items
- WHEN the trigger is rendered
- THEN the counter badge displays the capped value "9+" rather than "12"
Scenario: Trigger toggles the panel
- GIVEN the notification panel is closed
- WHEN the user activates the bell trigger
- THEN the panel opens anchored to the trigger
- WHEN the user activates the bell trigger again
- THEN the panel closes
Requirement: Notification item read/unread distinction and activation {#req-notification-center-item-read-state}
Each notification item SHALL be visually distinguished as unread (a leading dot / accent rail / weight) versus read, and activating an item SHALL mark it read; read/unread state SHALL be the center's own UI-layer state and SHALL persist across sessions.
Scenario: Unread item is visually distinguished
- GIVEN a list containing one unread item and one read item
- WHEN the panel renders the list
- THEN the unread item carries its unread indicator (leading dot / accent rail / weight)
- AND the read item does not carry the unread indicator
Scenario: Activating an item marks it read
- GIVEN an unread notification item and an unread counter of 1
- WHEN the user opens/activates that item
- THEN the item becomes read and loses its unread indicator
- AND the unread counter decrements to 0
Scenario: Read state persists across sessions
- GIVEN an item that was marked read in a previous session
- WHEN the center is reloaded in a new session
- THEN that item is still shown as read
Requirement: Mark-all-read action {#req-notification-center-mark-all-read}
The panel SHALL provide a mark-all-read action that clears every unread item in the current view and drives the unread counter to zero.
Scenario: Mark-all-read clears the current view
- GIVEN the panel showing 4 unread items in the current view
- WHEN the user activates the mark-all-read action
- THEN all 4 items become read
- AND the unread counter badge is hidden
Requirement: Newest-first ordering with urgency and DND recording {#req-notification-center-ordering-dnd}
Items SHALL list newest-first within a section; critical urgency MAY raise an item's prominence but SHALL NOT reorder it past the newest-first rule; and an item suppressed by Do-Not-Disturb SHALL still be recorded and appear in the center.
Scenario: Items list newest-first
- GIVEN three notifications recorded at ascending timestamps t1 < t2 < t3
- WHEN the panel renders the list
- THEN the item with timestamp t3 appears first and the item with t1 appears last
Scenario: Critical urgency does not break newest-first
- GIVEN a critical (urgency 2) item recorded before a newer normal (urgency 1) item
- WHEN the list renders within the section
- THEN the newer normal item is ordered ahead of the older critical item
Scenario: DND-suppressed notification is still recorded
- GIVEN Do-Not-Disturb mode is active
- WHEN a notification is raised and its transient snackbar is suppressed
- THEN the notification is recorded and appears as an item in the center
Requirement: Accessibility and keyboard obligations {#req-notification-center-a11y}
The trigger SHALL be a labelled button whose accessible name includes the unread count; new notifications SHALL announce via an ARIA live region (polite for normal, assertive only for critical); and the open panel SHALL be a focus-managed dropdown where Escape closes, focus is trapped while open, list items are arrow-navigable, and Enter/Space activates.
Scenario: Trigger accessible name includes unread count
- GIVEN a notification center with 3 unread items
- WHEN assistive technology queries the trigger button
- THEN its accessible name includes the unread count (e.g. "Notifications, 3 unread")
Scenario: New notifications announce at the correct live-region politeness
- GIVEN the ARIA live region is present
- WHEN a normal-urgency notification arrives
- THEN it is announced via the
politelive region - WHEN a critical-urgency notification arrives
- THEN it is announced via the
assertive/alert live region
Scenario: Open panel is keyboard focus-managed
- GIVEN the notification panel is open with focus inside it
- WHEN the user presses Escape
- THEN the panel closes
- WHEN the panel is reopened and the user presses Arrow keys
- THEN focus moves between list items
- AND pressing Enter or Space activates the focused item
References
meta/docs/stack/specs/components/snackbars.kmdmeta/docs/stack/specs/components/badges.kmdmeta/docs/stack/specs/app-layout/topbar-placement.kmdengines/sdk/kroma/src/notify_center.rstools/design-gen/docs/govbr-ds-gap-analysis.md