{
  "components": [
    {
      "name": "Button",
      "description": "Action trigger. With `href` renders an anchor with identical styling (D33); `disabled` then maps to `aria-disabled`.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Label content."
        },
        {
          "name": "variant",
          "type": "\"accent\" | \"accent-subtle\" | \"neutral\" | \"neutral-subtle\" | \"ghost\" | \"danger\" | \"danger-subtle\" | \"outline\"",
          "required": false,
          "default": "neutral",
          "description": "Visual variant."
        },
        {
          "name": "size",
          "type": "24 | 32 | 40 | 48",
          "required": false,
          "default": 32,
          "description": "Height in px (24 | 32 | 40 | 48)."
        },
        {
          "name": "href",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Render as an anchor with this href (D33). disabled → aria-disabled,\nno href attribute, pointer-events: none."
        },
        {
          "name": "target",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Anchor target; only used with href."
        },
        {
          "name": "rel",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Anchor rel; only used with href."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLButtonElement | HTMLAnchorElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "IconButton",
      "description": "Square icon-only button; same variant/size/href contract as Button. Give it an accessible name (`aria-label`).",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "The icon element — an Icon* component."
        },
        {
          "name": "aria-label",
          "type": "string",
          "required": true,
          "default": null,
          "description": "Accessible name for the icon-only control. Required: an IconButton has\nno text to name it, so omitting this ships an unnameable button (D60).\nIf you also pass `aria-labelledby`, that wins per the accname algorithm."
        },
        {
          "name": "variant",
          "type": "\"accent\" | \"neutral\" | \"ghost\" | \"danger\"",
          "required": false,
          "default": "neutral",
          "description": "Visual variant."
        },
        {
          "name": "size",
          "type": "24 | 32 | 40 | 48",
          "required": false,
          "default": 32,
          "description": "Square size in px (24 | 32 | 40 | 48)."
        },
        {
          "name": "href",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Render as an anchor with this href (D33). disabled → aria-disabled,\nno href attribute, pointer-events: none."
        },
        {
          "name": "target",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Anchor target; only used with href."
        },
        {
          "name": "rel",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Anchor rel; only used with href."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLButtonElement | HTMLAnchorElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Card",
      "description": "Content container with an optional edge-to-edge media slot; `stacked` (media above body) or `featured` (media beside body) layout.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Body content, beside or below the media slot."
        },
        {
          "name": "variant",
          "type": "\"stacked\" | \"featured\"",
          "required": false,
          "default": "stacked",
          "description": "stacked = media above body; featured = media beside body (~1.6fr/1fr), stacks under md."
        },
        {
          "name": "media",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Media slot rendered edge-to-edge (img, AspectRatio, …)."
        },
        {
          "name": "hoverLift",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Lift on hover: translateY(-6px) over --psi-duration-350."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the root element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Panel",
      "description": "Elevated surface panel (D51): secondary background, faint hairline, radius-12 — the shared --psi-surface-* recipe Dialog's panel also binds. Not a Card: no media slot, no hover lift, opaque by design.",
      "slots": [
        {
          "name": "body",
          "accepts": {},
          "cardinality": "1..*",
          "order": 1
        }
      ],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Panel contents."
        },
        {
          "name": "padding",
          "type": "16 | 24",
          "required": false,
          "default": 24,
          "description": "Inner padding in px."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the root element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "DescriptionList",
      "description": "Key/value display (D70): a `<dl>` of term/value pairs, the body `detail-drawer` has always described and never had a component for. No `size` prop — this is type, and the type scale is already expressed by --psi-text-*. Consumers restyle via the token family, not a prop.",
      "slots": [
        {
          "name": "body",
          "accepts": {
            "components": [
              "DescriptionItem"
            ]
          },
          "cardinality": "1..*",
          "order": 1
        }
      ],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "One DescriptionItem per field."
        },
        {
          "name": "layout",
          "type": "\"stacked\" | \"inline\"",
          "required": false,
          "default": "stacked",
          "description": "`stacked` puts the term above its value; `inline` puts them in a\ntwo-column grid, which is what a detail drawer wants."
        },
        {
          "name": "gap",
          "type": "8 | 12 | 16",
          "required": false,
          "default": 12,
          "description": "Gap between pairs, in px."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDListElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the root element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "DescriptionItem",
      "description": "One term/value pair. The term is a prop and the value is children, matching Field's `label` idiom rather than inventing a two-slot shape. Renders `<div><dt/><dd/></div>`. The wrapper is the HTML5-sanctioned grouping element for a `<dl>`, so the `<dt>`/`<dd>` association assistive tech relies on is intact — and it keeps both layouts to one CSS rule each instead of fighting a single grid to space pairs differently from the term/value inside a pair.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "The value — renders the <dd>."
        },
        {
          "name": "term",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The term — renders the `<dt>`."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the group wrapper."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Input",
      "description": "Single-line text input with pixel-true heights (24–48) and an error state. Inside a Field, id/aria-describedby/aria-invalid/required are wired automatically (D49).",
      "slots": [],
      "props": [
        {
          "name": "type",
          "type": "\"number\" | \"text\" | \"search\" | \"email\" | \"tel\" | \"url\" | \"password\" | \"date\"",
          "required": false,
          "default": "text",
          "description": "Input type."
        },
        {
          "name": "size",
          "type": "24 | 32 | 40 | 48",
          "required": false,
          "default": 32,
          "description": "Height in px (24 | 32 | 40 | 48)."
        },
        {
          "name": "error",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Show error styling. Inside a Field, the Field's error also lights this."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLInputElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<input>` element."
        },
        {
          "name": "placeholder",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Native placeholder text shown while the field is empty (only on components whose host element supports it)."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Select",
      "description": "Styled native `<select>` with pixel-true heights (24–48) and an error state. Inside a Field, id/aria-describedby/aria-invalid/required are wired automatically (D49).",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "The <option> elements."
        },
        {
          "name": "size",
          "type": "24 | 32 | 40 | 48",
          "required": false,
          "default": 32,
          "description": "Height in px (24 | 32 | 40 | 48). Replaces the native `size` attribute\n(which sets the visible option-row count on a listbox-style `<select>`)\nwith the design system's control height in pixels."
        },
        {
          "name": "error",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Show error styling. Inside a Field, the Field's error also lights this."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLSelectElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<select>` element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Field",
      "description": "Labeled form-row wrapper: label above, control, one message line below — description normally, error when set (aria-live). Auto-wires id/aria-describedby/aria-invalid into Input and Select (D49).",
      "slots": [
        {
          "name": "label",
          "accepts": {},
          "cardinality": "0..1",
          "order": 1
        },
        {
          "name": "body",
          "accepts": {},
          "cardinality": "1..*",
          "order": 2
        },
        {
          "name": "description",
          "accepts": {},
          "cardinality": "0..1",
          "order": 3
        }
      ],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "The control this field labels."
        },
        {
          "name": "label",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Label content; renders a <label> (or <legend> in group mode)."
        },
        {
          "name": "description",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Helper line under the control; replaced by error when error is set."
        },
        {
          "name": "error",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Error content; when truthy it replaces the description and switches the\nfield (and a wrapped Input/Select) into error state."
        },
        {
          "name": "required",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Renders the required marker and flows `required` to the control."
        },
        {
          "name": "group",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Group mode: fieldset/legend wrapping several self-labeled controls\n(Checkbox/Switch); the message describes the whole group."
        },
        {
          "name": "htmlFor",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Override the generated control id (pair it with the same id on the control)."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement | HTMLFieldSetElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the root element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Dialog",
      "description": "Modal dialog on the native <dialog> top layer: focus trap, aria-modal and focus restore come from the platform; title/footer slots, Esc/backdrop/ close-button dismissal via onClose(reason) (D50). `placement` moves the panel and nothing else (D66): an `inline-start` / `inline-end` Dialog is a drawer, pinned full-height to that edge, with the same modality, focus trap, focus restore and dismissal reasons as a centered one. There is deliberately no separate Drawer component — a sibling would have to duplicate or wrap all of that.",
      "slots": [
        {
          "name": "title",
          "accepts": {
            "contracts": [
              "inline-content"
            ]
          },
          "cardinality": "0..1",
          "order": 1
        },
        {
          "name": "body",
          "accepts": {},
          "cardinality": "0..*",
          "order": 2
        },
        {
          "name": "footer",
          "accepts": {
            "components": [
              "Button"
            ],
            "contracts": [
              "inline-content"
            ]
          },
          "cardinality": "0..*",
          "order": 3
        }
      ],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Body content, between the title and footer slots."
        },
        {
          "name": "open",
          "type": "boolean",
          "required": true,
          "default": null,
          "description": "Controlled open state; syncs to showModal()/close()."
        },
        {
          "name": "onClose",
          "type": "(reason: \"esc\" | \"backdrop\" | \"close-button\") => void",
          "required": true,
          "default": null,
          "description": "Called on every dismissal attempt with its source; the consumer flips `open`."
        },
        {
          "name": "title",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Heading; renders an <h2> wired to aria-labelledby. Without it, pass aria-label."
        },
        {
          "name": "footer",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Action row (Buttons — one accent per group, danger for destructive)."
        },
        {
          "name": "width",
          "type": "400 | 560 | 720",
          "required": false,
          "default": 560,
          "description": "Panel width in px (400 | 560 | 720). For an `inline-*` placement this is\nthe drawer's width; the height is always the full viewport."
        },
        {
          "name": "placement",
          "type": "\"center\" | \"inline-start\" | \"inline-end\"",
          "required": false,
          "default": "center",
          "description": "Where the panel sits. `inline-start` / `inline-end` pin it full-height to\nthat edge, making it a drawer (D66)."
        },
        {
          "name": "dismissible",
          "type": "boolean",
          "required": false,
          "default": true,
          "description": "false = no close button, Esc and backdrop swallowed — footer is the only exit."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDialogElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying <dialog> element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Checkbox",
      "description": "Native checkbox with a built-in label; for independent binary choices.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Label text."
        },
        {
          "name": "aria-label",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Accessible name when there is no visible label — a table's row-selection\ncheckbox, say. Optional, unlike IconButton's required form (D60): a\nlabelled checkbox is still the common case."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLInputElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<input type=\"checkbox\">` element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Switch",
      "description": "On/off toggle (`role=\"switch\"`) with a built-in label; for settings that take effect immediately.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Label text."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLInputElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<input type=\"checkbox\" role=\"switch\">` element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Tag",
      "description": "Compact label for status or category; semantic variants, optional subtle tint and dismiss button.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Label content."
        },
        {
          "name": "variant",
          "type": "\"neutral\" | \"accent\" | \"success\" | \"warning\" | \"danger\"",
          "required": false,
          "default": "neutral",
          "description": "Semantic color variant."
        },
        {
          "name": "subtle",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Use tinted background with semantic foreground."
        },
        {
          "name": "onDismiss",
          "type": "(() => void)",
          "required": false,
          "default": null,
          "description": "When provided, renders a dismiss button that calls this handler."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLSpanElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<span>` element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Toolbar",
      "description": "Horizontal grouping row for filter/search controls (D52). Wraps on overflow; zero JS. Deliberately NOT ARIA role=\"toolbar\" — that role contracts roving-tabindex arrow-key navigation, wrong for form controls. With aria-label it announces as role=\"group\".",
      "slots": [
        {
          "name": "body",
          "accepts": {},
          "cardinality": "0..*",
          "order": 1
        }
      ],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "The controls."
        },
        {
          "name": "gap",
          "type": "8 | 12 | 16",
          "required": false,
          "default": 8,
          "description": "Gap between controls in px."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the root element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Tooltip",
      "description": "Short hover/focus hint attached to a single trigger element.",
      "slots": [],
      "props": [
        {
          "name": "content",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "Tooltip content."
        },
        {
          "name": "children",
          "type": "ReactElement<Record<string, unknown>, string | JSXElementConstructor<any>>",
          "required": true,
          "default": null,
          "description": "The trigger element (must accept ref, onMouseEnter/Leave, onFocus/Blur)."
        },
        {
          "name": "placement",
          "type": "\"top\" | \"bottom\" | \"left\" | \"right\"",
          "required": false,
          "default": "top",
          "description": "Preferred placement."
        }
      ]
    },
    {
      "name": "NavBar",
      "description": "Top navigation bar with brand, nav-link, and trailing-action slots.",
      "slots": [],
      "props": [
        {
          "name": "brand",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Brand slot (wordmark / logo link), leading edge."
        },
        {
          "name": "actions",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Trailing actions slot (theme switch, CTA)."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Nav links."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the header element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "AspectRatio",
      "description": "Fixed-aspect-ratio box that sizes its child media.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Content constrained to the ratio."
        },
        {
          "name": "ratio",
          "type": "number",
          "required": true,
          "default": null,
          "description": "Width/height ratio, e.g. 16/10 or 4/5."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the frame element."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Menu",
      "description": "Action menu on the native Popover API top layer: the top layer and light dismiss come from the platform; Esc, roving keyboard, placement and dismissal reasons are Psi's (D53). Controlled-only, like Dialog (D50). Controlled-only means Menu does not change its own visibility. Esc and item-select *report* a dismissal via onClose(reason) and leave the popover open; only the consumer flipping `open` to false actually closes it. Esc calls preventDefault() to suppress the platform's own popover dismissal, which is what makes that possible. Light dismiss (outside click) is the one asymmetry, and it is forced by the platform: the browser hides the popover itself and the hide-side `beforetoggle` is not cancelable, so the popover is already closed by the time the resulting `toggle` lets us report onClose(\"outside\"). The consumer must still flip `open` to false so React's state matches the DOM. `toggle` therefore reports only that one reason. A close driven by the consumer (`open` true -> false) runs through the sync effect's own hidePopover(), which raises a `toggle` too — that one is suppressed, because a programmatic close is not a dismissal and must not call onClose.",
      "slots": [
        {
          "name": "trigger",
          "accepts": {
            "contracts": [
              "interactive"
            ]
          },
          "cardinality": "1..1",
          "order": 1
        },
        {
          "name": "body",
          "accepts": {
            "components": [
              "MenuItem",
              "MenuSeparator"
            ]
          },
          "cardinality": "1..*",
          "order": 2
        }
      ],
      "props": [
        {
          "name": "open",
          "type": "boolean",
          "required": true,
          "default": null,
          "description": "Controlled open state; syncs to showPopover()/hidePopover()."
        },
        {
          "name": "onClose",
          "type": "(reason: \"item-select\" | \"esc\" | \"outside\") => void",
          "required": true,
          "default": null,
          "description": "Called when a dismissal is *requested*; the consumer flips `open`.\n\nMenu never closes itself on `\"esc\"` or `\"item-select\"` — the popover stays\nopen until `open` becomes false (D50). `\"outside\"` is the one exception:\nlight dismiss is performed by the browser before it tells us, so by the\ntime that reason is reported the popover is already closed. Flipping\n`open` to false is still required, to keep React's state in step.\n\nInvariant (D58): a dismissal is only ever reported for a menu that is\ncurrently open according to its own `open` prop. The platform can close\nan auto popover before the consumer's state catches up — clicking another\nmenu's trigger light-dismisses this one before the consumer's click\nhandler runs — and a report for an already-closed menu would clear a\nselection that has since moved on."
        },
        {
          "name": "trigger",
          "type": "ReactElement<Record<string, unknown>, string | JSXElementConstructor<any>>",
          "required": true,
          "default": null,
          "description": "The trigger element. Must be a single React element that spreads unknown\nprops onto a focusable node (a Psi `Button`, say): Menu clones it to add\n`aria-haspopup=\"menu\"` and `aria-expanded`, so assistive tech associates\nthe menu with the control users actually focus."
        },
        {
          "name": "placement",
          "type": "\"bottom-start\" | \"bottom-end\" | \"top-start\" | \"top-end\"",
          "required": false,
          "default": "bottom-start",
          "description": "Placement relative to the trigger."
        },
        {
          "name": "aria-label",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Accessible name for the menu when there is no visible label."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "MenuItem and MenuSeparator children."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the popover element."
        }
      ]
    },
    {
      "name": "MenuItem",
      "description": "One action in a Menu. Renders a real <button> so activation, Enter and Space come from the platform; the roving tabindex is applied by Menu's keyboard hook, which finds items via [data-psi-menu-item] (D53).",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "Item label."
        },
        {
          "name": "onSelect",
          "type": "() => void",
          "required": true,
          "default": null,
          "description": "Fires on activation; Menu then reports the dismissal via onClose(\"item-select\") (D50)."
        },
        {
          "name": "variant",
          "type": "\"neutral\" | \"danger\"",
          "required": false,
          "default": "neutral",
          "description": "danger is for destructive actions only (house rule)."
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": ""
        }
      ]
    },
    {
      "name": "MenuSeparator",
      "description": "Hairline rule between Menu item groups. No props by design (D53).",
      "slots": [],
      "props": []
    },
    {
      "name": "Table",
      "description": "Data table on native table semantics. Holds no state: sorting, selection and pagination are the consumer's (D62, extending D50/D53).",
      "slots": [
        {
          "name": "body",
          "accepts": {
            "components": [
              "TableHead",
              "TableBody"
            ]
          },
          "cardinality": "1..*",
          "order": 1
        }
      ],
      "props": [
        {
          "name": "size",
          "type": "32 | 40 | 48",
          "required": false,
          "default": 40,
          "description": "Row height in px."
        },
        {
          "name": "stickyHeader",
          "type": "boolean",
          "required": false,
          "default": null,
          "description": "Pins the header while the body scrolls."
        },
        {
          "name": "sortable",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Enables the sort affordance on header cells that declare a `sortKey`."
        },
        {
          "name": "sort",
          "type": "TableSortState | null",
          "required": false,
          "default": null,
          "description": "Controlled sort state; `null` when nothing is sorted."
        },
        {
          "name": "onSortChange",
          "type": "((sort: TableSortState) => void)",
          "required": false,
          "default": null,
          "description": "Called with the **next** sort state, already toggled — store it as given\nand do not toggle again. A fresh column arrives `\"asc\"`; an active `\"asc\"`\ncolumn emits `\"desc\"`; an active `\"desc\"` column emits `\"asc\"`.\n\nOptional in the type because `sortable` may be false; a discriminated union\nexpressing the real contract does not survive docgen's flat prop\nextraction, which would strip these props from the manifest entirely (D62)."
        },
        {
          "name": "selectable",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Renders the row-selection checkbox column."
        },
        {
          "name": "selected",
          "type": "ReadonlySet<string>",
          "required": false,
          "default": "new Set<string>()",
          "description": "Controlled selection, keyed by each `TableRow`'s `rowId`."
        },
        {
          "name": "onSelectionChange",
          "type": "((selected: ReadonlySet<string>) => void)",
          "required": false,
          "default": null,
          "description": "Called with the next selection. See `onSortChange` on why it is optional."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "TableHead and TableBody."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLTableElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<table>` element."
        }
      ]
    },
    {
      "name": "TableHead",
      "description": "`<thead>`. When the table is selectable, prepends the select-all cell to the FIRST header row so the header's column count matches the body's — the checkbox column is Table's, not the consumer's schema. Clones the row rather than rebuilding it, so the consumer's own props on that `TableRow` survive.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The header TableRow."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "TableBody",
      "description": "`<tbody>` wrapper.",
      "slots": [],
      "props": [
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The body TableRows."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "TableRow",
      "description": "`<tr>`. `rowId` is the key the `selected` set holds.",
      "slots": [],
      "props": [
        {
          "name": "rowId",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Stable identity for selection. Required for selectable tables (D62)."
        },
        {
          "name": "selectLabel",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Accessible name for this row's selection checkbox."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The row's cells."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "TableHeaderCell",
      "description": "`<th scope=\"col\">`. `aria-sort` belongs on the th, never on the inner button — assistive tech reads the sort state from the column header.",
      "slots": [],
      "props": [
        {
          "name": "sortKey",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Sort key this column emits. Enables the sort control when the table is `sortable`."
        },
        {
          "name": "numeric",
          "type": "boolean",
          "required": false,
          "default": null,
          "description": "Right-aligns and renders tabular figures (D62)."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Header label."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "TableCell",
      "description": "`<td>`. `numeric` means right-aligned *and* tabular — a column that aligns but whose digits jitter between rows defeats the purpose.",
      "slots": [],
      "props": [
        {
          "name": "numeric",
          "type": "boolean",
          "required": false,
          "default": null,
          "description": "Right-aligns and renders tabular figures (D62)."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Cell content."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Pagination",
      "description": "Numbered pager with ellipsis truncation (D63). Standalone rather than a Table family member: `table-pagination` composes it as a Toolbar sibling of the page-size Select.",
      "slots": [],
      "props": [
        {
          "name": "page",
          "type": "number",
          "required": true,
          "default": null,
          "description": "Current page, 1-based. A value outside `[1, pageCount]` (including\nnon-finite values like `NaN`) is clamped for rendering and logs a\n`console.warn` in development. `pageCount < 1` renders no page buttons."
        },
        {
          "name": "pageCount",
          "type": "number",
          "required": true,
          "default": null,
          "description": "Total number of pages."
        },
        {
          "name": "onPageChange",
          "type": "(page: number) => void",
          "required": true,
          "default": null,
          "description": "Called with the requested page. Required — unlike Table's `onSortChange`/\n`onSelectionChange`, which are optional because `sortable`/`selectable`\ngate whether they're meaningful, `Pagination` has no such gating boolean:\nthe prop is unconditionally meaningful, so a `Pagination` without it is a\ndead control (final review finding, D62)."
        },
        {
          "name": "siblingCount",
          "type": "number",
          "required": false,
          "default": 1,
          "description": "Pages shown either side of the current one before truncating."
        },
        {
          "name": "aria-label",
          "type": "string",
          "required": false,
          "default": "Pagination",
          "description": "Accessible name for the nav landmark."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        }
      ]
    },
    {
      "name": "Toast",
      "description": "A single transient notification card (D64). Controlled and presentational: it holds no state, runs no timer, and never removes itself — `onDismiss` reports and the owner disposes, exactly as Dialog (D50) and Menu (D53) do. Toast does not carry its own `role`/`aria-live`. Politeness belongs to the two persistent wrappers inside ToastRegion, because a live region must pre-exist the content it announces; a role on the toast itself would reintroduce the mount-with-content problem one level down. Composed by hand with ToastRegion for full control, or driven by ToastProvider's queue.",
      "slots": [
        {
          "name": "body",
          "accepts": {},
          "cardinality": "1..*",
          "order": 1
        },
        {
          "name": "action",
          "accepts": {
            "components": [
              "Button"
            ],
            "contracts": [
              "inline-content"
            ]
          },
          "cardinality": "0..1",
          "order": 2
        }
      ],
      "props": [
        {
          "name": "variant",
          "type": "\"neutral\" | \"success\" | \"warning\" | \"danger\"",
          "required": false,
          "default": "neutral",
          "description": "Status variant."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The message."
        },
        {
          "name": "action",
          "type": "ReactNode",
          "required": false,
          "default": null,
          "description": "Trailing affordance — a ghost Button (Undo, Retry, View)."
        },
        {
          "name": "onDismiss",
          "type": "(() => void)",
          "required": false,
          "default": null,
          "description": "When provided, renders the dismiss button and calls this. Toast does not\nremove itself; the owner disposes."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<div>`."
        }
      ]
    },
    {
      "name": "ToastRegion",
      "description": "The positioned live region that holds the toast stack (D64). Two things here are load-bearing and both look like details: 1. **The two live wrappers are always rendered**, empty queue included. A live region announces mutations to a subtree that already existed; a wrapper that mounts together with its first toast reads as a new subtree, and that first toast is never announced. 2. **`popover=\"manual\"`, not `\"auto\"`.** Manual puts the region in the native top layer without light dismiss. The top layer is required because Dialog uses showModal() — also top layer — so a fixed region at --psi-z-overlay would paint *under* the modal backdrop, hiding the confirmation for the action a user just took inside a dialog. And `auto` would be dismissed by the very click that raised the toast. Routing reads `variant` off each child, the same Children.map technique Table uses for select-all injection. Consequence of the split, visible in the `InRegion` VR baseline: the stack is grouped by politeness, not strictly chronological — every assertive toast sorts below every polite one regardless of arrival order. Chronological order is preserved *within* each group. Keeping it exact across both would mean one live region with a politeness that changes per message, which is the thing the two wrappers exist to avoid. Accepted: at `limit` 3 the grouping reads as severity ordering, and the newest toast still lands nearest the screen edge within its group.",
      "slots": [],
      "props": [
        {
          "name": "placement",
          "type": "\"top-start\" | \"top-end\" | \"bottom-start\" | \"bottom-end\"",
          "required": false,
          "default": "bottom-end",
          "description": "Corner the stack occupies."
        },
        {
          "name": "aria-label",
          "type": "string",
          "required": false,
          "default": "Notifications",
          "description": "Accessible name for the region."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The toast stack — `Toast` elements, routed to a live wrapper by variant."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the region element."
        }
      ]
    },
    {
      "name": "ToastProvider",
      "description": "Owns the toast queue, its auto-dismiss timers, and the single ToastRegion (D65). This is the library's one stateful container, and the exception is deliberately narrow. D50 and D53 rejected internal state for Dialog and Menu because the consumer already owned the state that decided visibility — a menu is open because a user clicked a trigger the consumer rendered. A toast has no such owner: it is created by an outcome, not by a UI state, and it disappears on a timer nobody is watching. The rule that survives is the useful half — presentational components stay controlled (`Toast` still holds nothing), a stateful container may exist when the state has no natural owner, and it must be opt-in. Timers pause while the pointer or focus is inside the region (WCAG 2.2.1), and resume with the time *remaining* rather than a fresh full duration — restarting would let a user hold a toast open indefinitely by jiggling the mouse.",
      "slots": [],
      "props": [
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "default": 3,
          "description": "Max simultaneous toasts; the oldest is evicted first."
        },
        {
          "name": "duration",
          "type": "number",
          "required": false,
          "default": 5000,
          "description": "Auto-dismiss for toasts with no action, in ms."
        },
        {
          "name": "actionDuration",
          "type": "number",
          "required": false,
          "default": 10000,
          "description": "Auto-dismiss for toasts carrying an action, in ms. An affordance that\nvanishes before it can be reached is not an affordance."
        },
        {
          "name": "placement",
          "type": "\"top-start\" | \"top-end\" | \"bottom-start\" | \"bottom-end\"",
          "required": false,
          "default": "bottom-end",
          "description": "Corner the stack occupies."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The subtree that may call `useToast()`. The region is rendered alongside it."
        }
      ]
    },
    {
      "name": "Tabs",
      "description": "Tab set root (D67) — holds no selection state of its own, following D50, D53 and D62: `value` and `onValueChange` are required and there is no `defaultValue`. Values are strings rather than indices because an index breaks the moment a tab is inserted, and real tab sets map to ids. `Tab` and `TabPanel` pair by value, so their source order need not match. Activation is automatic: arrow keys move focus and selection together. That is the APG default where panel content is already available, and manual activation is deliberately not offered as a mode — a consumer for whom activating a panel is expensive already controls what the panel renders.",
      "slots": [
        {
          "name": "body",
          "accepts": {
            "components": [
              "TabList",
              "TabPanel"
            ]
          },
          "cardinality": "1..*",
          "order": 1
        }
      ],
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "default": null,
          "description": "Controlled selected tab, matched against each `Tab`/`TabPanel` value."
        },
        {
          "name": "onValueChange",
          "type": "(value: string) => void",
          "required": true,
          "default": null,
          "description": "Fires with the newly selected value; the consumer flips `value`."
        },
        {
          "name": "orientation",
          "type": "\"horizontal\" | \"vertical\"",
          "required": false,
          "default": "horizontal",
          "description": "Axis of the tab list, which also picks the arrow keys."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "A `TabList` and one `TabPanel` per tab."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the wrapper `<div>`."
        }
      ]
    },
    {
      "name": "TabList",
      "description": "The `role=\"tablist\"` container (D67). Owns the roving tabindex: the whole list is one Tab stop, and the arrow keys move between tabs inside it.",
      "slots": [],
      "props": [
        {
          "name": "aria-label",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Accessible name for the tab set. Declared here rather than inherited so\ndocgen keeps it in the manifest (D60) — a tablist without a name is\nannounced as an unlabelled group, and the prop would otherwise be\ninvisible to an agent reading the manifest."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "One `Tab` per view."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the tablist element."
        }
      ]
    },
    {
      "name": "Tab",
      "description": "One `role=\"tab\"` (D67). Renders a real `<button>`. `disabled` sets `aria-disabled` rather than the `disabled` attribute, so the tab stays discoverable to assistive tech while being skipped by roving navigation — the same choice MenuItem made in D53.",
      "slots": [],
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "default": null,
          "description": "Pairs this tab with the `TabPanel` of the same value."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "Label — keep it to one or two words."
        },
        {
          "name": "disabled",
          "type": "boolean",
          "required": false,
          "default": false,
          "description": "Skipped by arrow navigation and not selectable, but still announced."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLButtonElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the underlying `<button>`."
        }
      ]
    },
    {
      "name": "TabPanel",
      "description": "One `role=\"tabpanel\"` (D67). Every panel renders; unselected ones carry `hidden`. Returning null instead would leave `aria-controls` on every unselected tab pointing at an element that does not exist, and would throw away DOM state — a half-filled form in a panel would lose its values on each tab switch. The cost is a heavier DOM when panels are large; the answer to that is rendering less inside the panel, not unmounting it behind the consumer's back. `tabIndex={0}` because a panel whose content has no focusable element would otherwise be unreachable by keyboard.",
      "slots": [],
      "props": [
        {
          "name": "value",
          "type": "string",
          "required": true,
          "default": null,
          "description": "Pairs this panel with the `Tab` of the same value."
        },
        {
          "name": "children",
          "type": "ReactNode",
          "required": true,
          "default": null,
          "description": "The view for this tab. Rendered even when unselected, then `hidden`."
        },
        {
          "name": "className",
          "type": "string",
          "required": false,
          "default": null,
          "description": "Additional CSS class name(s) merged onto the component's root element."
        },
        {
          "name": "ref",
          "type": "Ref<HTMLDivElement>",
          "required": false,
          "default": null,
          "description": "Forwarded ref to the panel element."
        }
      ]
    }
  ],
  "icons": [
    "IconAlertCircle",
    "IconAlertTriangle",
    "IconArrowDown",
    "IconArrowUpRight",
    "IconCheck",
    "IconChevronDown",
    "IconChevronRight",
    "IconClose",
    "IconCopy",
    "IconEdit",
    "IconExternalLink",
    "IconEye",
    "IconEyeOff",
    "IconGitHub",
    "IconInfo",
    "IconInstagram",
    "IconLinkedIn",
    "IconLoader",
    "IconMinus",
    "IconMoreHorizontal",
    "IconPlus",
    "IconSearch",
    "IconSettings",
    "IconTrash",
    "IconUser",
    "IconX"
  ]
}
