Skip to content

Stats and dashboard widgets ​

The sixteen cards, rows and lists the dashboards are built from.

Open in demo
Open in demo

The accent ​

Almost every widget here takes an accent, and the six are the same six everywhere: primary, sky, emerald, amber, violet, rose. It tints the icon tile, not the text.

In light mode the tiles use solid -50 tints, which stay true on a surface that is not white, where an alpha wash shifts with whatever is behind it. Dark mode keeps an alpha tint, because a literal -50 swatch glares on the card. primary stays on the theme token, so it follows a re-theme instead of drifting away from it.

Charts cannot use a Tailwind class, because a canvas resolves neither a class nor a custom property. accentHex(accent, themePrimary) hands back the drawable color, with primary resolved from the live theme.

Formatting is yours ​

Every figure these widgets take is a string you have already formatted, except where the type says a number. That is deliberate: the widget does not know whether 1200 is money, a count or a duration, and guessing produces $1,200 where you wanted 1,200 units.

The exceptions are StatsComparison, StatsProgress, FunnelBars and UpcomingList, which take numbers or dates because they do arithmetic or relative formatting with them.


Stat cards ​

Four card shapes for a KPI row. All four take loading, and all four take the accent.

StatsSimple ​

A label, a figure and an optional trend.

PropTypeDescription
titlestringThe label.
valuestring | numberThe figure.
iconLucideIconDrawn in the accent tile.
trend{ value: number, direction: 'up' | 'down' | 'neutral' }The percentage is sign-independent; direction carries the sign.
accentStatAccentDefaults to primary.
loadingbooleanSkeleton instead of the card.

StatsComparison ​

This period against the last. It works out the change itself, which is why value and previous are numbers.

PropTypeDescription
titlestringThe label.
valuenumberThis period.
previousnumberThe one before.
iconLucideIcon
accentStatAccent
loadingboolean

StatsProgress ​

A figure against a target, with a bar.

PropTypeDescription
titlestringThe label.
valuenumberWhere you are.
targetnumberWhere you are going.
iconLucideIcon
accentStatAccent
loadingboolean

StatsChart ​

The same card with a sparkline under it.

PropTypeDescription
titlestringThe label.
valuestring | numberThe figure.
seriesnumber[]The sparkline's points.
iconLucideIcon
trend{ value, direction }
accentStatAccent
loadingboolean

Loading ​


Sparkline ​

A bare trace, for putting inside something else. It has no chart library behind it.

PropTypeDefaultDescription
seriesnumber[]requiredThe points.
colorstringrequiredA concrete color. Use accentHex() to resolve an accent.
areabooleanfalseA soft fill under the line, and a y axis clamped to the data's own range.

area is opt-in for both effects together. Eight pages outside the dashboards render this, and turning either on globally would restyle them. StatCard opts in, because at 76 by 34 pixels a bare hairline on a near-zero axis reads as a flat dash.

Give it a size on the element: it fills its box.


Dashboard widgets ​

StatCard ​

The dashboards' own KPI card: the accent tile, a trend row with a caption, and an optional sparkline.

The example at the top of this page is four of them.

PropTypeDefaultDescription
labelstringrequired
valuestring | numberrequired
iconLucideIcon—
accentStatAccent'primary'
trend{ value, direction }—
seriesnumber[]—Omit for a card with no chart.
captionstring—Trailing text in the trend row, such as "vs last month".

HeroCard ​

The one card on a dashboard that is a filled block rather than a white one. An eyebrow, a headline figure, supporting copy, and either a progress ring or up to three supporting figures.

PropTypeDefaultDescription
eyebrowstring—Small label above the headline.
valuestring | numberrequiredThe headline.
descriptionstring—One or two lines.
accentStatAccent'primary'A solid fill, one stop back in dark mode.
percentnumber—0 to 100. The value renders inside a progress ring.
metrics{ label, value }[]—Up to three figures along the bottom.

SectionCard ​

The card every dashboard block sits in: a title, an optional subtitle, an optional action, and your content.

PropTypeDefaultDescription
titlestringrequired
subtitlestring—
flushbooleanfalseRemoves the body padding, for tables and edge-to-edge charts.

Slots: default for the body, action for a control in the header.

It can shrink below its content's minimum width, so a card holding a wide table lets the table scroll rather than taking its own header off the screen. It also fills its grid cell, so a short card in a stretched row does not leave a band of dead space under it.

DashboardGreeting ​

The header the dashboards open with instead of a page title. It greets by time of day and by the signed-in user's first name, and it carries the same pin as the page header, because it replaces it.

PropTypeDescription
subtitlestringOne line under the greeting. Say what this dashboard is for.

Slots: actions.

The greeting is one translation key per time band with the name as an argument, greeting.morningNamed and the rest, so each language writes the whole sentence its own way rather than gluing a name onto a fragment. With no signed-in user it falls back to the unnamed form, which is what the preview above shows.

The pin stores the route's title, not the greeting: "Good morning, Jane" would be a strange thing to find in a sidebar.

ActivityTimeline ​

A feed with a rail down the side.

entries is TimelineEntry[]: { id, title, description?, time?, icon?, accent? }. time is pre-formatted.

AssetRows ​

A holdings list: a ticker tile, a name, a price, a 24-hour move and an optional trace.

rows is AssetRow[]: { id, symbol, name, price, change, holding?, series? }. price is pre-formatted; change is a number whose sign carries the direction.

FunnelBars ​

Stages that narrow, with the drop between them.

PropTypeDefaultDescription
stagesFunnelStage[]required{ label, value, color? }. Without a color the bars walk a preset ramp.
formatter(value: number) => stringformatNumberHow each stage's value is printed.

Leaderboard ​

A ranked list of people, with a bar and an avatar.

entries is LeaderboardEntry[]: { id, name, meta?, value, percent, avatar? }. With no avatar the tile shows the name's initials.

ProjectRows ​

Projects with a status chip, a progress bar, a due date and a stack of members.

PropTypeDefaultDescription
rowsProjectRow[]required{ id, name, meta?, status, progress, members, due? }.
maxAvatarsnumber3Members shown before the rest collapse into a "+N" chip.

status is on-track, at-risk, delayed or done, and it colors both the chip and the bar.

RecordRows ​

The general-purpose row list: invoices, transactions, tickets, shipments.

PropTypeDefaultDescription
rowsRecordRow[]requiredSee below.
dividedbooleantrueSeparators. Off for a short list inside a small card.

RecordRow is { id, title, meta?, value?, valueMeta?, status?, icon?, avatar?, accent?, valueTone? }. The leading tile prefers an icon, then an avatar, then the title's initials. status.tone and valueTone are neutral, info, success, warning or danger, and valueTone is what tints a credit green and a debit red.

UpcomingList ​

What is coming, with the date rendered in the app's language.

items is UpcomingItem[]: { id, title, meta?, date, accent? }. date is a real Date, because this is the one widget that formats it for you, in the active language.

Accessibility ​

  • Every icon tile is decorative; the label beside it carries the meaning.
  • A trend badge carries an arrow as well as its color, so up, down and flat are distinguishable without color. The arrow is decorative, so a screen reader reads the percentage on its own; put the direction in the caption where that matters.
  • Progress bars and funnel bars are presentational: the figure they stand for is printed beside them, so the bar adds nothing a screen reader needs.
  • Figures use tabular numerals, so a value that updates does not make the row jitter, and a percentage carries dir="ltr", because its leading digit is bidi-neutral and would otherwise move in a right-to-left layout.
  • Titles and subtitles carry dir="auto", so a record's own name resolves its direction from its own first strong character.