Search docs

Jump to any table example

Discord
ShadTable

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

Production Dashboard Table

A production-line status table combining status badges, output/utilization progress bars, and 7-day trend sparklines — a composition of the Conditional Formatting and Summary/KPI examples on one realistic dataset, not a new feature on its own.

npx shadcn add https://shad-table.dev/r/production-dashboard-table.json
Line
Status
Output vs. Target
Utilization
7-Day Trend

Line A — Assembly

Day shift

Running
1,420 / 1,200118%

94%

Line B — Packaging

Day shift

Running
980 / 1,10089%

78%

Line C — Welding

Night shift

Down
210 / 95022%

22%

Line D — Paint Booth

Day shift

Maintenance
340 / 80043%

41%

Line E — Final QC

Day shift

Running
610 / 600102%

88%

Line F — CNC Mill

Night shift

Idle
0 / 5000%

0%

Line G — Injection Mold

Day shift

Running
2,150 / 1,800119%

97%

Line H — Labeling

Night shift

Running
730 / 90081%

65%

How it works

1.Three primitives, not one new idea

This example is deliberately a composition, not a new feature: the same matchRule engine from the Conditional Formatting Table, the same hand-rolled Sparkline from the Summary/KPI Table, and a small new ProgressBar are combined onto one realistic dataset. Nothing here needs to be understood in isolation if you've already seen those two examples.

src/components/production-dashboard/columns.tsx
import { ProgressBar } from './progress-bar'
import { Sparkline } from './sparkline'
import { matchRule } from './rules'

2.Progress bars need a default color; text formatting doesn't

The Conditional Formatting Table lets rule?.className be undefined for a healthy row — cn() just drops it and the cell renders unstyled. A progress bar can't do that; an unstyled bar still has to be some color. So every rule list here ends with a catch-all test: () => true, guaranteeing matchRule always returns something to color the bar with.

src/components/production-dashboard/columns.tsx
const outputRules: FormatRule<ProductionLine>[] = [
{ test: (row) => row.outputToday / row.targetOutput < 0.8, className: 'bg-rose-500' },
{ test: (row) => row.outputToday / row.targetOutput < 1, className: 'bg-amber-500' },
{ test: () => true, className: 'bg-emerald-500' }, // catch-all — always matches
]

3.The bar renders a percentage; the label renders the real numbers

ProgressBar only ever sees a 0–100 value and clamps it — it has no idea what a 'line' or a 'target' is. The Output column computes that percentage itself and separately renders the actual output/target integers next to the bar, since over-target output (>100%) still needs to clamp visually but shouldn't lie about the real number.

src/components/production-dashboard/columns.tsx
const pct = (row.original.outputToday / row.original.targetOutput) * 100
<span>{row.original.outputToday} / {row.original.targetOutput}</span>
<ProgressBar value={pct} barClassName={rule?.className} />

4.A sparkline can be colored the same way a bar can

Sparkline renders its polyline with stroke="currentColor", so wrapping it in a <span className={rule?.className}> recolors the whole trend line through CSS inheritance — no prop threading into the SVG itself. The rule here compares the trend's last value to its first, so a declining line renders in the same rose-500 used for a low output bar.

src/components/production-dashboard/columns.tsx
const trendRules: FormatRule<ProductionLine>[] = [
{ test: (row) => row.trend[row.trend.length - 1] < row.trend[0], className: 'text-rose-500' },
{ test: () => true, className: 'text-emerald-500' },
]
<span className={rule?.className}>
<Sparkline data={row.original.trend} />
</span>