{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "stat",
  "type": "registry:ui",
  "title": "Stat",
  "description": "A number tile for one figure: a small label, the value large in the sans with tabular numbers, and an optional change in words. Green and red are for good and bad news only. A group of tiles measures its own width.",
  "registryDependencies": [
    "https://littmd.vercel.app/r/litt.json",
    "https://littmd.vercel.app/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/litt/ui/stat.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/stat 2026.11.06. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Stat: a number tile for one figure that tells you how things stand, such as active projects or the\n * median build time. Compose a row of them in a StatGroup.\n *\n * - Flat: no box at all (no fill, no hairline); the figure sits on the page and space parts the tiles.\n * - Surface (components.md, Stat): `.litt-raised`, so the surface radius, surface-1, an inset hairline and the\n *   top highlight, and no outer shadow (R3). Never glass (R3), never an accent rail. Padding is space-4 in a narrow\n *   group and space-6 once the group is 48rem wide. A tile links nowhere, so it has no hover and no motion.\n * - Type: the label is the small size in fg-subtle, the value is the h3 size in fg in the sans with tabular\n *   numbers (a figure that updates does not wobble), and a unit follows the value at the same size in fg-subtle.\n *   The value carries the title weight that comes with the h3 step, and the label stays at the text weight (R8).\n * - Delta: a word and the number (\"Up 12% on last week\") at the caption size. The word says the direction. The\n *   color says whether it is news: success for good, danger for bad, fg-subtle for neither. Hidden text repeats\n *   the judgment (\"good news\"), so color is never the only signal. No arrow glyph, no dot.\n * - Trend: an optional slot at the foot of the tile, under the delta and the full width of the tile, for a Sparkline.\n *   It sits at the bottom whatever else the tile holds, so the trends in a row of tiles line up even when only some\n *   tiles have a delta. It is part of the description, so a screen reader reads the value, the delta, then the trend.\n * - Group: a description list laid out as a grid that measures itself (a container query), not the window: the\n *   shell takes 240px of a laptop screen, so the window is the wrong measure. Two across, four across from 40rem,\n *   one below 20rem. Tiles in a row share the row's height.\n * - Semantics: each tile is a term (the label) and its description (the value), so a screen reader reads\n *   \"Active projects, 12\". A Stat outside a group wraps itself in its own list.\n * - Motion: none. A value that changes just changes; an entrance cascade belongs to the page.\n * - Fun mode (motion/fun.tsx, a trial, off unless a <FunMode> is on): a value given as a number counts up on arrival\n *   with fun mode's pen, the same steady hand that draws a Sparkline in the trend slot, so the figure and its line\n *   arrive together. While it counts, screen readers get the value itself. At rest it is the static tile exactly.\n */\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport { useFun, useFunValue } from \"../../lib/litt/motion\";\n\n/** Whether a change is good news, bad news or neither. It sets the delta's color, and a hidden word for screen readers. */\nexport type StatTone = \"good\" | \"bad\" | \"neutral\";\n\nconst TONE: Record<StatTone, string> = {\n  good: \"text-litt-success\",\n  bad: \"text-litt-danger\",\n  neutral: \"text-litt-fg-subtle\",\n};\nconst TONE_WORD: Record<StatTone, string> = { good: \", good news\", bad: \", bad news\", neutral: \"\" };\n\nconst InGroup = createContext(false);\n\nexport type StatProps = {\n  /** What the figure measures, in sentence case with no colon: \"Deploys this week\". */\n  label: string;\n  /** The figure. A number or a short string such as \"99.98\". It is set in the sans with tabular numbers. */\n  value: ReactNode;\n  /** A unit that follows the value in fg-subtle, such as \"s\" or \"%\". Leave it out for a plain count. */\n  unit?: string;\n  /** A word and the number: \"Up 12% on last week\". Say the direction in words. Never write an arrow on its own. */\n  delta?: string;\n  /** Whether the change is good news, bad news or neither. A fall in build time is good, so this is not the direction. */\n  tone?: StatTone;\n  /** A Sparkline for the foot of the tile, under the delta and the full width of the tile. It sits at the bottom, so trends in a row line up whether or not a tile has a delta. */\n  trend?: ReactNode;\n  /** Extra classes for the tile, such as a column span. */\n  className?: string;\n};\n\n/** One number tile. Put it in a StatGroup, or on its own. */\nexport function Stat({ label, value, unit, delta, tone = \"neutral\", trend, className = \"\" }: StatProps) {\n  const grouped = useContext(InGroup);\n  /* Fun mode: a number counts with the pen. Its digits are hidden from screen readers while they move. */\n  const fun = useFun();\n  const numeric = typeof value === \"number\";\n  const counted = useFunValue(numeric ? value : 0, { kind: \"draw\" });\n  const figure =\n    fun && numeric && counted.moving ? (\n      <>\n        <span aria-hidden=\"true\">{Math.round(counted.shown)}</span>\n        <span className=\"sr-only\">{value}</span>\n      </>\n    ) : (\n      value\n    );\n  const tile = (\n    <div\n      className={\n        \"litt-raised in-[[data-material=flat]]:bg-transparent in-[[data-material=flat]]:shadow-none flex h-full min-w-0 flex-col gap-2 rounded-litt-surface p-4 @min-[48rem]:p-6 \" + className\n      }\n    >\n      <dt className=\"text-litt-small text-litt-fg-subtle\">{label}</dt>\n      <dd className=\"m-0 flex flex-1 flex-col gap-1\">\n        <span className=\"text-litt-h3 tabular-nums text-litt-fg\">\n          {figure}\n          {unit && <span className=\"text-litt-fg-subtle\">{unit}</span>}\n        </span>\n        {delta && (\n          <span className={\"text-litt-caption \" + TONE[tone]}>\n            {delta}\n            <span className=\"sr-only\">{TONE_WORD[tone]}</span>\n          </span>\n        )}\n        {trend ? <div className=\"mt-auto pt-3\">{trend}</div> : null}\n      </dd>\n    </div>\n  );\n  return grouped ? tile : <dl className=\"@container\">{tile}</dl>;\n}\n\nexport type StatGroupProps = {\n  /** The tiles. Each is a Stat. */\n  children: ReactNode;\n  /** Extra classes for the grid, such as a top margin. */\n  className?: string;\n};\n\n/** A grid of Stats that measures its own width: two across, four across from 40rem, one below 20rem. */\nexport function StatGroup({ children, className = \"\" }: StatGroupProps) {\n  return (\n    <InGroup.Provider value>\n      <div className=\"@container\">\n        <dl className={\"grid grid-cols-2 gap-4 @max-[20rem]:grid-cols-1 @min-[40rem]:grid-cols-4 \" + className}>\n          {children}\n        </dl>\n      </div>\n    </InGroup.Provider>\n  );\n}\n",
      "target": "components/litt/stat.tsx"
    }
  ],
  "docs": "Import Stat and StatGroup from components/litt/stat. Say the direction in the delta's words, and set tone to good, bad or neutral for whether it is news. Put the tiles in a StatGroup: two across, four across from 40rem.",
  "meta": {
    "version": "2026.11.06",
    "released": "2026-11-06",
    "changelog": "https://littmd.vercel.app/changelog.md"
  }
}
