[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. 🫖 Status page with uptime monitoring & API monitoring as code 🫖 openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor

README.md

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 #

  1. Separation of Concerns: Each component has a single, clear purpose
  2. Data Attributes: Components use data-* attributes for status-based styling
  3. CSS Group Patterns: Parent components establish context via group classes
  4. Headless Hooks: Complex interactions separated from presentation (e.g., useStatusBar)
  5. 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 defaultStatusBlocksLabels when no provider is mounted
  • defaultStatusBlocksLabels: English (en-US) defaults exported from status.utils.ts

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 sets data-embed=true on a group/embed element
  • 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 asChild for 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 (asChild for the link)
  • StatusPageHeaderActions: Right-side fixed-width cluster (subscribe, contact, mobile-menu trigger)
  • 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 asChild for the link)
  • StatusPageGetInTouchButton: Outlined text-label button (uses asChild for 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 subscribe snippet
  • 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 + locales list
  • 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 context
  • role="feed" on StatusEventGroup for event feed semantics
  • aria-label, aria-pressed, aria-expanded where appropriate
  • Tooltip triggers have aria-label for 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:


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.