NotificationCenter
A notification list with per-item mark-as-read and delete, an empty state, and each entry rendered as a clickable card with its timestamp.
01 Usage
Basic
Notifications
<script lang="ts">
import { NotificationCenter } from '@urbicon-ui/auth';
const demoNotifications = [
{
id: '1',
userId: 'demo',
title: 'Welcome!',
body: 'Your account has been created.',
type: 'system',
typeKey: 'welcome',
url: null,
icon: null,
createdAt: new Date(Date.now() - 5 * 60000),
readAt: null
},
{
id: '2',
userId: 'demo',
title: 'New feature available',
body: 'Passkey login is now supported.',
type: 'system',
typeKey: 'feature',
url: null,
icon: null,
createdAt: new Date(Date.now() - 3600000),
readAt: new Date()
}
];
</script>
<NotificationCenter
notifications={demoNotifications}
onMarkAsRead={(id) => console.log('Mark as read:', id)}
onMarkAllAsRead={() => console.log('Mark all as read')}
onDelete={(id) => console.log('Delete:', id)}
/>
02 Accessibility
Each notification is a button
The body of every item is a <button>, not a
click handler on the <li>, so it is reachable
with Tab and activatable with Enter or Space without any extra ARIA.
Unread rows say so
The unread dot is aria-hidden="true" — it is
decoration — and the localized "Unread" sits beside it as visually hidden text inside the
row's button, ahead of the title, so the button's accessible name begins with it — "Unread
Deploy finished …" where a sighted user sees the dot. The data-unread attribute stays for CSS, and the hidden word survives unstyled: it is the state, not a default look. A
custom item snippet replaces the whole row, so the marker
is yours to render there.
The delete button names its notification
It renders a × glyph, so the name comes from an aria-label: the localized "Delete" with the
notification's title appended — "Delete — Deploy finished". Ten notifications are ten
distinguishable buttons rather than ten identical ones, and the name still begins with the
visible word, so voice control ("click Delete") keeps working.
The list is announced, and the timestamp is machine-readable
Items sit in a <ul>, so the count is
announced before the contents. The relative age renders in a <time> element whose datetime attribute carries the ISO instant behind the
rounded label, so "1h ago" has an exact time attached to it. A record whose timestamp does not
parse drops the attribute rather than the row.
03 API Reference
Prop | Type | Default | Description | |
|---|---|---|---|---|
notifications required | import('../../../server/adapters/types.js').NotificationRecord[] | — | Notification records to display. | |
class | string | — | Extra classes on the root element. | |
item | Snippet<[import('../../../server/adapters/types.js').NotificationRecord]> | — | Custom notification item renderer. | |
onDelete | (id: string) => void | — | Called when a notification is deleted. | |
onMarkAllAsRead | () => void | — | Called when all notifications are marked as read. | |
onMarkAsRead | (id: string) => void | — | Called when a single notification is marked as read. | |
onNotificationClick | (
notification: import('../../../server/adapters/types.js').NotificationRecord
) => void | — | Called when a notification is clicked (e.g. to navigate to a URL).
SECURITY: notification.url is DB-/server-sourced and untrusted; before
passing it to goto() / window.location, validate it is same-origin or
relative (reject javascript: and absolute cross-origin URLs). Never
navigate to a raw notification.url. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ NotificationCenter: { … } }}>.
Resolves after the provider defaults and before this instance's own
slotClasses, so a project-wide look lives in one place instead of being
repeated at every usage site. | |
slotClasses | Partial<Record<'root' | 'header' | 'list' | 'item' | 'empty', string>> | — | Per-slot class overrides. | |
t | PartialAuthLocale | — | Locale overrides, deep-merged over the bundle registered for the active
locale — English unless registerAuthLocale ran for it. Pass any subset,
from a single string to a whole tree. | |
unstyled | boolean | — | Strip all default styling. |
04 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
NotificationCenterProps | interface | props | 0 | — | |
PartialAuthLocale | type | helper | 1 | Consumer-facing locale input: any subset of AuthLocale. Components
accept this as their t prop and deep-merge it over the bundle registered
for the active locale — English unless registerAuthLocale ran for it — so
overriding a single string never silently blanks the rest. | |
DeepPartial | type | helper | 0 | Recursive partial: every branch and leaf becomes optional. | |
AuthLocale | interface | helper | 0 | The complete auth locale bundle. Every key is required: the bundles this
package ships (en, de) satisfy the full shape, and consumer overrides
enter as PartialAuthLocale, deep-merged by mergeAuthLocale over the
bundle registered for the active locale — English unless registerAuthLocale
ran for it — so component markup reads keys directly, without per-key
?? '…' fallback literals.
Placeholder convention: dynamic values use **single-brace** tokens
({n}, {name}, {email}) that the consuming component substitutes itself
via String.replace('{token}', value). There is deliberately **no**
{{…}} runtime interpolator in this package. The key-based translator twin
(authT/at) was removed, so {{…}} here would render verbatim. |
05 Installation
Import
import { NotificationCenter } from '@urbicon-ui/auth';