Data Display
Stat Card
The KPI tile every dashboard opens with: a label, one number, and how that number moved.
import { StatCard } from 'omaris' Examples
Basic
A label, one number and how it moved. delta is a fraction — 0.124 is +12.4% — and its sign picks the arrow and the colour.
<script lang="ts"> import { StatCard } from 'omaris';</script><StatCard class="w-72 max-w-full" label="Revenue" value={48210} format={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }} delta={0.124} deltaLabel="vs last week"/> Kpi row
The row a dashboard opens with: one column on a phone, two on a tablet, four on a desktop. A Sparkline from omaris/chart goes in the chart snippet, and invert is for a metric where down is the good news.
<script lang="ts"> import { StatCard } from 'omaris'; import { Sparkline } from 'omaris/chart'; type Kpi = { label: string; value: number | string; format?: Intl.NumberFormatOptions; delta: number; invert?: boolean; trend: number[]; color: string; }; const KPIS: Kpi[] = [ { label: 'Revenue', value: 48210, format: { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }, delta: 0.124, trend: [32, 35, 31, 38, 42, 40, 45, 44, 49, 52], color: 'var(--chart-1)' }, { label: 'Orders', value: 1284, delta: 0.061, trend: [90, 96, 94, 101, 99, 108, 112, 110, 118, 121], color: 'var(--chart-2)' }, { label: 'Refunds', value: 23, delta: -0.18, invert: true, trend: [34, 31, 33, 29, 30, 27, 26, 28, 24, 23], color: 'var(--chart-3)' }, { label: 'Avg. delivery', value: '42 min', delta: 0.05, invert: true, trend: [38, 39, 37, 40, 41, 39, 42, 43, 41, 42], color: 'var(--chart-4)' } ];</script><div class="grid w-full grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4"> {#each KPIS as kpi (kpi.label)} <StatCard variant="elevated" label={kpi.label} value={kpi.value} format={kpi.format} delta={kpi.delta} invert={kpi.invert} deltaLabel="vs last week" > {#snippet chart()}<Sparkline data={kpi.trend} height={36} color={kpi.color} />{/snippet} </StatCard> {/each}</div> States
loading keeps the label and swaps the rest for skeletons of the same size, so nothing jumps when the figure lands. animate counts up to it, and does nothing at all under reduced motion.
<script lang="ts"> import { Button, StatCard } from 'omaris'; let loading = $state(true);</script><div class="flex w-full max-w-2xl flex-col gap-4"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2"> <StatCard {loading} animate label="Active users" value={8214} delta={0.041} deltaLabel="today"> {#snippet icon()} ... {/snippet} </StatCard> <StatCard {loading} animate variant="filled" tone="tertiary" label="Conversion" value={0.0342} format={{ style: 'percent', maximumFractionDigits: 2 }} delta={-0.012} deltaLabel="vs yesterday" /> </div> <Button variant="tonal" class="self-start" onclick={() => (loading = !loading)}> {loading ? 'Load figures' : 'Back to loading'} </Button></div> Interactive
Give it href or onclick and the whole tile is one target — state layer, ripple, and a press that gives under the finger.
<script lang="ts"> import { StatCard } from 'omaris';</script><div class="grid w-full max-w-2xl grid-cols-1 gap-4 sm:grid-cols-2"> <StatCard href="#" label="Open tickets" value={37} delta={-0.2} invert deltaLabel="since Monday"> {#snippet footer()}View the queue →{/snippet} </StatCard> <StatCard href="#" variant="elevated" label="Next payout" value={12480} format={{ style: 'currency', currency: 'USD' }} deltaLabel="Friday, 3 April" > {#snippet footer()}Payout schedule →{/snippet} </StatCard></div> Overridden
class on the tile wins over its own radius, classes reaches every part, and the caller's role, aria-label and dir stay. locale="ar-IQ" formats the Arabic way with Western digits, as every number here does.
<script lang="ts"> import { StatCard } from 'omaris';</script><StatCard class="w-72 max-w-full rounded-shape-2xl" classes={{ value: 'text-primary', delta: 'rounded-shape-xs', icon: 'rounded-full' }} role="group" aria-label="الإيرادات هذا الشهر" dir="rtl" label="الإيرادات" value={1250000} format={{ style: 'currency', currency: 'IQD', maximumFractionDigits: 0 }} locale="ar-IQ" delta={0.2} deltaLabel="مقارنة بالشهر الماضي"> {#snippet icon()} ... {/snippet}</StatCard> When to use it
Use it for
- The KPI row at the top of a dashboard — revenue, orders, active users — in a grid that goes one, two, then four columns wide.
- One number and how it moved:
deltaas a fraction (0.124is +12.4%) with adeltaLabelthat says against what — "vs last week". - A metric where down is the good news — churn, refunds, response time — with
invert, so a fall is green and a rise is red. - A trend under the figure: a
Sparklinefromomaris/chartin thechartsnippet. - A tile that opens the report behind it:
hreforonclickmakes the whole tile one target.
Not for
Do
- Pass numbers, not strings, and describe them with
format— currency,notation: 'compact', fraction digits. Digits stay Western in every locale. - Keep the label to a couple of words; it truncates rather than wrapping.
- Use
loadingwhile the figure is on its way. The label stays and the tile keeps its height, so the row does not jump when the numbers land. - Pick one
variantfor the whole row —elevatedon a plain page,filledinside another surface.
Don't
- Pass a delta already multiplied by 100 —
12.4reads as +1,240%. Use0.124, ordeltaFormat={{ style: 'decimal' }}for a delta that is a count. - Put a button or a link inside a tile that has
hreforonclick; the tile is already the target. - Turn on
animatefor a whole grid of tiles that refresh every few seconds; count up the one figure that matters. - Colour a tile's value by hand to say good or bad — that is what the delta chip and
invertare for.
Quick reference
tone primary(default)secondarytertiarydestructivesuccesswarninginfo
API
StatCard
The KPI tile every dashboard opens with: a label, one number, and how that number moved.
It is a Card underneath — the same three surfaces, the same sizes, and the same rule for becoming pressable: give it href or onclick and the whole tile is one target, with the state layer, the ripple and a press that gives a little under the finger.
delta is a fraction, the way a ratio comes out of arithmetic: 0.124 is +12.4%, -0.03 is −3%. The arrow and the colour come from its sign, and invert is for the metrics where down is the good news — churn, refunds, response time.
Charts are not imported here, so the main entry stays light. Put a Sparkline from omaris/chart in the chart snippet and it spans the bottom of the tile.
import { StatCard } from 'omaris' <StatCard label="Revenue" value={48210} format={{ style: 'currency', currency: 'USD' }} delta={0.124} deltaLabel="vs last week" /> 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- Label and value on the start side, the icon on the end.
body- No description in the source yet.
label- No description in the source yet.
icon- The icon's tonal container.
value- No description in the source yet.
trend- Delta chip and its label.
delta- The arrow and the percentage, on a tinted pill.
deltaLabel- No description in the source yet.
chart- Wraps the
chartsnippet; bleeds a little so a sparkline reaches the edges. footer- No description in the source yet.
Props
label string What is being measured — "Revenue", "Active users".
value string | number The figure. A number is formatted with format and locale, in Western digits whatever the locale; a string is shown as given, for a value you have already formatted ("4m 12s", "—").
format Intl.NumberFormatOptions Intl.NumberFormat options for a numeric value — currency, notation: 'compact', fraction digits. The numbering system is pinned to Western digits on top of whatever you pass.
locale string BCP-47 tag for the value and the delta. Defaults to the runtime's.
delta number The change, as a fraction: 0.124 is +12.4%, -0.03 is −3%. The arrow and the colour come from its sign; the chip shows its size.
deltaFormat Intl.NumberFormatOptions How delta is written. Defaults to a percentage with at most one fraction digit. Pass { style: 'decimal' } for a delta that is a count rather than a ratio ("+3 orders").
deltaLabel string What the delta is measured against — "vs last week".
trend StatCardTrend Which way it moved. Derived from the sign of delta; set it for a trend you know without a delta to show.
invert Defaults to false
boolean Down is the good direction — churn, refunds, latency. Swaps which trend is coloured as success and which as destructive; the arrow still points the way the number went.
variant Defaults to 'outlined'
CardVariant Card surface, as on Card.
size Defaults to 'md'
CardSize Padding, gap and radius, as on Card; the value's type role steps with it.
tone Defaults to 'primary'
Tone The icon container's colour role.
primarysecondarytertiarydestructivesuccesswarninginfo
loading Defaults to false
boolean Swap the value, the delta and the chart for skeletons. The label stays, since you know it already, and the tile keeps its height.
animate Defaults to false
boolean Count up to a numeric value on mount, and tween between values as it changes. Off by default, and skipped entirely under reduced motion.
ripple Defaults to true
boolean Ripple on press. Pressable tiles only.
class string classes StatCardClasses Per-part Tailwind overrides. class still covers the root.
icon Snippet An icon, drawn in a tonal square at the top end of the tile.
chart Snippet A chart along the bottom — a Sparkline from omaris/chart.
footer Snippet A last line — a link to the report, when the figure was updated.
shape ButtonShape Pill (round) or MD3 rounded rectangle (square).
confetti boolean | ConfettiOptions Confetti on press. true for the default burst, or any ConfettiOptions — confetti={{ preset: 'fireworks' }}.
toggle boolean Turns the button into a two-state toggle driven by pressed. Ignored when href is set — a link has no pressed state.
pressed bindableboolean Selected state of a toggle button. Bindable.
children Snippet