Status Blocks Components #
A sophisticated composition-based architecture for displaying system status information. This collection provides flexible, accessible, and beautifully designed components for building status pages, incident reports, and uptime dashboards.
Overview #
The blocks components are built on a composition pattern where simple primitives can be combined to create complex status displays. Rather than monolithic components with dozens of props, you compose smaller focused components together to achieve your desired layout and behavior.
Key Features #
- Composition-First: Build complex UIs from simple, focused primitives
- Type-Safe: Full TypeScript support with discriminated unions
- Accessible: ARIA roles, keyboard navigation, screen reader support
- Themeable: CSS variables for colors, dark mode support
- Responsive: Mobile-first design with adaptive layouts
- Interactive: Keyboard navigation, hover cards, collapsible groups
- Localizable: Optional i18n provider for translated labels and locale-aware date formatters; sensible English defaults out of the box
Architecture #
Design Principles #
- Separation of Concerns: Each component has a single, clear purpose
- Data Attributes: Components use
data-*attributes for status-based styling - CSS Group Patterns: Parent components establish context via
groupclasses - Headless Hooks: Complex interactions separated from presentation (e.g.,
useStatusBar) - Composition over Configuration: Combine components rather than configure via props
Component Layers #
┌──────────────────────────────────────────────────────────┐
│ Layout Components (Status, StatusComponent, StatusEvent) │
│ └─ Establish context via data-variant/data-status │
├──────────────────────────────────────────────────────────┤
│ Display Components (StatusIcon, StatusMessage, etc.) │
│ └─ Respond to parent context via CSS selectors │
├──────────────────────────────────────────────────────────┤
│ Interactive Components (StatusBar, StatusComponentGroup) │
│ └─ Manage state and interactions via hooks │
├──────────────────────────────────────────────────────────┤
│ Utility Components (StatusTimestamp, StatusBlank, etc.) │
│ └─ Provide specialized functionality │
├──────────────────────────────────────────────────────────┤
│ i18n (StatusBlocksI18nProvider, useStatusBlocksLabels) │
│ └─ Supply translated labels + locale-aware date │
│ formatters; English defaults when unmounted │
└──────────────────────────────────────────────────────────┘
Core Concepts #
Status Types #
All components work with a unified StatusType:
- success: All systems operational (green)
- degraded: Partial performance issues (yellow)
- error: Outage or major issue (red)
- info: Maintenance or informational (blue)
- empty: No data available (muted gray)
Data Attributes #
Components use data-variant or data-status attributes to establish status context:
<StatusComponent variant="degraded">
{/* Child components automatically style for degraded status */}
</StatusComponent>
Child components use CSS selectors to respond:
.group-data-[variant=degraded]/component:text-warning
Composition Pattern #
Rather than:
<Monitor
name="API"
status="success"
uptime="99.9%"
showIcon={true}
showStatus={true}
/>
We compose:
<StatusComponent variant="success">
<StatusComponentHeader>
<StatusComponentHeaderLeft>
<StatusComponentIcon />
<StatusComponentTitle>API</StatusComponentTitle>
</StatusComponentHeaderLeft>
<StatusComponentHeaderRight>
<StatusComponentUptime>99.9%</StatusComponentUptime>
<StatusComponentStatus />
</StatusComponentHeaderRight>
</StatusComponentHeader>
</StatusComponent>
Component Categories #
1. Layout Components #
Primary containers that establish page structure and status context.
Status Components #
- Status: Root container for status pages
- StatusHeader: Header with brand and title
- StatusTitle: Page title
- StatusDescription: Subtitle text
- StatusContent: Main content area
- StatusBrand: Logo/brand image
- StatusIcon: Status indicator icon
Monitor Components #
- StatusComponent: Individual monitor/service container
- StatusComponentHeader: Monitor header layout
- StatusComponentHeaderLeft: Left-aligned header content
- StatusComponentHeaderRight: Right-aligned header content
- StatusComponentBody: Monitor content area
2. Status Display Components #
Components for showing monitor status, uptime, and metrics.
- StatusComponentIcon: Status indicator for monitors
- StatusComponentTitle: Monitor/service name
- StatusComponentDescription: Info tooltip for monitors
- StatusComponentUptime: Uptime percentage display
- StatusComponentStatus: Automatic status label
- StatusComponentFooter: Date range footer
3. Status Banner Components #
Prominent banner for displaying system-wide status.
- StatusBanner: Complete banner with icon/message/timestamp
- StatusBannerContainer: Base container for custom banners
- StatusBannerMessage: Automatic status message
- StatusBannerTitle: Colored title bar
- StatusBannerContent: Main content area
- StatusBannerIcon: Banner status icon
- StatusBannerTabs: Tab container for multi-section banners
- StatusBannerTabsList: Tab navigation
- StatusBannerTabsTrigger: Individual tab button
- StatusBannerTabsContent: Tab panel content
4. Status Bar Components #
Interactive uptime timeline with hover cards.
- StatusBar: Main timeline component
- useStatusBar: Headless hook for custom implementations
- StatusBarEvent: Event badge in hover cards
- StatusBarSkeleton: Loading skeleton
5. Event Components #
Components for displaying incident reports and maintenance.
Container Components #
- StatusEventGroup: Feed container
- StatusEvent: Individual event container
- StatusEventContent: Event content wrapper
Content Components #
- StatusEventTitle: Event title
- StatusEventTitleCheck: Resolved indicator
- StatusEventAffected: Affected services container
- StatusEventAffectedBadge: Single service badge
Date/Time Components #
- StatusEventDate: Date with relative time
- StatusEventAside: Sidebar date (desktop)
Timeline Components #
- StatusEventTimelineReport: Incident updates timeline
- StatusEventTimelineReportUpdate: Single update entry
- StatusEventTimelineMaintenance: Maintenance entry
- StatusEventTimelineTitle: Timeline entry title
- StatusEventTimelineMessage: Timeline entry message
- StatusEventTimelineDot: Colored status dot
- StatusEventTimelineSeparator: Connecting line
6. Empty State Components #
Visualizations for empty states.
- StatusBlankEvents: Empty state for incidents
- StatusBlankMonitors: Empty state for monitors
- StatusBlankContainer: Generic empty state container
- StatusBlankTitle: Empty state title
- StatusBlankDescription: Empty state description
7. Utility Components #
Specialized functionality components.
- StatusTimestamp: Timezone-aware timestamp with hover details
- StatusFeed: Unified feed of reports and maintenance
- StatusComponentGroup: Collapsible monitor grouping
- StatusBlankAction: Styled chrome wrapper for empty-state CTAs (links/buttons)
8. i18n / Localization #
- StatusBlocksI18nProvider: React context that supplies translated labels + locale-aware date formatters to all blocks
- useStatusBlocksLabels: Hook blocks read from; falls back to
defaultStatusBlocksLabelswhen no provider is mounted - defaultStatusBlocksLabels: English (
en-US) defaults exported fromstatus.utils.ts
9. Page Chrome (Header / Footer / Switchers) #
Presentation-only chrome blocks for assembling a complete status page around the body components above. All routing-, theme-, and locale-agnostic — caller wires their own next/link, next-themes, next-intl, etc.
Shell #
- StatusPageShell: Outer
<div>wrapper (full-height column with gap) - StatusPageMain: Inner
<main>content column with embed-aware Tailwind classes that activate only when an ancestor setsdata-embed=trueon agroup/embedelement
Header #
- StatusPageHeader:
<header>wrapper - StatusPageHeaderContent: Inner
<nav>row with default centering and padding - StatusPageHeaderBrand: Left-side fixed-width slot for the brand button
- StatusPageHeaderBrandButton: Outlined size-8 icon button (uses
asChildfor the link) - StatusPageHeaderBrandFallback: Letter-from-title typography for when no page icon is present
- StatusPageHeaderNav: Desktop nav
<ul> - StatusPageHeaderNavItem: Single nav entry with active styling (
asChildfor the link) - StatusPageHeaderActions: Right-side fixed-width cluster (subscribe, contact, mobile-menu trigger)
Footer #
- StatusPageFooter:
<footer>wrapper - StatusPageFooterContent: Inner row with default centering and padding
- StatusPagePoweredBy: "powered by …" line — pass the link element as children
- StatusPageFooterActions: Right-side cluster (last-updated, locale, theme)
Get-in-Touch #
- StatusPageGetInTouchIcon: Ghost icon button with tooltip chrome (uses
asChildfor the link) - StatusPageGetInTouchButton: Outlined text-label button (uses
asChildfor the link)
Status Updates (subscription channels) #
- StatusUpdates: Popover root for "Get updates" controls
- StatusUpdatesTrigger: Trigger button (default label from
labels.subscribe) - StatusUpdatesContent: Popover content wrapper
- StatusUpdatesSection: Per-channel description + content slot
- StatusUpdatesCopyInput: Copyable URL input with toast feedback
- StatusUpdatesRss: RSS + optional Atom copy boxes
- StatusUpdatesJson: JSON feed copy box
- StatusUpdatesSlack: Slack
/feed subscribesnippet - StatusUpdatesSsh: SSH command copy box
Switchers #
- StatusThemeSwitcher: Agnostic light/dark/system switcher — caller owns
value+onValueChange - StatusThemeSwitcherSkeleton: Same footprint as the trigger; render before the active theme is known
- StatusLocaleSwitcher: Agnostic locale picker — caller owns
value+onValueChange+localeslist - StatusLocaleSwitcherSkeleton: Same footprint; render before the active locale is known
Composition Patterns #
Pattern 1: Basic Monitor Display #
<StatusComponent variant="success">
<StatusComponentHeader>
<StatusComponentHeaderLeft>
<StatusComponentIcon />
<StatusComponentTitle>Production API</StatusComponentTitle>
<StatusComponentDescription>
Handles all production traffic
</StatusComponentDescription>
</StatusComponentHeaderLeft>
<StatusComponentHeaderRight>
<StatusComponentUptime>99.95%</StatusComponentUptime>
<StatusComponentStatus />
</StatusComponentHeaderRight>
</StatusComponentHeader>
<StatusComponentBody>
<StatusBar data={uptimeData} />
<StatusComponentFooter data={uptimeData} />
</StatusComponentBody>
</StatusComponent>
Pattern 2: Status Banner with Tabs #
<StatusBannerTabs status="degraded" defaultValue="impact">
<StatusBannerTabsList>
<StatusBannerTabsTrigger value="impact" status="degraded">
Impact
</StatusBannerTabsTrigger>
<StatusBannerTabsTrigger value="updates" status="degraded">
Updates
</StatusBannerTabsTrigger>
</StatusBannerTabsList>
<StatusBannerTabsContent value="impact">
<StatusBannerTitle>Degraded Performance</StatusBannerTitle>
<StatusBannerContent>
<p>We are experiencing elevated latency across all regions.</p>
<StatusEventAffected>
<StatusEventAffectedBadge>API</StatusEventAffectedBadge>
<StatusEventAffectedBadge>Database</StatusEventAffectedBadge>
</StatusEventAffected>
</StatusBannerContent>
</StatusBannerTabsContent>
<StatusBannerTabsContent value="updates">
<StatusBannerContent>
<StatusEventTimelineReport updates={incidentUpdates} />
</StatusBannerContent>
</StatusBannerTabsContent>
</StatusBannerTabs>
Pattern 3: Incident Timeline #
<StatusEvent>
<StatusEventAside>
<StatusEventDate date={incidentDate} />
</StatusEventAside>
<StatusEventContent>
<div className="flex items-center gap-2">
<StatusEventTitle>API Gateway Outage</StatusEventTitle>
<StatusEventTitleCheck />
</div>
<StatusEventAffected>
<StatusEventAffectedBadge>API Gateway</StatusEventAffectedBadge>
<StatusEventAffectedBadge>Authentication</StatusEventAffectedBadge>
</StatusEventAffected>
<StatusEventTimelineReport
updates={[
{
status: "resolved",
message: "All services have been restored",
date: new Date("2024-01-15T12:00:00Z")
},
{
status: "monitoring",
message: "Fix deployed, monitoring recovery",
date: new Date("2024-01-15T11:45:00Z")
},
{
status: "identified",
message: "Root cause identified in load balancer",
date: new Date("2024-01-15T11:15:00Z")
},
{
status: "investigating",
message: "Investigating API timeouts",
date: new Date("2024-01-15T11:00:00Z")
}
]}
/>
</StatusEventContent>
</StatusEvent>
Pattern 4: Unified Feed #
<StatusFeed
statusReports={[
{
id: 1,
title: "API Outage",
affected: ["API", "Database"],
updates: [
{
status: "resolved",
message: "Issue resolved",
date: new Date()
}
]
}
]}
maintenances={[
{
id: 2,
title: "Database Upgrade",
message: "Upgrading to PostgreSQL 15",
affected: ["Database"],
from: new Date("2024-01-20T02:00:00Z"),
to: new Date("2024-01-20T04:00:00Z")
}
]}
/>
Pattern 5: Grouped Monitors #
<StatusContent>
<StatusComponentGroup
title="Core Services"
status="success"
defaultOpen={true}
>
<StatusComponent variant="success">
<StatusComponentHeader>
<StatusComponentHeaderLeft>
<StatusComponentIcon />
<StatusComponentTitle>API Server</StatusComponentTitle>
</StatusComponentHeaderLeft>
</StatusComponentHeader>
</StatusComponent>
<StatusComponent variant="success">
<StatusComponentHeader>
<StatusComponentHeaderLeft>
<StatusComponentIcon />
<StatusComponentTitle>Database</StatusComponentTitle>
</StatusComponentHeaderLeft>
</StatusComponentHeader>
</StatusComponent>
</StatusComponentGroup>
<StatusComponentGroup
title="Third-Party Services"
status="degraded"
defaultOpen={false}
>
<StatusComponent variant="degraded">
<StatusComponentHeader>
<StatusComponentHeaderLeft>
<StatusComponentIcon />
<StatusComponentTitle>Payment Gateway</StatusComponentTitle>
</StatusComponentHeaderLeft>
</StatusComponentHeader>
</StatusComponent>
</StatusComponentGroup>
</StatusContent>
Internationalization (i18n) #
All user-facing strings, ARIA labels, and date formatters used by blocks are sourced from a single StatusBlocksLabels object via the useStatusBlocksLabels() hook. Blocks never import next-intl, react-intl, or any other i18n library directly — that contract makes them shadcn-registry-shippable while still letting consumer apps localize them.
Default behavior (no provider) #
Blocks render English (en-US) out of the box. No setup is required:
import { StatusBanner } from "@openstatus/ui/components/blocks/status-banner";
<StatusBanner status="success" />
// Renders: "All Systems Operational" with en-US date formatting
Internally, useStatusBlocksLabels() returns defaultStatusBlocksLabels (exported from status.utils.ts) when no provider is mounted.
Localizing in your app #
Mount <StatusBlocksI18nProvider> once near the root of your tree and pass a StatusBlocksLabels object:
"use client";
import { useMemo } from "react";
import { useTranslations, useLocale } from "next-intl"; // or any i18n library
import {
StatusBlocksI18nProvider,
type StatusBlocksLabels,
} from "@openstatus/ui/components/blocks/status-i18n";
export function MyStatusBlocksProvider({ children }: { children: React.ReactNode }) {
const t = useTranslations();
const locale = useLocale();
const labels = useMemo<StatusBlocksLabels>(() => {
const dateFmt = new Intl.DateTimeFormat(locale, { dateStyle: "long" });
const dateShortFmt = new Intl.DateTimeFormat(locale, { dateStyle: "medium" });
const dateTimeFmt = new Intl.DateTimeFormat(locale, {
dateStyle: "medium",
timeStyle: "short",
});
return {
systemStatus: {
success: { long: t("All Systems Operational"), short: t("Operational") },
degraded: { long: t("Degraded Performance"), short: t("Degraded") },
error: { long: t("Partial Outage"), short: t("Outage") },
info: { long: t("Maintenance"), short: t("Maintenance") },
empty: { long: t("No Data"), short: t("No Data") },
},
incidentStatus: {
resolved: t("Resolved"),
monitoring: t("Monitoring"),
identified: t("Identified"),
investigating: t("Investigating"),
},
requestStatus: {
success: t("Normal"),
degraded: t("Degraded"),
error: t("Error"),
info: t("Maintenance"),
empty: t("No Data"),
},
today: t("today"),
ongoing: t("ongoing"),
reportResolved: t("Report resolved"),
noRecentNotifications: t("No recent notifications"),
// ...remaining labels (see StatusBlocksLabels type for the full list)
ariaStatusTracker: t("Status tracker"),
ariaDayStatus: (n) => t("Day {n} status", { n }),
clickAgainToUnpin: t("Click again to unpin"),
durationIn: (s) => t("(in {duration})", { duration: s }),
durationEarlier: (s) => t("({timeFromLast} earlier)", { timeFromLast: s }),
durationFor: (s) => t("(for {duration})", { duration: s }),
durationAcross: (s) => t("across {duration}", { duration: s }),
formatDate: (d) => dateFmt.format(d),
formatDateShort: (d) => dateShortFmt.format(d),
formatDateTime: (d) => dateTimeFmt.format(d),
formatDateRange: (from, to) =>
from && to ? `${dateFmt.format(from)} – ${dateFmt.format(to)}` : "",
};
}, [t, locale]);
return (
<StatusBlocksI18nProvider value={labels}>{children}</StatusBlocksI18nProvider>
);
}
Mount it at the locale layout (or app root) above any block usage:
<NextIntlClientProvider locale={locale} messages={messages}>
<MyStatusBlocksProvider>{children}</MyStatusBlocksProvider>
</NextIntlClientProvider>
Why a context (not props)? #
Blocks like StatusComponentStatus and StatusBannerMessage use CSS-only conditional rendering (data-[variant=...]:block) to swap labels — they need all status labels available at render time, not just "the active one." A context gives them that without prop drilling through every parent.
Why no next-intl import inside blocks? #
@openstatus/ui ships through the shadcn registry. A direct dependency on next-intl would force every consumer (Next.js or not) to install it. The provider pattern lets the consumer's app — which already has its own i18n setup — bridge translations into the blocks.
Customizing dates only #
If you only need locale-aware dates (English copy is fine), supply just the formatter fields and spread the defaults for the rest:
import { defaultStatusBlocksLabels } from "@openstatus/ui/components/blocks/status.utils";
const labels: StatusBlocksLabels = {
...defaultStatusBlocksLabels,
formatDate: (d) => new Intl.DateTimeFormat(locale, { dateStyle: "long" }).format(d),
formatDateShort: (d) => new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(d),
formatDateTime: (d) =>
new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(d),
};
Extension Slots #
Blocks expose slot props so app-specific concerns (Next.js <Link>, markdown rendering, etc.) stay out of the registry while still being injectable by consumers.
| Block | Slot | Purpose |
|---|---|---|
StatusEventTimelineReport |
renderMessage?: (msg: string) => ReactNode |
Pipe report message body through a markdown renderer (<ProcessMessage>, etc.) |
StatusEventTimelineMaintenance |
renderMessage?: (msg: string) => ReactNode |
Same, for maintenance bodies |
StatusEventTimelineTitle |
asChild?: boolean |
Wrap the title in a custom element (e.g. <Link>) — narrow click target without invalidating nested-interactive HTML |
StatusBar |
renderEvent?: (event, index) => ReactNode |
Wrap event badges with a Next <Link> (for routing to event detail) |
StatusBar |
renderBar? / renderCard? |
Customize bar segments and hover-card content |
StatusFeed |
renderEvent?: (event, content) => ReactNode |
Wrap each event row (e.g. with <Link>) |
StatusFeed |
renderReportMessage? / renderMaintenanceMessage? |
Forwarded to the timeline blocks above |
StatusFeed |
footer?: ReactNode |
Append content below the feed (e.g. "View events history" link) |
StatusFeed |
emptyAction?: ReactNode |
Append content inside the empty state |
StatusBlankEvents / StatusBlankMonitors |
action?: ReactNode |
CTA inside the empty state, wrapped in <StatusBlankAction> chrome |
Default behavior is preserved when slots are omitted — the registry preview and unconfigured consumers render correctly without any wiring.
Theming & Styling #
CSS Variables #
Components use CSS variables for status colors:
--success: /* Green */
--warning: /* Yellow */
--destructive: /* Red */
--info: /* Blue */
--muted: /* Gray */
Dark Mode #
All components support dark mode via CSS variables. Dark mode colors are automatically applied based on the dark class on a parent element.
Customization #
Override component styles using className:
<StatusComponent variant="success" className="custom-styles">
{/* Component content */}
</StatusComponent>
Accessibility #
Keyboard Navigation #
- StatusBar: Full arrow key navigation (←/→ between bars, ↑/↓ between monitors)
- StatusComponentGroup: Space/Enter to toggle, standard collapsible keyboard support
- StatusBannerTabs: Tab key navigation, arrow keys to switch tabs
ARIA Support #
role="toolbar"on StatusBar for keyboard navigation contextrole="feed"on StatusEventGroup for event feed semanticsaria-label,aria-pressed,aria-expandedwhere appropriate- Tooltip triggers have
aria-labelfor screen readers
Screen Readers #
- StatusTimestamp includes timezone information in accessible format
- StatusIcon variations are distinguished by accessible labels
- Empty states include descriptive text for context
API Reference #
For detailed API documentation, see the JSDoc comments in each component file:
- status-layout.tsx - Page layout components
- status-component.tsx - Monitor display components
- status-banner.tsx - Status banner components
- status-bar.tsx - Uptime timeline components
- status-events.tsx - Event and incident components
- status-feed.tsx - Unified feed component
- status-timestamp.tsx - Timestamp component
- status-blank.tsx - Empty state components
- status-component-group.tsx - Grouping component
- status-icon.tsx - Icon component
- status-i18n.tsx - i18n provider, hook, and
StatusBlocksLabelstype - status.utils.ts - Utility functions and
defaultStatusBlocksLabels - status-page-shell.tsx - Outer page-shell wrappers (
StatusPageShell,StatusPageMain) - status-page-header.tsx - Header chrome (brand, nav, actions slots)
- status-page-footer.tsx - Footer chrome (powered-by, action cluster)
- status-page-get-in-touch.tsx - Contact-link button variants (icon + text)
- status-updates.tsx - Subscription channel primitives (RSS / Atom / JSON / Slack / SSH)
- status-theme-switcher.tsx - Agnostic light/dark/system switcher
- status-locale-switcher.tsx - Agnostic locale picker
Note: These components follow the headless UI pattern where applicable, separating state management from presentation. This allows for maximum flexibility while maintaining type safety and accessibility.