@k2b/ui
Operational surfaces
Panel headers, data panels, notices, ranges, and status vocabulary.
Persistent notices and six semantic status tones keep operational meaning consistent across panels and dense rows.
System status
Updated just now
Release deployed
Version 2.4 is serving all regions.
Maintenance scheduled
Telemetry pauses briefly at 02:00 UTC.
Backfill complete
All historical samples are available.
Delayed source
The last sample arrived 8 minutes ago.
Database unavailable
Current diagnostics could not be loaded.
Routes
6 states
TSX
Copyimport { DataPanel, NoticeCard, PanelHeader, RangePicker, StatusBadge,} from "@k2b/ui"; const notices = [ { tone: "neutral", title: "Release deployed", detail: "Version 2.4 is serving all regions." }, { tone: "info", title: "Maintenance scheduled", detail: "Telemetry pauses briefly at 02:00 UTC." }, { tone: "success", title: "Backfill complete", detail: "All historical samples are available." }, { tone: "warning", title: "Delayed source", detail: "The last sample arrived 8 minutes ago." }, { tone: "danger", title: "Database unavailable", detail: "Current diagnostics could not be loaded." },] as const; <div class="ui-demo-form-grid"> <PanelHeader title="System status" subtitle="Updated just now" actions={ <RangePicker value="24h" options={[ { value: "1h", href: "?range=1h" }, { value: "24h", href: "?range=24h" }, ]} /> } /> <NoticeCard.Grid items={notices}> {(notice) => <NoticeCard tone={notice.tone} title={notice.title} detail={notice.detail} />} </NoticeCard.Grid> <DataPanel title="Routes" subtitle="6 states"> <div class="ui-demo-row ui-data-panel-demo-body"> <StatusBadge label="Online" tone="ok" /> <StatusBadge label="Attention" tone="warning" /> <StatusBadge label="Failed" tone="error" /> <StatusBadge label="Degraded" tone="degraded" /> <StatusBadge label="Refreshing telemetry and dependency health" tone="running" variant="dot" title="Refreshing telemetry and dependency health" /> <StatusBadge label="Disabled" tone="neutral" variant="text" /> </div> </DataPanel></div>The observability components give admin and operations pages a shared structure: PanelHeader names a panel, DataPanel frames records, StatusBadge states health, NoticeCard keeps findings visible, and RangePicker selects a URL-backed time window.
The application owns queries, filters, domain wording, and recovery actions.
Use observability surfaces
Use these components together when an operations page needs to answer:
- What scope is shown?
- Is the system healthy?
- What needs attention?
- Which records explain the summary?
- Which time window produced the result?
Do not use a toast for a finding that must remain visible. Do not use an empty state for data that failed to load.
Import
import {
DataPanel,
DataTable,
NoticeCard,
PanelHeader,
RangePicker,
StatusBadge,
} from "@k2b/ui";Properties
PanelHeader
PanelHeader renders a title, optional subtitle, and trailing actions. The parent owns the surface, border, padding, and spacing.
| Property | Type | Default | Purpose |
|---|---|---|---|
title |
JSX.Element |
required | Names the panel. |
subtitle |
JSX.Element |
none | States a count, scope, or current view. |
actions |
JSX.Element |
none | Adds compact trailing controls. |
as |
"h1" | "h2" | "h3" |
"h2" |
Preserves heading hierarchy. |
size |
"sm" | "md" |
"sm" |
Selects panel or page-level type scale. |
DataPanel already includes PanelHeader.
DataPanel
DataPanel frames a list or table. It accepts slots for search, filters, actions, rows, and a footer.
Set error when the query failed. It takes precedence over isEmpty. Set isEmpty only after the query succeeded with no rows.
| Property | Purpose |
|---|---|
title, subtitle, actions |
Describe the panel and its scope. |
search, filters |
Insert application-owned controls. |
children |
Render the list or table. |
error |
Replace the body with a failed-load state. |
isEmpty, empty |
Replace the body after a successful empty result. |
footer |
Add pagination or other controls below the rows. |
as |
Use "h1" only when the panel is the primary page content. |
Search remains a slot because client islands must stay in the consuming application.
StatusBadge
StatusBadge separates shared status meaning from domain wording.
| Tone | Meaning |
|---|---|
ok |
Healthy or successfully completed. |
warn |
Attention is required, but the operation can continue. |
error |
Failed, unavailable, or blocked. |
degraded |
Running with an unavailable dependency or reduced capability. |
running |
Work is in progress. |
neutral |
Disabled, unknown, or informational. |
Use variant="dot" in dense tables and variant="text" when the surrounding layout already provides a boundary. Keep the visible label specific: Offline, Failed, and Rejected can all use the error tone.
Long labels truncate visually without losing their text in the DOM. Add
title when the complete wording must also be available on hover. The
running icon or dot animates only when reduced motion is not requested.
NoticeCard
NoticeCard keeps one diagnostic finding visible. tone accepts neutral,
info, success, warning, or danger; the default is warning. title names the
finding and detail provides the evidence.
NoticeCard.Grid receives an items array and a render function. It renders
nothing for an empty array. One item stays in one column; two items become two
columns at 48rem; three or more use two columns at 48rem and three at 80rem.
<NoticeCard.Grid items={findings}>
{(finding) => (
<NoticeCard
tone={finding.tone}
title={finding.title}
detail={finding.detail}
/>
)}
</NoticeCard.Grid>
<NoticeCard.Grid items={[]}>
{() => <NoticeCard title="Not rendered" />}
</NoticeCard.Grid>RangePicker
RangePicker renders ordinary links because the selected window belongs in the URL and affects server queries.
| Property | Purpose |
|---|---|
options |
Supplies { value, label?, href } for every available window. |
value |
Marks the current option with aria-current. |
label |
Adds a visible caption. Pass null to omit it. |
ariaLabel |
Names the navigation when no visible label is present. |
Build every href from the current filter state so changing the range does not discard unrelated filters.
Accessibility
Choose heading levels from the page hierarchy. Every status needs a visible label; tone and icons are supplementary.
Name a label-free RangePicker with ariaLabel. Keep diagnostic titles specific enough to scan without their detail. Search and filter slots retain responsibility for their own labels and keyboard behavior.
Runtime
All five components render on the server. RangePicker works without hydration. Interactive search or filter controls passed into DataPanel belong to the consuming island.
Example
<NoticeCard.Grid items={findings}>
{(finding) => (
<NoticeCard
tone={finding.tone}
title={finding.title}
detail={finding.detail}
/>
)}
</NoticeCard.Grid>
<div class="app-badge-row">
<StatusBadge tone="ok" label="Online" />
<StatusBadge tone="warning" label="Overdue" />
<StatusBadge tone="error" label="Failed" />
<StatusBadge
tone="degraded"
label="Diagnostics unavailable"
title="Postgres diagnostics unavailable"
/>
<StatusBadge tone="running" label="Refreshing" variant="dot" />
<StatusBadge tone="neutral" label="Disabled" variant="text" />
</div>
<DataPanel
title="Routes"
subtitle={`${rows.length} of ${total} routes`}
actions={
<RangePicker
label={null}
ariaLabel="Request window"
value={range}
options={ranges.map((value) => ({
value,
href: buildUrl(filters, { range: value }),
}))}
/>
}
error={loadError}
isEmpty={rows.length === 0}
empty="No route produced traffic in this window."
>
<DataTable
rows={rows}
columns={columns}
renderCell={({ row, col, render, value }) =>
col.id === "state"
? <StatusBadge tone={row.state} label={row.stateLabel} />
: render(value)
}
/>
</DataPanel>