Delta badge
Free · MIT"+12.4%" change badge whose digits roll, arrow rotates to the new direction and background pulses green or red, with an inverted mode for metrics where down is good.
Installation
$ npx shadcn@latest add https://goodui.dev/r/delta-badge.jsonThe file lands in components/goodui/delta-badge.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/delta-badge works too.
Usage
import { DeltaBadge } from "@/components/goodui/delta-badge"
const rows = [
{ label: "Revenue", value: "$48,295", delta: 12.4 },
{ label: "Bookings", value: "1,284", delta: 4.1 },
{ label: "Cancellations", value: "38", delta: -3.2, goodWhen: "down" as const },
]
export function WeekOverWeek() {
return (
<div className="divide-y rounded-xl border bg-card">
{rows.map((r) => (
<div key={r.label} className="flex items-center justify-between px-4 py-3">
<div>
<div className="text-sm font-medium">{r.label}</div>
<div className="text-xs text-muted-foreground tabular-nums">{r.value}</div>
</div>
<DeltaBadge value={r.delta} goodWhen={r.goodWhen} />
</div>
))}
</div>
)
}When to use
- Next to a KPI to show the change from the previous period: "+12.4%" revenue vs last week.
- Metrics where down is good (cancellations, churn, costs) with goodWhen="down", so a drop shows green.
- Comparison tables that refresh, where the change should be noticed when it updates.
- Showing the value itself rather than its change: use number-flow.
- Signalling that a table cell just changed, up or down: use cell-flash.
- Status labels like "Paid" or "Overdue": use badge.
| If you need | Use |
|---|---|
| Inline in a KPI card or table | variant="soft" (default) |
| Strong emphasis on a hero stat | variant="solid" |
| Quiet, on busy backgrounds | variant="outline" |
| Lower is better (churn, no-shows, cost) | goodWhen="down" |
| Absolute change, not percent | suffix="" or suffix=" pts" |
Examples
Small solid and outline badges
<DeltaBadge value={12.4} variant="solid" size="sm" />
<DeltaBadge value={-3.2} goodWhen="down" variant="outline" size="sm" />
<DeltaBadge value={0} size="sm" />Whole-number change without the arrow
<DeltaBadge value={7} decimals={0} suffix=" pts" hideIcon />API reference
| Prop | Type | Default |
|---|---|---|
| value* The change, e.g. 12.4 for +12.4%. | number | — |
| decimals Decimals shown. | number | 1 |
| suffix Text after the number; use "" or " pts" for non-percent changes. | string | "%" |
| goodWhen Which direction is good. Use "down" for churn, no-shows or costs. | "up" | "down" | "up" |
| variant soft: tinted background, solid: filled green or red with white text, outline: colored border and text. | "soft" | "solid" | "outline" | "soft" |
| size sm is 20px tall with 11px text, md is 24px with 12px text. | "sm" | "md" | "md" |
| hideIcon Hide the arrow icon. | boolean | false |
| labels Text for built-in controls and screen readers; pass translations here. | Partial<DeltaBadgeLabels> | — |
Also accepts every <span> attribute.
Accessibility
- An sr-only span reads the change in words, e.g. "Up 12.4%", "Down 3.2%" or "No change, 0.0%"; the arrow, sign and rolling digits are aria-hidden. Translate the words with labels (up, down, flat).
- Green and red are not the only signal: the sign, the arrow direction and the words all say the direction.
- There is no aria-live region; changes are not announced on their own.
Motion
- Digits roll through number-flow. The arrow rotates on a spring (stiffness 260, damping 18): up at 0°, down at 180°, sideways at 90° for no change.
- When the rounded value changes, a tinted ring grows to 1.35x and fades over 0.6 s, and the number scales 1 → 1.12 → 1 in 0.35 s. Nothing pulses on first render.
- Background and text colors cross-fade over 300 ms when the tone flips.
- Reduced motion: no ring and no scale pulse; the arrow and digits snap to the new state.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- value is the change itself in percent units (12.4 means +12.4%), not a ratio (0.124) and not the new total.
- Do not add a + or − yourself; the badge adds the sign and uses the true minus character.
- Color comes from direction and goodWhen; there is no tone prop. Use goodWhen="down" for cancellations, churn, refunds and costs.
- The value is rounded to decimals (default 1) before deciding the direction, so 0.04 shows as flat "0.0%".
- labels={{ up: (a) => `Plus ${a}`, down: (a) => `Minus ${a}`, flat: (a) => `Unverändert, ${a}` }} translates the screen-reader text; a is the formatted amount with suffix.
FAQ
Why is my drop shown in green?
goodWhen="down" is set, so a decrease counts as good. Remove it for metrics where up is good.
Does it work in a table that re-renders often?
Yes. It only pulses when the rounded value actually changes, so re-renders with the same value do nothing.
Related
Digits roll to the new value with locale formatting, prefixes and suffixes.
KPI card with seven variants, tones, shadows and sizes: count-up or rolling NumberFlow values, auto-toned delta badges (invertible for no-show rates), line, area or bar sparklines that draw in, icons, footers and a skeleton state.
Wraps a table value and briefly flashes it green when it rises, red when it falls, with a fading ▲/▼, like a trading terminal.
One badge, every layer: solid, soft, outline, status dot, gradient, glass and neo in six tones, with pulse, shine, bounce-in and count-pop animations, icons, removable chips and clickable filters.