Stat / metric card
Anatomy: number + label, a trend delta, a sparkline, a leading icon
Trend semantics: up isn’t always good (cost / latency flip the color, not the glyph)
Icon-plate tint (task 2740, opt-in): default stays the neutral D6 plate; tint colors the glyph + plate
Experimental (task 2763): subtle theme-following surface gradient; opt-in, default stays the flat surface
Featured · compact · loading · empty
Dashboard cluster: equal-height tiles; the first is clickable (tab / click it)
Number is a mono figure, label muted, delta is warm status + a glyph (never color alone), the featured fill uses on-accent, sparkline is brand cyan, all var(--token). Tiles opened 0 · count-up honors reduced-motion. The island is live.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,// not yet exported from @dizyx/nockerl-react; promotion is tracked.)// The number is a mono figure; the label is muted; a delta is a WARM status color// + a direction glyph (never color alone). Featured fills with cyan. All tokens.
export function DashboardCluster() { return ( <div className="stat-grid"> <StatCard label="Tokens" value={formatCount(1_240_000)} icon={<TokenIcon />} delta={{ value: '12%', trend: 'up' }} spark={tokenTrend} onClick={openTokens} /> <StatCard label="Sessions" value="42" icon={<SessionIcon />} delta={{ value: '6', trend: 'up' }} /> {/* up isn't always good, so cost/latency flip the color, not the glyph */} <StatCard label="Cost · 7d" value="$184" icon={<CostIcon />} delta={{ value: '14%', trend: 'up', goodWhenDown: true }} /> <StatCard label="p95 latency" value="640ms" icon={<LatencyIcon />} delta={{ value: '8%', trend: 'down', goodWhenDown: true }} /> <StatCard label="Tokens · all time" value="1.2M" tone="featured" /> <StatCard label="Errors · 24h" value="-" /> {/* empty, not a zero */} </div> );}// Android: Jetpack Compose (canonical). The shipped tile is `StatTile`// (chat/ui/ClusterSheet.kt): a compact surface with a small leading icon + muted// label, then a MONOSPACE value and an optional trailing Sparkline. Counts use// `formatTokenCount` (1M / 42K). Delta/trend is not yet on the native tile.import com.nockerl.app.chat.ui.formatTokenCount
@Composableprivate fun StatTile( icon: ImageVector, label: String, value: String, modifier: Modifier = Modifier, iconTint: Color? = null, trailing: (@Composable -> Unit)? = null,) { Column( modifier = modifier .background(MaterialTheme.colorScheme.surfaceContainer, RoundedCornerShape(6.dp)) .padding(horizontal = 8.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(2.dp), ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon(icon, null, tint = iconTint ?: MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(11.dp)) Spacer(Modifier.width(4.dp)) Text(label, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) } Row(verticalAlignment = Alignment.CenterVertically) { Text(value, style = MaterialTheme.typography.labelSmall, fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f)) if (trailing != null) trailing() // e.g. a Sparkline(...) } }}
// Usage, the GPU/board row from the cluster sheet:StatTile(icon = Icons.Outlined.Bolt, iconTint = colors.modelType.gpu, label = "Tokens", value = formatTokenCount(1_240_000), modifier = Modifier.weight(1f))// macOS / iOS: SwiftUI, from the NockerlDesign package (the package// canon, to the D6 ruling 70a5f6a): the leading glyph sits MUTED in a NEUTRAL// HAIRLINE PLATE (never an accent fill: "we rarely fill", "cyan is earned"),// the label is the uppercase muted line, the value is the big mono figure, and// the delta is a WARM status color + a direction glyph (never color alone).// Voice's hand-rolled HomeSection StatCard (accent chip + gradient card)// normalizes to this on adoption. Sparkline is not yet on the native tile.import NockerlDesign
// The Home overview grid:NockerlStatCard(label: "Transcriptions", value: "\(records.count)") { Image(systemName: "waveform")}NockerlStatCard( label: "Tokens", value: formatTokenCount(tokens), delta: NockerlStatDelta(value: "12%", trend: .up)) { Image(systemName: "bolt")}// up isn't always good, so cost/latency flip the COLOR, not the glyph:NockerlStatCard( label: "Cost · 7d", value: "$184", delta: NockerlStatDelta(value: "14%", trend: .up, goodWhenDown: true))// Empty state: a clearly-seen dash (it mutes), never a misleading 0.NockerlStatCard(label: "Latency", value: "-")Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
label * | string | Short metric name. Bound to the muted label.small role. | |
value * | string | Headline figure, pre-formatted (e.g. 1.2M, $184). Pass a dash for the empty state, never a misleading 0. | |
delta | { value: string; trend: "up" | "down"; goodWhenDown?: boolean } | Trend chip: a warm status color + a direction glyph (▲/▼) + a sign. Never color alone. goodWhenDown flips the color for cost/latency (the glyph stays). | |
icon | ReactNode | Optional leading glyph, rendered on the ratified D6 neutral hairline plate (muted glyph, transparent fill) by default. Opt into a hue with tint. | |
spark | number[] | Optional inline sparkline, normalized to its own min/max with a filled "now" dot. Brand cyan (on-accent when featured). | |
tone | 'default' | 'featured' | 'default' | featured fills the tile with the cyan gradient (one per cluster); labels use on-accent. |
tint | 'accent' | 'success' | 'warning' | 'danger' | Opt-in icon-plate tint that colors the glyph + plate a soft hue (soft wash + hue border + hue glyph, the Badge-soft recipe). Omit for the neutral D6 plate (default renders byte-identical). Ignored on featured tiles. | |
gradient | boolean | false | EXPERIMENTAL (task 2763, not ratified canon): a subtle THEME-FOLLOWING surface gradient (a gentle neutral diagonal, not the featured cyan). Default renders the flat surface, byte-identical. Ignored on featured. |
size | 'comfortable' | 'compact' | 'comfortable' | Density. compact is the dense ClusterSheet tile; comfortable the Voice card. |
loading | boolean | false | Skeleton shimmer instead of content; holds the tile height and sets aria-busy. |
onClick | () => void | When set, the whole tile becomes one button (hover lift, focus-visible ring, single accessible name). |
| Parameter | Type | Default | Description |
|---|---|---|---|
icon * | ImageVector | Leading glyph (11.dp), tinted by iconTint. | |
label * | String | Metric name, rendered in labelSmall on onSurfaceVariant (muted). | |
value * | String | Headline figure in FontFamily.Monospace. Format counts with formatTokenCount (1M / 42K). | |
modifier | Modifier | Modifier | External modifier. Pass Modifier.weight(1f) to tile equal-width across a row. |
iconTint | Color? | null | Icon color; falls back to onSurfaceVariant. The cluster sheet uses model-type tints. |
trailing | (@Composable -> Unit)? | null | Optional trailing slot: the shipped tile drops a Sparkline here. |
NockerlStatCard is the packaged tile, with NockerlCard chrome (surface1 +
catch-light + hairline + the L2 neutral shadow; Voice’s gradient fill retires).
The head band reserves the 40pt plate height even icon-less, so headline numbers
share one offset across a tile row. Not NockerlControlMetrics. That is the
control-layer metric constants (the 44pt touch floor), not a component.
| Parameter | Type | Default | Description |
|---|---|---|---|
label * | String | Metric name: uppercase, 12pt medium, onCardMuted. | |
value * | String | Headline figure, pre-formatted in the MONO family at the display-small size (28pt bold, tabular). Pass a dash for the empty state (it mutes), never a misleading 0. | |
delta | NockerlStatDelta? | nil | (value, trend: .up/.down, goodWhenDown). A warm status color + ▲/▼, never color alone; goodWhenDown flips the color for cost/latency (the glyph stays). |
icon | @ViewBuilder | Leading glyph (SF Symbol Image or any view), muted, inside the ratified D6 neutral hairline plate by default (40pt, radius control, transparent + cardHairline). Opt into a hue with tint. | |
tint | NockerlStatTint? | nil | Opt-in icon-plate tint. .accent / .success / .warning / .danger color the glyph + plate a soft hue (soft wash 14% + hue border 28% + hue glyph, the Badge-soft recipe). Omit for the neutral D6 plate (default renders byte-identical). Warm hues stay status-meaningful; .accent is the earned-cyan case. |
density | NockerlControlDensity | .comfortable | The rhythm tier (the shared NockerlControlDensity, one density story, not a bespoke knob): .comfortable is the unchanged 40pt-plate / 28pt-value tile; .compact mirrors the web compact tier with 32pt plate, space3 padding, space2 gap, a 20pt value (≈88→ a ~26% shorter tile) for dense dashboards and small windows. Legibility + the neutral-plate anatomy hold. |