Stat Card
A card that displays a single value with optional label, timestamp, and zone-aware coloring. Scales responsively with its container.
Playground
info
onZoneChange callbacks work in your local project but not in this interactive playground due to how the editor re-renders components.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | RelayDataPoint | — | Required. { value, timestamp, isLoading?, error? } |
numericValue | number | — | Numeric value for zone evaluation. Use when data.value is a formatted string |
label | string | — | Label text below the value |
formatValue | function | — | Custom display formatter: (value) => string. See formatValue |
alertZones | AlertZone[] | [] | Color zones — zone color applies to the value text |
onZoneChange | function | — | Called when value enters a new zone — see onZoneChange |
borderRadius | number | string | — | number (px), "rounded", or "sharp" |
borderColor | string | "#e0e0e0" | Card border color |
borderThickness | number | — | Card border width in px |
styles | object | — | Custom styling — see Styles |
showLastUpdated | boolean | false | Show the timestamp from data.timestamp |
formatTimestamp | function | — | Custom timestamp formatter: (ts) => string |
lastUpdatedMargin | number | 8 | Margin above the timestamp in px |
showLoading | boolean | true | Show skeleton shimmer when data.isLoading is true |
onError | function | — | Called on invalid data — see onError |
formatValue
Controls how data.value is displayed. Without it, the raw value is shown.
// append unit
formatValue={(v) => `${v}°C`}
// round
formatValue={(v) => Math.round(v).toLocaleString()}
// status text
formatValue={(v) => v > 80 ? "HOT" : "OK"}
Zone Color Behavior
When alert zones are set, the value text color changes to match the active zone. The label and timestamp colors are not affected unless overridden via styles.
Styles
styles={{
value: { fontSize: 32, fontWeight: 700, color: "#1e293b", fontFile: "/fonts/Custom.woff2" },
label: { fontSize: 13, fontWeight: 400, color: "#6b7280" },
lastUpdated: { fontSize: 11, fontWeight: 400, color: "#9ca3af" },
background: { color: "#ffffff" },
width: 200,
height: 120,
}}
| Field | Applies to | Properties |
|---|---|---|
value | The large value display | fontFamily, fontSize, fontWeight, color, fontFile |
label | Label text below value | fontFamily, fontSize, fontWeight, color, fontFile |
lastUpdated | Timestamp text | fontFamily, fontSize, fontWeight, color, fontFile |
background | Card background | color |
| Field | Type | Default | Description |
|---|---|---|---|
width | string | number | — | Fixed width override |
height | string | number | — | Fixed height override |
info
Explicit styles.value.color overrides zone coloring. If you want zone-aware colors, don't set styles.value.color.
With Hooks
import { useRelayLatest, StatCard } from "@relay-x/ui";
function TemperatureCard() {
const latest = useRelayLatest({
deviceIdent: "sensor_01",
metric: "temperature",
timeRange: { start: Date.now() - 86400000, end: Date.now() },
});
return (
<StatCard
data={latest}
label="Temperature"
formatValue={(v) => `${v}°C`}
showLastUpdated
/>
);
}