{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "steps",
  "type": "registry:ui",
  "title": "Steps",
  "description": "Where someone is in a flow of a few steps, in the step marks: a check for done, a half-filled circle for now, an empty circle for what is left. A row, a column, or in a narrow box Step 2 of 4 with a rail per step.",
  "registryDependencies": [
    "https://littmd.vercel.app/r/litt.json",
    "https://littmd.vercel.app/r/icons.json"
  ],
  "files": [
    {
      "path": "registry/litt/ui/steps.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/steps 2026.11.06. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Steps: where someone is in a flow of a few steps, such as setting up a studio or checking out\n * (components.md, \"Steps\"). It says which steps are done, which one is now and what is left.\n *\n * - Marks: the step marks Plan and Tool call use, so one mark means one thing everywhere: a check in a circle for done\n *   (--litt-fg-muted), a half-filled circle for the current step (--litt-fg, never spinning or pulsing) and an empty circle for\n *   what is left (--litt-fg-subtle). Never a number in a colored disc and never the accent: a step is not an action.\n * - Words: the current step's label at the title weight in --litt-fg, done ones in --litt-fg-muted, the rest in --litt-fg-subtle,\n *   with an optional description under each in the vertical form.\n * - Horizontal: one row, a hairline between steps, in --litt-fg once the step before it is done. When its box is narrower\n *   than about 640px it becomes \"Step 2 of 4\", the current label, and a row of 4px rails, one per step, the done and\n *   current ones in --litt-fg. The box decides, not the screen, so it also fits a narrow panel.\n * - Vertical: a column, with the hairline running down between the marks, for a side panel or a checklist.\n * - Going back: with onStepSelect, done steps are buttons that go back to that step. The current step and what is left\n *   are never buttons: a flow moves forward by its own Continue.\n * - Accessibility: an ordered list named by the label. The current step has aria-current=\"step\", and each step's state\n *   is said in hidden text (\"done\", \"current\", \"not started\"). Nothing animates; the colors change at once.\n * - Flat: done is an ink disc with a tick, now an ink ring, to come the --litt-line-control edge; connectors and rails are thin ink or grey pills.\n */\nimport type { ReactNode } from \"react\";\nimport { CheckIcon, StatusIcon } from \"./icons\";\n\nexport type Step = {\n  /** Unique, and the React key. */\n  id: string;\n  /** One or two words: \"Details\", \"Team\", \"Billing\". */\n  label: string;\n  /** A line under the label, in the vertical form only. */\n  description?: ReactNode;\n};\n\nexport type StepsProps = {\n  /** What the flow is, as the list's name: \"Set up your studio\". */\n  label: string;\n  steps: Step[];\n  /** The index of the step someone is on. Steps before it are done. Set it to steps.length once the flow is finished. */\n  current: number;\n  /** Makes done steps buttons that go back to that step. */\n  onStepSelect?: (index: number) => void;\n  /** horizontal fits above a form; vertical fits a side panel or a checklist. */\n  orientation?: \"horizontal\" | \"vertical\";\n  className?: string;\n};\n\ntype State = \"done\" | \"current\" | \"todo\";\nconst SAID: Record<State, string> = { done: \"done\", current: \"current\", todo: \"not started\" };\nconst INK: Record<State, string> = { done: \"text-litt-fg-muted\", current: \"text-litt-fg font-litt-title\", todo: \"text-litt-fg-subtle\" };\nconst MARK: Record<State, \"done\" | \"running\" | \"pending\"> = { done: \"done\", current: \"running\", todo: \"pending\" };\n\nconst back =\n  \"rounded-litt-control transition-colors duration-(--litt-dur-instant) ease-litt hover:text-litt-fg \" +\n  \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-litt-focus\";\n\n/* Flat's mark (proposals/flat-pill.md): done is an ink disc with a tick, now an ink ring, to come the 3:1 control edge. */\nconst FLAT_MARK: Record<State, string> = {\n  done: \"bg-litt-fg text-litt-canvas\",\n  current: \"border-2 border-litt-fg\",\n  todo: \"border-[1.5px] border-litt-line-control\",\n};\n\n/** The mark, the label and the hidden state of one step, as a button when it can be gone back to. */\nfunction Label({ step, state, onSelect }: { step: Step; state: State; onSelect?: () => void }) {\n  const words = (\n    <>\n      {step.label}\n      <span className=\"sr-only\">, {SAID[state]}</span>\n    </>\n  );\n  const mark = (\n    <>\n      <StatusIcon\n        status={MARK[state]}\n        className={\"shrink-0 in-[[data-material=flat]]:hidden \" + (state === \"current\" ? \"text-litt-fg\" : state === \"done\" ? \"text-litt-fg-muted\" : \"text-litt-fg-subtle\")}\n      />\n      <span aria-hidden=\"true\" className={\"hidden size-4 shrink-0 place-items-center rounded-litt-pill in-[[data-material=flat]]:grid \" + FLAT_MARK[state]}>\n        {state === \"done\" && <CheckIcon className=\"size-3\" />}\n      </span>\n    </>\n  );\n  return onSelect && state === \"done\" ? (\n    <button type=\"button\" onClick={onSelect} className={\"flex min-w-0 items-center gap-2 text-left text-litt-small \" + INK[state] + \" \" + back}>\n      {mark}\n      <span className=\"min-w-0 truncate\">{words}</span>\n    </button>\n  ) : (\n    <span className={\"flex min-w-0 items-center gap-2 text-litt-small \" + INK[state]}>\n      {mark}\n      <span className=\"min-w-0 truncate\">{words}</span>\n    </span>\n  );\n}\n\n/** Where someone is in a flow of a few steps: done, now and what is left, in the step marks. */\nexport function Steps({ label, steps, current, onStepSelect, orientation = \"horizontal\", className = \"\" }: StepsProps) {\n  const stateOf = (i: number): State => (i < current ? \"done\" : i === current ? \"current\" : \"todo\");\n  const now = steps[Math.min(current, steps.length - 1)];\n  const finished = current >= steps.length;\n\n  if (orientation === \"vertical\") {\n    return (\n      <ol aria-label={label} className={\"grid \" + className}>\n        {steps.map((step, i) => {\n          const state = stateOf(i);\n          return (\n            <li key={step.id} aria-current={state === \"current\" ? \"step\" : undefined} className=\"grid grid-cols-[1rem_minmax(0,1fr)] gap-x-2\">\n              <div className=\"col-span-2\">\n                <Label step={step} state={state} onSelect={onStepSelect && (() => onStepSelect(i))} />\n              </div>\n              {/* The hairline down to the next mark, in --litt-fg once this step is done. */}\n              <span aria-hidden=\"true\" className={\"mx-auto w-px \" + (i < steps.length - 1 ? (state === \"done\" ? \"bg-litt-fg in-[[data-material=flat]]:bg-litt-fg\" : \"bg-litt-line in-[[data-material=flat]]:bg-litt-surface-3\") : \"\") + \" in-[[data-material=flat]]:w-0.5 in-[[data-material=flat]]:rounded-litt-pill\"} />\n              <div className={\"pb-6 text-litt-small text-litt-fg-subtle \" + (i === steps.length - 1 ? \"pb-0\" : \"\")}>\n                {step.description && <p className=\"pt-1\">{step.description}</p>}\n              </div>\n            </li>\n          );\n        })}\n      </ol>\n    );\n  }\n\n  return (\n    <div className={\"@container w-full \" + className}>\n      {/* Wide: every step in one row. */}\n      <ol aria-label={label} className=\"hidden items-center gap-3 @min-[40rem]:flex\">\n        {steps.map((step, i) => {\n          const state = stateOf(i);\n          return (\n            <li key={step.id} aria-current={state === \"current\" ? \"step\" : undefined} className=\"flex min-w-0 flex-1 items-center gap-3 last:flex-none\">\n              <Label step={step} state={state} onSelect={onStepSelect && (() => onStepSelect(i))} />\n              {i < steps.length - 1 && <span aria-hidden=\"true\" className={\"h-px min-w-6 flex-1 in-[[data-material=flat]]:h-0.5 in-[[data-material=flat]]:rounded-litt-pill \" + (state === \"done\" ? \"bg-litt-fg in-[[data-material=flat]]:bg-litt-fg\" : \"bg-litt-line in-[[data-material=flat]]:bg-litt-surface-3\")} />}\n            </li>\n          );\n        })}\n      </ol>\n\n      {/* Narrow: where you are in words, and one rail per step. */}\n      <div className=\"grid gap-2 @min-[40rem]:hidden\">\n        <p className=\"flex items-baseline justify-between gap-4\">\n          <span className=\"text-litt-small font-litt-title text-litt-fg\">{finished ? \"All steps done\" : now?.label}</span>\n          <span className=\"text-litt-caption font-normal tabular-nums text-litt-fg-subtle\">\n            {finished ? `${steps.length} of ${steps.length}` : `Step ${current + 1} of ${steps.length}`}\n          </span>\n        </p>\n        <ol aria-label={label} className=\"flex gap-1\">\n          {steps.map((step, i) => {\n            const state = stateOf(i);\n            return (\n              <li key={step.id} aria-current={state === \"current\" ? \"step\" : undefined} className=\"flex-1\">\n                <span className={\"block h-1 rounded-litt-control in-[[data-material=flat]]:rounded-litt-pill \" + (state === \"todo\" ? \"bg-[color-mix(in_oklab,var(--litt-fg)_12%,transparent)] in-[[data-material=flat]]:bg-litt-surface-3\" : \"bg-litt-fg in-[[data-material=flat]]:bg-litt-fg\")} />\n                <span className=\"sr-only\">\n                  {step.label}, {SAID[state]}\n                </span>\n              </li>\n            );\n          })}\n        </ol>\n      </div>\n    </div>\n  );\n}\n",
      "target": "components/litt/steps.tsx"
    }
  ],
  "docs": "Import Steps from components/litt/steps. Pass the steps and the current index; onStepSelect lets done steps go back.",
  "meta": {
    "version": "2026.11.06",
    "released": "2026-11-06",
    "changelog": "https://littmd.vercel.app/changelog.md"
  }
}
