crucıble

Semaphore

The uptime bar, alive — labeled status-tracker rows that stack into an ops wall with a shared time axis. Boots with a sub-second left-to-right ignition cascade on first view, pulses the newest bucket, slides new buckets in on a live interval (suspended offscreen), and raises a hover chip with bucket detail. Statuses are shape-and-brightness coded, not just hue: dim operational, notched degraded, saturated glowing incident. Pure CSS animation, zero dependencies, visually-hidden textual summary per row, static wall under reduced motion.

cssfree

Installation

CLI

npx shadcn@latest add @crucible/semaphore

Props

PropTypeDefaultDescription
segmentsSemaphoreSegment[]Single-row tracker: time buckets, oldest first. Ignored when rows is set. Omit both for a seeded sample row.
rowsSemaphoreRow[]Wall composition: multiple labeled tracker rows sharing one time axis. Takes precedence over segments.
colorsRecord<string, string | SemaphoreStatusStyle>Status → style map, merged over the built-in operational / degraded / incident / empty styles. A plain color string keeps that status's default shape/brightness coding.
liveIntervalnumber0Live mode: advance every N milliseconds — the oldest bucket drops, a new one slides in from the right. 0 = static (no timers, zero JS work). Suspends offscreen and on hidden tabs.
nextSegment(rowIndex: number, tick: number) => SemaphoreSegmenta deterministic mostly-operational feedProduces the incoming bucket in live mode.
cascadebooleantrueBoot cascade on first scroll into view (once): segments ignite left-to-right like a self-test.
cascadeSpeednumber1Cascade speed multiplier — 2 boots twice as fast.
pulsebooleantrueBreathing pulse on each row's newest segment.
segmentWidthnumber6Segment width in px.
segmentHeightnumber26Segment height in px.
gapnumber3Gap between segments in px.
radiusnumber2Segment corner radius in px.
axisreadonly [string, string]Shared time-axis labels rendered under the wall: [oldest, newest].
showChipbooleantrueShow the hover chip with bucket detail.
renderChip( segment: SemaphoreSegment, context: { rowIndex: number; segmentIndex: number; rowLabel?: string } ) => React.ReactNodeCustom hover-chip contents; the default chip shows status, label, and detail.
pausedbooleanfalseFreeze everything at the fully-populated wall (also stops live mode).
Also accepts all props of Omit<React.ComponentPropsWithoutRef<"div">, "children">they pass through to the underlying element.

Honors prefers-reduced-motion with a designed static fallback, and passes className through.