Stat
Free · MITHeadless, accessible, and styled entirely through the attribute surface below — bring your own CSS system, or start from the reference theme.
Install
Without a flag you get no styling at all — plain HTML carrying the attribute surface below. --theme copies the same theme in two dialects;--bridge lumos re-points either one onto Lumos for Astro's tokens (it needs a theme to re-point, and Lumos itself, already in your project).
Unstyled — behavior only, you write the CSS
npx astrocraft-ui add display/statPlain-CSS theme — no build step
npx astrocraft-ui add display/stat --theme cssTailwind theme — needs Tailwind v4
npx astrocraft-ui add display/stat --theme tailwindPlain-CSS theme, re-pointed onto Lumos tokens
npx astrocraft-ui add display/stat --theme css --bridge lumosTailwind theme, re-pointed onto Lumos tokens
npx astrocraft-ui add display/stat --theme tailwind --bridge lumosLive demo
Stat
A KPI row is a description list, which is why "Revenue" and the number under it are associated rather than merely adjacent. Each delta announces its direction as a WORD — green-up / red-down is the canonical "colour as the only carrier of meaning" failure.
- Revenue
- £12,480Increased 12.4%
- Churn
- 2.1%Decreased 0.4pp
- Seats
- 318No change 0
Attribute surface
With no class-merging machinery, these attributes are the whole extension point. State is a data-attribute or a native attribute; visibility is the hiddenattribute. Generated from source by pnpm slots.
| Component | Slots | Variants | Runtime state | Native state |
|---|---|---|---|---|
| `Stat.astro` | `stat` | — | — | — |
| `StatDelta.astro` | `stat-delta` `stat-delta-direction` | `data-trend`: `up` · `down` · `flat` | — | — |
| `StatGroup.astro` | `stat-group` | `data-columns`: `1` · `2` · `3` · `4` | — | — |
| `StatLabel.astro` | `stat-label` | — | — | — |
| `StatValue.astro` | `stat-value` | — | — | — |
Source
What the command copies — 6 files, alias-free so the copy lands standing alone. This is the whole component, shown without its comments; the files you install keep them, with the reasoning behind every rule.
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"div">;
const { class: className, ...rest } = Astro.props;
---
<div class={className} data-slot="stat" {...rest}><slot /></div>
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"span"> & {
trend?: "up" | "down" | "flat";
label?: string;
};
const { trend = "flat", label, class: className, ...rest } = Astro.props;
const WORD = { up: "Increased", down: "Decreased", flat: "No change" } as const;
const direction = `${label ?? WORD[trend]} `;
---
<span class={className} data-slot="stat-delta" data-trend={trend} {...rest}>
<span data-slot="stat-delta-direction">{direction}</span><slot />
</span>
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"dl"> & { columns?: 1 | 2 | 3 | 4 };
const { columns = 3, class: className, ...rest } = Astro.props;
---
<dl class={className} data-slot="stat-group" data-columns={columns} {...rest}>
<slot />
</dl>
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"dt">;
const { class: className, ...rest } = Astro.props;
---
<dt class={className} data-slot="stat-label" {...rest}><slot /></dt>
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"dd">;
const { class: className, ...rest } = Astro.props;
---
<dd class={className} data-slot="stat-value" {...rest}><slot /></dd>
import Stat from "./Stat.astro";
import StatDelta from "./StatDelta.astro";
import StatGroup from "./StatGroup.astro";
import StatLabel from "./StatLabel.astro";
import StatValue from "./StatValue.astro";
export { Stat, StatDelta, StatGroup, StatLabel, StatValue };
export default StatGroup;
What you get
The component source, copied into your project by npx astrocraft-ui add display/stat — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.