Search docs

Jump to any table example

Discord
ShadTable

A collection of composable table components built on shadcn/ui and TanStack Table.

Live Status Indicators

A service-health table where status and latency update on their own on a timer — a pulsing dot for actively-monitored states, a still one once a service goes down. Toggle Live/Paused to see the updates stop.

npx shadcn add https://shad-table.dev/r/live-status-table.json
ServiceStatusLatencyUptime (30d)
api-gateway
us-east-1
Operational42ms99.98%
auth-service
us-east-1
Operational61ms99.95%
payments-service
eu-west-1
Degraded180ms99.72%
search-index
us-west-2
Operational35ms99.99%
notifications-worker
ap-southeast-1
Operational88ms99.90%
billing-cron
eu-west-1
Down98.41%

How it works

1.One cell mutates per tick, not the whole table

The interval advances a ref-backed cursor and only maps over the row at that index, leaving every other row's object reference untouched. That means TanStack Table only re-renders the one row that actually changed instead of the whole body repainting every 1.8s.

src/components/live-status/data-table.tsx
const index = cursor.current % prev.length
cursor.current += 1
return prev.map((service, i) => {
if (i !== index) return service
const status = nextStatus(service.status)
return { ...service, status, latencyMs: jitterLatency(service.latencyMs, status) }
})

2.Status transitions are weighted, not random

nextStatus() picks from a small array of possible next states per current status, where the array itself encodes the odds — operational lists itself three times and degraded once, so a healthy service mostly stays healthy and rarely dips. A uniform 3-way random pick would make every service flicker between states constantly, which reads as broken rather than live.

src/components/live-status/data-table.tsx
const TRANSITIONS: Record<ServiceStatus, ServiceStatus[]> = {
operational: ['operational', 'operational', 'operational', 'degraded'],
degraded: ['operational', 'operational', 'degraded', 'down'],
down: ['degraded', 'down', 'down'],
}

3.The pulse ring is absent for "down", not just recolored

StatusIndicator only renders the animate-ping ring when config.pulse is true, which is false for "down". A dead service shouldn't pull the eye the way a live, changing one should — the animation itself is the signal that something is actively being monitored, so it has to stop when there's nothing left to watch.

src/components/live-status/status-indicator.tsx
const STATUS_CONFIG: Record<ServiceStatus, { label: string; dot: string; pulse: boolean }> = {
operational: { label: 'Operational', dot: 'bg-emerald-500', pulse: true },
degraded: { label: 'Degraded', dot: 'bg-amber-500', pulse: true },
down: { label: 'Down', dot: 'bg-red-500', pulse: false },
}

4.The Live/Paused toggle just flips the effect's dependency

isLive is a plain useState read by the interval effect's dependency array. Toggling it to false lets the effect's cleanup clear the interval on the next render, and toggling back true re-arms it — no separate start/stop functions or manual clearInterval bookkeeping outside the effect.

src/components/live-status/data-table.tsx
useEffect(() => {
if (!isLive) return
const interval = window.setInterval(() => { /* mutate one row */ }, 1800)
return () => window.clearInterval(interval)
}, [isLive, onDataChange])