Skip to content
omaris

Data Display

Status Page

The page itself: one sentence at the top, and everything else under it.

import { StatusPage } from 'omaris'
Learn

Examples

Basic

The banner is the whole design: someone opening a status page has one question and about two seconds of patience for it, so the answer is the largest thing on the screen, in a colour, with an icon that says the same thing for anyone the colour doesn't reach. worstStatus() computes it — the overall state of a system is its worst part, never its average.

All systems operational

Checked Sep 8, 12:00 PM

API

REST and GraphQL endpoints

Operational
90 days ago 99.92% uptime Today
Dashboard

app.example.com

Operational
90 days ago 99.88% uptime Today
Webhooks
Operational
90 days ago 99.92% uptime Today
<script lang="ts">	import { StatusPage, StatusService, worstStatus, type StatusPoint } from 'omaris';​	function history(seed: number, blips: Record<number, StatusPoint['level']> = {}): StatusPoint[] {		const today = new Date('2026-09-08T12:00:00Z');		return Array.from({ length: 90 }, (_, index) => {			const date = new Date(today.getTime() - (89 - index) * 86_400_000);			const level = blips[index] ?? ((index * seed) % 53 === 0 ? 'degraded' : 'operational');			return { date, level, uptime: level === 'operational' ? 100 : 96.4 };		});	}​	const services = [		{ name: 'API', description: 'REST and GraphQL endpoints', history: history(7) },		{ name: 'Dashboard', description: 'app.example.com', history: history(11, { 84: 'degraded' }) },		{ name: 'Webhooks', history: history(13) }	];</script>​<StatusPage	level={worstStatus(services.map((service) => service.history.at(-1)!.level))}	updatedAt={new Date('2026-09-08T12:00:00Z')}	class="w-full">	<div class="flex flex-col rounded-shape-lg border border-border px-4">		{#each services as service (service.name)}			<StatusService {...service} class="border-b border-border last:border-b-0" />		{/each}	</div></StatusPage>

Uptime history

A status page that shows only today answers "is it working now" and nothing else; the bar answers "is it usually working", which is the question anyone reading one actually has. How many days fit is measured rather than guessed — the track draws as many bars as it can at a readable width and drops the oldest — so the same row is ninety days wide and three weeks narrow with no breakpoint anywhere. Hover a bar for the day.

Webhooks

Outbound delivery

Operational
90 days ago 99.06% uptime Today
Documentation
Operational
90 days ago 100.00% uptime Today
<script lang="ts">	import { StatusService, type StatusPoint } from 'omaris';​	const today = new Date('2026-09-08T12:00:00Z');	const bad: Record<number, StatusPoint['level']> = { 61: 'degraded', 86: 'major', 87: 'partial' };​	const history: StatusPoint[] = Array.from({ length: 90 }, (_, index) => {		const date = new Date(today.getTime() - (89 - index) * 86_400_000);		const level = bad[index] ?? 'operational';		return {			date,			level,			uptime: level === 'operational' ? 100 : level === 'major' ? 21.4 : 96.8,			note: level === 'operational' ? undefined : 'Elevated error rates in eu-west'		};	});</script>​<div class="flex w-full flex-col rounded-shape-lg border border-border px-4">	<StatusService		name="Webhooks"		description="Outbound delivery"		{history}		class="border-b border-border"	/>	<StatusService		name="Documentation"		history={history.map((day) => ({ ...day, level: 'operational', uptime: 100 }))}		density="compact"	/></div>

Incidents

Newest first, because the question is always "what is happening now" and the answer should not be at the bottom of a scroll. The older updates stay, in order, because the second question is "how long has this been going on" — and a page that overwrites its own history every twenty minutes cannot answer either one afterwards. The badge follows the most recent update.

Elevated error rates on the API

Identified Sep 8, 11:40 AM
Affected services: APIDashboard
  1. Identified Sep 8, 12:02 PM

    A connection pool exhausted after a bad deploy. Rolling back.

  2. Investigating Sep 8, 11:40 AM

    We are seeing a rise in 5xx responses on the API and are looking into it.

Webhook delivery delayed in eu-west

Resolved Sep 6, 08:12 AM
Affected services: Webhooks
  1. Resolved Sep 6, 10:05 AM

    The queue is empty. Every delayed webhook was sent.

  2. Monitoring Sep 6, 09:20 AM

    The rollback is out and the backlog is draining.

  3. Investigating Sep 6, 08:12 AM

    A backlog is building on the eu-west delivery queue.

<script lang="ts">	import { StatusIncident } from 'omaris';</script>​<div class="flex w-full flex-col gap-8">	<StatusIncident		title="Elevated error rates on the API"		level="major"		startedAt={new Date('2026-09-08T11:40:00Z')}		services={['API', 'Dashboard']}		updates={[			{				at: new Date('2026-09-08T11:40:00Z'),				stage: 'investigating',				body: 'We are seeing a rise in 5xx responses on the API and are looking into it.'			},			{				at: new Date('2026-09-08T12:02:00Z'),				stage: 'identified',				body: 'A connection pool exhausted after a bad deploy. Rolling back.'			}		]}	/>​	<StatusIncident		title="Webhook delivery delayed in eu-west"		level="partial"		startedAt={new Date('2026-09-06T08:12:00Z')}		services={['Webhooks']}		muted		updates={[			{				at: new Date('2026-09-06T08:12:00Z'),				stage: 'investigating',				body: 'A backlog is building on the eu-west delivery queue.'			},			{				at: new Date('2026-09-06T09:20:00Z'),				stage: 'monitoring',				body: 'The rollback is out and the backlog is draining.'			},			{				at: new Date('2026-09-06T10:05:00Z'),				stage: 'resolved',				body: 'The queue is empty. Every delayed webhook was sent.'			}		]}	/></div>

Emphasis

emphasis is how loudly the banner is drawn: tonal for a status page, solid for a wallboard across a room, quiet for a strip inside an app that is not about status. StatusIndicator is the same state at dot size — the label ships by default, because a page whose whole message is a colour has no message for a good share of the people looking at it.

Major system outage

Checked Sep 8, 12:00 PM

Degraded performance

Checked Sep 8, 12:00 PM

All systems operational

Live

Operational Degraded performance us-east-1 Under maintenance
<script lang="ts">	import { StatusIndicator, StatusPage } from 'omaris';</script>​<div class="flex w-full flex-col gap-3">	<StatusPage level="major" emphasis="solid" updatedAt={new Date('2026-09-08T12:00:00Z')} />	<StatusPage level="degraded" emphasis="tonal" updatedAt={new Date('2026-09-08T12:00:00Z')} />	<StatusPage level="operational" emphasis="quiet" updatedLabel="Live" />​	<div class="flex flex-wrap items-center gap-5 pt-1">		<StatusIndicator level="operational" />		<StatusIndicator level="degraded" pulse />		<StatusIndicator level="major" label="us-east-1" pulse />		<StatusIndicator level="maintenance" size="sm" />	</div></div>

Overridden

Every part is reachable through classes: the banner's radius, the icon disc, the height of the bars and their corners. class still covers the root, and title and updatedLabel replace the sentences the level would otherwise write.

Two regions are slow

Updated a moment ago

eu-west
Degraded performance
3 months ago 99.94% uptime Today
<script lang="ts">	import { StatusPage, StatusService, type StatusPoint } from 'omaris';​	const today = new Date('2026-09-08T12:00:00Z');	const history: StatusPoint[] = Array.from({ length: 90 }, (_, index) => {		const date = new Date(today.getTime() - (89 - index) * 86_400_000);		const level: StatusPoint['level'] = index >= 88 ? 'degraded' : 'operational';		return { date, level, uptime: level === 'operational' ? 100 : 97.2 };	});</script>​<StatusPage	level="degraded"	title="Two regions are slow"	updatedLabel="Updated a moment ago"	class="w-full gap-3"	classes={{ banner: 'rounded-shape-sm', mark: 'rounded-shape-sm' }}>	<StatusService		name="eu-west"		level="degraded"		{history}		legend="3 months ago"		classes={{ track: 'h-12', bar: 'rounded-shape-xs' }}	/></StatusPage>

When to use it

Use it for

  • The page people open when something looks broken: one sentence at the top, the services under it, and what happened recently.
  • A per-service uptime history, the ninety-day bar. It answers "is it usually working".
  • An incident and its thread. StatusIncident keeps every update in order and badges the incident with the latest one.
  • A status strip inside an app that is not about status: emphasis="quiet", or StatusIndicator on its own beside a region, a queue or a host.
  • A wallboard: emphasis="solid" is the accent at full strength, readable across a room.

Not for

  • A metric over time, like latency or error rate → Chart. The bar here is categorical: each day is a state, not a number.
  • One thing's state inline in a table or list → a Badge or a Chip. StatusIndicator is for a row where the state is the point.
  • Progress through a task you started → Progress.
  • Telling someone their own action failed → Toast or Alert. This page is about the system.

Do

  • Compute the banner with worstStatus(). The overall state of a system is its worst part.
  • Always pass updatedAt (or updatedLabel). A status page with no timestamp looks broken.
  • Give history oldest first. Days that do not fit are dropped from the front, so the most recent ones stay on screen.
  • Use the levels as meant: degraded is slow, partial is some of it down, major is all of it.
  • Write note on the bad days. It is what the hover readout shows.
  • Keep resolved incidents on the page, muted. The history is the credibility.

Don't

  • Rely on the dot alone. StatusIndicator ships its label because green and amber are the same dot to many readers.
  • Mark planned work as an outage. maintenance is left out of the uptime sum on purpose.
  • Show ninety focusable bars. The track is one role="img" with the row summarised.
  • Round uptime up to 100%. formatUptime refuses to, and so should whatever feeds it.

Quick reference

emphasis StatusPage
  • tonal (default)
  • solid
  • quiet

How loudly the banner is drawn.

size StatusIndicator
  • sm
  • md (default)
  • lg
density StatusService
  • comfortable (default)
  • compact

API

StatusPage

The page itself: one sentence at the top, and everything else under it.

The banner is the whole design. Someone opening a status page has one question and about two seconds of patience for it, so the answer is the largest thing on the screen, in a colour, with an icon that says the same thing for anyone the colour doesn't reach — and worstStatus() computes it, because the overall state of a system is its worst part, never its average.

The time it was last checked sits under that. A status page with no timestamp is a status page nobody can tell from a broken one.

import { StatusPage } from 'omaris'
<StatusPage level={worstStatus(services.map((s) => s.level))} updatedAt={new Date()}>  {#each services as service}    <StatusService {...service} />  {/each}</StatusPage>

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
banner
The headline panel.
mark
The icon disc.
headline
No description in the source yet.
title
No description in the source yet.
updated
"Checked 2 minutes ago".
actions
Buttons on the right of the banner.
section
A section — the services, the incident history.
sectionHeader
No description in the source yet.
sectionTitle
No description in the source yet.
group
The stack of services, with a hairline between rows.
timeline
The stack of incidents.
empty
Shown where a section has nothing in it — the good outcome, usually.

Props

level required
StatusLevel

The one thing the page says. worstStatus() computes it from the parts.

title
string

Replaces the sentence the level would otherwise write.

updatedAt
Date | string

When the numbers were last true.

updatedLabel
string

Replaces "Checked <time>".

emphasis

Defaults to 'tonal'

StatusPageEmphasis
tonal
Tinted panel. The default: readable across a room.
solid
The accent at full strength — for a wallboard.
quiet
A hairline and a dot. For a status strip inside an app.
locale
string
actions
Snippet

Buttons on the right of the banner — subscribe, refresh, history.

banner
Snippet

Replaces the whole banner.

children
Snippet

Everything under it: services, incidents, whatever else.

class
string
classes
StatusPageClasses

Per-part Tailwind overrides. class still covers the root.

StatusIncident

One incident, and the thread of updates under it.

Newest first, because the question is always "what is happening now" and the answer to it should not be at the bottom of a scroll. The older updates stay, in order, because the second question is "how long has this been going on" — and a status page that overwrites its own history every twenty minutes cannot answer either one afterwards.

import { StatusIncident } from 'omaris'

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
header
No description in the source yet.
title
basis so the row actually wraps. flex-1 alone is a basis of zero, so on a phone the badge and the timestamp keep their width and the headline takes what is left — which was 130px, and an incident title read three words down a column. Below 12rem the two of them drop to a line of their own instead.
stage
The badge saying how far along it is.
when
No description in the source yet.
scope
The affected services, listed under the title.
chip
No description in the source yet.
updates
The thread. The rule down the left is drawn by the items.
update
One update. The line and the node are before/after on the item itself, so the thread is one element per update rather than an element per update plus an element per connector.
updateHead
No description in the source yet.
updateStage
No description in the source yet.
updateWhen
No description in the source yet.
body
No description in the source yet.

Props

title required
string

What happened, in a line.

level

Defaults to 'major'

StatusLevel

How bad it was. Colours the title's marker.

startedAt
Date | string

When it started.

services

Defaults to []

string[]

What it affected.

updates

Defaults to []

IncidentUpdate[]

The thread. Given oldest first or newest first; it is sorted.

muted

Defaults to false

boolean

Dim it — for a resolved incident in a history list.

locale
string
children
Snippet

Replaces the update thread.

class
string
classes
StatusIncidentClasses

Per-part Tailwind overrides. class still covers the root.

StatusIndicator

A dot, and what it means.

The dot alone is the thing everybody draws and nobody can read: green and amber are the same dot to a good share of people looking at it, and a page whose entire message is a colour has no message for them. So the label ships by default and label={false} is the deliberate choice, with the state still readable to a screen reader either way.

A live state pulses. Not for decoration — a page left open on a wall is the normal way this is read, and the pulse is what says the page is still watching rather than frozen on an old render.

import { StatusIndicator } from 'omaris'

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
dot
The dot itself, with a halo that beats out when pulse is on.
label
No description in the source yet.

Props

level required
StatusLevel
label
string | false

The words beside the dot. false hides them, a string replaces them.

size

Defaults to 'md'

StatusIndicatorSize
sm
md
lg
pulse

Defaults to false

boolean

Beat the halo — for a page that is watching a live feed.

class
string
classes
StatusIndicatorClasses

StatusService

One service, and the last however many days of it.

The bar is the part that matters. A status page that shows only today answers "is it working now" and nothing else; the history is what answers "is it usually working", which is the question anyone reading a status page actually has.

How many days fit is measured, not guessed: the track counts how many bars it can draw at a readable width and shows that many of the most recent, so the same component is ninety days on a dashboard and three weeks on a phone without a breakpoint anywhere.

import { StatusService } from 'omaris'
<StatusService name="API" level="operational" history={days} />

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
header
Name on the left, current state on the right.
name
No description in the source yet.
description
No description in the source yet.
state
No description in the source yet.
track
The row of bars.
bar
One day.
readout
The hovered day's detail, floating over the track.
legend
"90 days ago — 99.98% uptime — Today".
legendRule
No description in the source yet.
uptime
No description in the source yet.

Props

name required
string

What the service is called.

description
string

A line under it — what it covers, or which region.

level
StatusLevel

How it is right now. Defaults to the most recent point's level.

history

Defaults to []

StatusPoint[]

Oldest first. Anything that does not fit is dropped from the front.

uptime
number

Uptime to print, 0–100. Computed from history when left out — pass it when the real number comes from somewhere with more data than the bars are showing.

legend
string | false

The words under the bar. false drops the whole legend row.

labels
{ state?: string | false; uptime?: string; today?: string }

The words the row writes for itself, for a page that is not in English: the state beside the dot, and the two fixed words in the legend. One prop rather than three, because they are always replaced together — a row that says "جاهزية" and "Today" is worse than either. state: false leaves the dot alone, with the state still read out to a screen reader.

minBarWidth

Defaults to 4

number

How thin the bars may get before older days are dropped, in px.

density

Defaults to 'comfortable'

StatusServiceDensity
comfortable
compact
locale
string

Locale for the dates and the percentage.

children
Snippet

Extra content under the legend — a link to the region's own page.

class
string
classes
StatusServiceClasses

Per-part Tailwind overrides. class still covers the root.