@godxjp/ui-mcp 19.3.1 → 19.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +100 -35
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Server as Le}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Me}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fe,ListResourcesRequestSchema as Be,ListToolsRequestSchema as qe,ReadResourceRequestSchema as Ve}from"@modelcontextprotocol/sdk/types.js";var v=[{name:"PageContainer",group:"layout",tagline:"Mandatory page shell \u2014 EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",props:[{name:"title",type:"string",required:!0,description:"Page heading rendered as <h1>."},{name:"subtitle",type:"string",description:"Secondary line beneath the title."},{name:"status",type:"ReactNode",description:'Status/meta band beside the title (StatusBadge, environment tag, "updated \u2026" meta). Sits on the title line at the token-owned --page-header-status-gap and wraps UNDER the title on compact viewports. Part of the canonical page-header contract \u2014 never hand-lay a badge next to the <h1>.'},{name:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"toolbar",type:"ReactNode",description:'FIXED chrome band between the page header and the body \u2014 a filter strip, a status bar, a "channel workflow" rail. It is a SIBLING of the body, not content inside it: with `fill` the body is the scroll viewport, so the band is `flex: none` OUTSIDE the scroller and never scrolls away or gets slid under. Shares the page gutters and the `measure` cap with the header and the body (the three bands line up on both edges), goes full-bleed under variant="flush" (wrap padded strips in PageContainer.Inset), and renders NOTHING when omitted \u2014 no wrapper, no gap. It sits FLUSH against the header above and the body below: chrome is attached, not a third page section floating between two voids, so the band cancels the container gap from itself and its ONLY breathing room is its own inset.'},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{name:"children",type:"ReactNode",description:'The page sections. Every direct child of the body is spaced from the previous one by --page-body-gap (the section step): drop your Cards straight in \u2014 do NOT wrap them in a Flex to space them, do NOT add gap-*/mt-*. Group items INSIDE a section with <Flex direction="col" gap> or <ResponsiveGrid>.'},{name:"breadcrumb",type:"BreadcrumbItemProp[]",description:"Ordered trail of { label, to? } segments above the title."},{name:"breadcrumbAriaLabel",type:"string",description:`Override the breadcrumb nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`},{name:"variant",type:'"default" | "narrow" | "flush" | "ghost"',defaultValue:'"default"',description:"Page shell layout; flush removes padding for full-bleed content."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Spacing density across the page subtree."},{name:"preset",type:'"default" | "admin-collection"',defaultValue:'"default"',description:'Whole-page semantic composition. "admin-collection" owns header-to-toolbar rhythm, collection search measure, control height and table density for the subtree through themeable tokens.'},{name:"headerLayout",type:'"stack" | "responsive-inline"',defaultValue:'"stack"',description:'How the title band and `extra` share the header row BELOW the 640px step. "stack" (default) drops `extra` onto its own full-width line under the subtitle. "responsive-inline" keeps it beside the title at the token-owned --page-header-extra-measure (11rem) and lets the title/subtitle wrap \u2014 use it for ONE compact control (a search field, a single primary action) that must stay on the title row at 390px. At >=640px the two arrangements are identical.'},{name:"headerScale",type:'"document" | "chrome"',defaultValue:'"document"',description:"What the page's top row IS, which decides the `<h1>`'s type step. \"document\" (default) = the row is the page's TITLE (a record, a form, a collection, a report): --page-title-font-size (h1, 20px) with the existing responsive step down at 720px; no attribute is emitted, so an existing page is byte-identical. \"chrome\" = the row is the surface's own furniture \u2014 a chat channel name, a mail subject line, an IDE tab \u2014 naming the thing the user is already INSIDE instead of announcing a document; the h1 takes --page-title-font-size-chrome (--heading-h3 = the 14px body step) at EVERY width, including below 720px where the document-scale step would otherwise pull it back UP. The heading stays an `<h1>` either way \u2014 this moves the type step only, never the element, so the screen-reader outline is untouched."},{name:"measure",type:'"default" | "narrow" | "medium"',defaultValue:'"default"',description:'Bounded page MEASURE shared by the header AND the body \u2014 a third axis, ORTHOGONAL to `variant` (chrome) and `headerLayout` (arrangement). "narrow" (--page-measure-narrow, 42rem outer \u2192 624px visible surface) and "medium" (--page-measure-medium, 48rem outer \u2192 720px visible surface) cap BOTH bands, so a header `extra` action ends flush with the body surface instead of stranded at the page edge \u2014 unlike variant="narrow", which caps only the body. The package-owned page gutters sit INSIDE the cap, and it is a max, so a 390px viewport stays fluid (358px surface at the 16px compact gutter). The footer is intentionally not capped (its border/background is page chrome when `stickyFooter` pins it).'},{name:"stickyFooter",type:"boolean",defaultValue:"false",description:'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'},{name:"footerReveal",type:'"always" | "onScroll"',defaultValue:'"always"',description:'When the footer is sticky, control WHEN it shows. "always" keeps it pinned the whole time; "onScroll" hides it until the header scrolls out of view then slides it up \u2014 the standard edit/create save bar. Stays mounted (no reflow \u2192 no jitter).'},{name:"fill",type:"boolean",defaultValue:"false",description:"Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface."},{name:"headerLoading",type:"boolean",defaultValue:"false",description:"Skeletonise the TITLE BAND only while the page's record resolves (title/subtitle placeholders + aria-busy; the <h1> stays in the outline with an sr-only accessible name). Breadcrumbs and `extra` stay live \u2014 they come from the route, not the record. This is not a page-wide loading flag; use DataState for the body."},{name:"linkComponent",type:"React.ElementType",description:"Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`."}],usage:["DO: Always wrap every page's content in PageContainer \u2014 it is the mandatory page shell. Pass `title` (required, rendered as `<h1>`) for every page; omitting it leaves the page without an accessible heading.","CANONICAL PAGE-HEADER CONTRACT: PageContainer's embedded header IS the DXS `PageHeader` \u2014 there is deliberately NO separate PageHeader export, so the page header cannot be re-created or nested. It owns breadcrumbs (`breadcrumb`), title (`title`), subtitle/description (`subtitle`), status/meta (`status`), actions (`extra`) and responsive overflow (`headerLayout` + `measure`). Loading/error/denied are compositions of siblings, never hand-rolls: skeleton `title`/`subtitle` content or `SkeletonDetail` body while a detail loads; `ErrorSurface` (from @godxjp/ui/layout) REPLACES the page for denied (403) / not-found (404) / failed (5xx) whole-page states; `Alert.QueryError` / `DataState` own an in-body query failure.","DO: Use the `extra` prop (not a sibling div, not a wrapper) for action buttons or controls that sit right of the title row \u2014 e.g. `extra={<Button>\u65B0\u898F\u4F5C\u6210</Button>}`. Use the `footer` prop for a pinned action bar below the body (e.g. Save/Cancel on a form page); combine with `stickyFooter` to pin it to the viewport bottom on scroll.","DO: Use `toolbar` for any FIXED strip that belongs between the page header and the page content \u2014 a filter/segment bar, a status or connection band, a list's bulk-action rail. DON'T put it in `children` (with `fill` the body is the scroller, so it scrolls out of sight) and NEVER hand-lay it with `position: sticky` / a `top-0 z-10` wrapper at the call site: page chrome is the shell's job, and a sticky strip still lets content flow underneath it \u2014 the half-sliced row every hand-rolled version produces. It stays out of the scroll viewport, inherits the page gutters and the `measure` cap so it lines up with the title and the body, and is entirely absent from the DOM when the prop is omitted.","DO: Know the `toolbar` band draws NO bottom rule by default \u2014 `--page-toolbar-divider` is unset and falls back to `--page-header-divider` (itself `none`), so a service that opts into the page-header divider gets a consistent band rule in ONE declaration, and `--page-toolbar-divider: none` silences just the band. `variant='ghost'` keeps both quiet unless a theme opts one in explicitly, which it then lets through.","DO: Give the `toolbar` band its own SURFACE from the theme when the design separates it from the page ground \u2014 `--page-toolbar-background: hsl(var(--card));` declared once (`:root` or a scoped `[data-tenant]`) paints the whole band, page gutters and `measure` cap included, and stays re-themeable per tenant. It is the `background` shorthand, so a gradient works too. The default is `transparent`, i.e. the band looks exactly as it did before the knob existed (rule #44).","DO: Set `--page-toolbar-pad-block` in the SAME theme declaration that paints or rules the band. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (chrome is attached \u2014 a ruled, painted band adrift in two 16px voids divides nothing), so there is no outside space to tune. The default is `0` and stays `0`: a transparent band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height comes straight off the scroll viewport the slot exists to protect. Once the band is painted or ruled it DOES have an inside, and `--page-toolbar-pad-block: var(--space-2)` is where that inset belongs. The CALL SITE never sets it \u2014 a strip padded at the call site pads only the strip, not the band.","DO: Silence the `footer` band's top rule with `--page-footer-divider: none` when the footer content already carries its own frame \u2014 a chat composer is a bordered Card, and the shell's full-width rule then lands directly above it as a SECOND line (a pixel diff against a consumer chat design caught a 100%-wide rule at y=701 the design does not have). This is the ONE page-chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where that line separates the actions from the content. Unset is byte-identical to the literal the rule used to hard-code. All three page bands are now one contract: `--page-header-divider` / `--page-toolbar-divider` / `--page-footer-divider`, each read at the CALL SITE with a fallback, none of them a `border-*` utility at the call site.","DON'T: Style the `toolbar` band from the call site. `toolbar={<div className='bg-card py-1.5'>\u2026</div>}` is hand-laid page chrome: the utility paints the STRIP, not the band, so it stops at the content box instead of running the full page width (and full-bleed under `variant='flush'`); it is invisible to per-tenant theming; and it puts geometry the shell owns back into the app. The band's ground, inset and rule are `--page-toolbar-background` / `--page-toolbar-pad-block` / `--page-toolbar-divider` \u2014 three theme declarations, zero call-site classes.","DO: Set `headerScale='chrome'` when the page's top row is CHROME rather than a document title \u2014 a chat channel header, a mail thread's subject line, an IDE tab, a conversation view. The `<h1>` drops to the body type step (--page-title-font-size-chrome) at every width, so the header band stops eating the height the content needs: a consumer chat page measured a 61px band with a 24px channel name where the design asked for ~40px at the `sm` step. Pair it with `variant='ghost'` for the full quiet chrome header \u2014 ghost drops the header's bottom pad and lets no divider inherit in \u2014 and with `fill` + `toolbar` + `footer`/`stickyFooter` for the canonical chat surface. The heading stays an `<h1>`: this is a type step, never a heading-level downgrade. The same attribute also drops the page's top padding to `--page-pad-block-start-chrome` (0), so the band sits ON the frame instead of floating in a document's top margin \u2014 four consequences of ONE fact (this row is furniture), not four props a call site has to keep in lockstep: the subtitle drops to `--page-subtitle-font-size-chrome` (~11px) so the caption under a channel name stops matching the name's own size, and the `extra` cluster centres on the bar instead of top-packing against a heading that is no longer tall. If a design wants its chrome inset or a different caption step, the theme retunes those tokens once; never pad, negative-margin or `self-center` the page at the call site.","DON'T: Reach for `headerScale='chrome'` just because a title \"looks too big\" on an ordinary document page (a record detail, a form, a collection, a report) \u2014 the page title is the document's headline and the h1 step is the system's answer for it; shrinking it there only breaks the type rhythm the rest of the page is measured against. And NEVER override `--page-title-font-size` (or put a `text-sm` / `text-base` utility on the title) at the call site to fake it: that re-themes every page in the subtree, is invisible to the 720px responsive step, and puts page-chrome geometry back in the app. If a service wants a different chrome step, it retunes `--page-title-font-size-chrome` (or `--page-subtitle-font-size-chrome`) once in its theme. Same for the header actions: never hang `self-center` / `items-center` on the node you pass to `extra` to fix an off-centre icon row \u2014 that aligns one call site's box while every other chrome page keeps the document's top-packed row.","DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageContainer.Inset.","DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.","DON'T: Use `density` to change individual control sizes \u2014 it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.","DO: Use `preset='admin-collection'` for canonical Admin list pages. It owns the toolbar/search/control/table composition once at PageContainer level; do not repeat widths, heights, cell padding or media queries on child fields and rows.","DO: Use `subtitle` (not `description`) and `extra` (not `actions`) \u2014 those are the canonical page-header names. If you see `description` / `actions` in old code, migrate them.","DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",'DO: Reach for `headerLayout="responsive-inline"` when a SINGLE compact header control (a member search, one primary action) must stay beside the title at 390px instead of wrapping under the subtitle. Its measure is the token `--page-header-extra-measure` (11rem) \u2014 never a consumer `w-[176px]` or a media query in app CSS. Keep the default `stack` when `extra` holds a toolbar of several buttons; squeezing those into the compact measure only makes them wrap in a narrower box.',"DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title. `variant='ghost'` does NOT overrule the token: it blocks a divider from INHERITING in (so an unset token stays silent) but an explicit `--page-header-divider` still draws on a ghost page \u2014 the same shape as `--page-toolbar-divider` on the band. Ghost's real quiet half is the header's bottom pad, which it drops.",'DO: Bound a readable/feed page with `measure="medium"` (720px visible surface) or `measure="narrow"` (624px) \u2014 NEVER a page-local `max-w-[720px]`, a wrapper div, or a consumer CSS variable override. `measure` caps the HEADER and the BODY together, which is the whole point: with `variant="narrow"` the header action stays out at the page edge while the body is 624px, so the action and the card do not share an end edge. Retune the presets once in a service theme via `--page-measure-narrow` / `--page-measure-medium`.','DO: Compose the axes \u2014 `variant="ghost" measure="medium" headerLayout="responsive-inline"` is the canonical quiet notification/inbox feed: ghost owns the quiet header rhythm (no divider, no header bottom pad, tighter title\u2192body gap), `measure` owns the shared 720px measure, `headerLayout` keeps one compact control on the title row at 390px. They are independent props precisely so chrome and measure are no longer one variant axis. DON\'T stack `variant="narrow"` on top of `measure` \u2014 the measure rule simply wins on the body (verified in Chromium: variant="narrow" + measure="medium" resolves the body to 768px, not the intersection), so the `variant="narrow"` is dead weight that only misleads the next reader. `variant="narrow"` is the legacy body-only cap; `measure="narrow"` is the same 624px surface with the header included.'],useCases:["A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body \u2014 use `variant='flush'` + `<PageContainer.Inset>` for the Toolbar above the table.","A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Flex direction='row' justify='between' className='w-full'><Button variant='outline'>\u524A\u9664</Button><Button>\u4FDD\u5B58</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view \u2014 the canonical edit/create pattern.","A settings or narrow-form page (e.g. account profile, entity configuration) where `variant='narrow'` constrains content to a readable column width and `stickyFooter` pins the submit bar.","A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Flex direction='col' gap='lg'>\u2026</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.","Any deep-nav page in a multi-level admin (e.g. Accounting > Ledger > Journal Entry #42) where a 3-segment breadcrumb trail provides back-navigation without browser history dependence.","A high-density data reconciliation page where an analyst needs to see maximum rows \u2014 use `density='compact'` to tighten all spacing across the DataTable, Toolbar, and controls in a single prop.","A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom \u2014 use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport.","A Slack-like chat channel, a mail thread, or an IDE-style tab view whose top row is the SURFACE's name rather than a document title \u2014 `headerScale='chrome'` (usually with `variant='ghost'`) puts the `<h1>` on the body type step so the header reads as a channel label and the band collapses to roughly the height of one control row, leaving the vertical space to the conversation.","A chat channel page where a fixed band (channel workflow / pinned-message / connection status) must sit between the page header and the scrolling transcript \u2014 `toolbar={<Toolbar>\u2026</Toolbar>}` with `fill` + `footer={<Composer/>}` + `stickyFooter`. The band is outside the scroller, so the transcript never travels under it and the composer stays pinned; a collection page uses the same slot for its filter strip above a full-bleed DataTable (`variant='flush'`)."],related:["PageContainer.Inset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.","PageContainer \u2014 always use PageContainer for new pages; it supports `children`, `toolbar`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) should be migrated to PageContainer.","AppShell \u2014 the outer shell that owns the sidebar/topbar layout grid; PageContainer lives inside AppShell's `children` slot. Do not put AppShell inside PageContainer \u2014 the nesting order is AppShell \u2192 PageContainer.","SplitPane \u2014 use instead of PageContainer when the page body needs a fixed-width aside panel alongside main content (e.g. a detail drawer next to a list). PageContainer has no aside slot; SplitPane fills that gap and can itself be placed inside PageContainer's children."],example:`import { PageContainer, Flex } from "@godxjp/ui/layout";
|
|
2
|
+
import{Server as Le}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Me}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fe,ListResourcesRequestSchema as Be,ListToolsRequestSchema as qe,ReadResourceRequestSchema as Ue}from"@modelcontextprotocol/sdk/types.js";var v=[{name:"PageContainer",group:"layout",tagline:"Mandatory page shell \u2014 EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",props:[{name:"title",type:"string",required:!0,description:"Page heading rendered as <h1>."},{name:"subtitle",type:"string",description:"Secondary line beneath the title."},{name:"status",type:"ReactNode",description:'Status/meta band beside the title (StatusBadge, environment tag, "updated \u2026" meta). Sits on the title line at the token-owned --page-header-status-gap and wraps UNDER the title on compact viewports. Part of the canonical page-header contract \u2014 never hand-lay a badge next to the <h1>.'},{name:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"toolbar",type:"ReactNode",description:'FIXED chrome band between the page header and the body \u2014 a filter strip, a status bar, a "channel workflow" rail. It is a SIBLING of the body, not content inside it: with `fill` the body is the scroll viewport, so the band is `flex: none` OUTSIDE the scroller and never scrolls away or gets slid under. Shares the page gutters and the `measure` cap with the header and the body (the three bands line up on both edges), goes full-bleed under variant="flush" (wrap padded strips in PageContainer.Inset), and renders NOTHING when omitted \u2014 no wrapper, no gap. It sits FLUSH against the header above and the body below: chrome is attached, not a third page section floating between two voids, so the band cancels the container gap from itself and its ONLY breathing room is its own inset.'},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{name:"children",type:"ReactNode",description:'The page sections. Every direct child of the body is spaced from the previous one by --page-body-gap (the section step): drop your Cards straight in \u2014 do NOT wrap them in a Flex to space them, do NOT add gap-*/mt-*. Group items INSIDE a section with <Flex direction="col" gap> or <ResponsiveGrid>.'},{name:"breadcrumb",type:"BreadcrumbItemProp[]",description:"Ordered trail of { label, to? } segments above the title."},{name:"breadcrumbAriaLabel",type:"string",description:`Override the breadcrumb nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`},{name:"variant",type:'"default" | "narrow" | "flush" | "ghost"',defaultValue:'"default"',description:"Page shell layout; flush removes padding for full-bleed content."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Spacing density across the page subtree."},{name:"preset",type:'"default" | "admin-collection"',defaultValue:'"default"',description:'Whole-page semantic composition. "admin-collection" owns header-to-toolbar rhythm, collection search measure, control height and table density for the subtree through themeable tokens.'},{name:"headerLayout",type:'"stack" | "responsive-inline"',defaultValue:'"stack"',description:'How the title band and `extra` share the header row BELOW the 640px step. "stack" (default) drops `extra` onto its own full-width line under the subtitle. "responsive-inline" keeps it beside the title at the token-owned --page-header-extra-measure (11rem) and lets the title/subtitle wrap \u2014 use it for ONE compact control (a search field, a single primary action) that must stay on the title row at 390px. At >=640px the two arrangements are identical.'},{name:"headerScale",type:'"document" | "chrome"',defaultValue:'"document"',description:"What the page's top row IS, which decides the `<h1>`'s type step. \"document\" (default) = the row is the page's TITLE (a record, a form, a collection, a report): --page-title-font-size (h1, 20px) with the existing responsive step down at 720px; no attribute is emitted, so an existing page is byte-identical. \"chrome\" = the row is the surface's own furniture \u2014 a chat channel name, a mail subject line, an IDE tab \u2014 naming the thing the user is already INSIDE instead of announcing a document; the h1 takes --page-title-font-size-chrome (--heading-h3 = the 14px body step) at EVERY width, including below 720px where the document-scale step would otherwise pull it back UP. The heading stays an `<h1>` either way \u2014 this moves the type step only, never the element, so the screen-reader outline is untouched."},{name:"measure",type:'"default" | "narrow" | "medium"',defaultValue:'"default"',description:'Bounded page MEASURE shared by the header AND the body \u2014 a third axis, ORTHOGONAL to `variant` (chrome) and `headerLayout` (arrangement). "narrow" (--page-measure-narrow, 42rem outer \u2192 624px visible surface) and "medium" (--page-measure-medium, 48rem outer \u2192 720px visible surface) cap BOTH bands, so a header `extra` action ends flush with the body surface instead of stranded at the page edge \u2014 unlike variant="narrow", which caps only the body. The package-owned page gutters sit INSIDE the cap, and it is a max, so a 390px viewport stays fluid (358px surface at the 16px compact gutter). The footer is intentionally not capped (its border/background is page chrome when `stickyFooter` pins it).'},{name:"stickyFooter",type:"boolean",defaultValue:"false",description:'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'},{name:"footerReveal",type:'"always" | "onScroll"',defaultValue:'"always"',description:'When the footer is sticky, control WHEN it shows. "always" keeps it pinned the whole time; "onScroll" hides it until the header scrolls out of view then slides it up \u2014 the standard edit/create save bar. Stays mounted (no reflow \u2192 no jitter).'},{name:"fill",type:"boolean",defaultValue:"false",description:"Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface."},{name:"headerLoading",type:"boolean",defaultValue:"false",description:"Skeletonise the TITLE BAND only while the page's record resolves (title/subtitle placeholders + aria-busy; the <h1> stays in the outline with an sr-only accessible name). Breadcrumbs and `extra` stay live \u2014 they come from the route, not the record. This is not a page-wide loading flag; use DataState for the body."},{name:"linkComponent",type:"React.ElementType",description:"Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`."}],usage:["DO: Always wrap every page's content in PageContainer \u2014 it is the mandatory page shell. Pass `title` (required, rendered as `<h1>`) for every page; omitting it leaves the page without an accessible heading.","CANONICAL PAGE-HEADER CONTRACT: PageContainer's embedded header IS the DXS `PageHeader` \u2014 there is deliberately NO separate PageHeader export, so the page header cannot be re-created or nested. It owns breadcrumbs (`breadcrumb`), title (`title`), subtitle/description (`subtitle`), status/meta (`status`), actions (`extra`) and responsive overflow (`headerLayout` + `measure`). Loading/error/denied are compositions of siblings, never hand-rolls: skeleton `title`/`subtitle` content or `SkeletonDetail` body while a detail loads; `ErrorSurface` (from @godxjp/ui/layout) REPLACES the page for denied (403) / not-found (404) / failed (5xx) whole-page states; `Alert.QueryError` / `DataState` own an in-body query failure.","DO: Use the `extra` prop (not a sibling div, not a wrapper) for action buttons or controls that sit right of the title row \u2014 e.g. `extra={<Button>\u65B0\u898F\u4F5C\u6210</Button>}`. Use the `footer` prop for a pinned action bar below the body (e.g. Save/Cancel on a form page); combine with `stickyFooter` to pin it to the viewport bottom on scroll.","DO: Use `toolbar` for any FIXED strip that belongs between the page header and the page content \u2014 a filter/segment bar, a status or connection band, a list's bulk-action rail. DON'T put it in `children` (with `fill` the body is the scroller, so it scrolls out of sight) and NEVER hand-lay it with `position: sticky` / a `top-0 z-10` wrapper at the call site: page chrome is the shell's job, and a sticky strip still lets content flow underneath it \u2014 the half-sliced row every hand-rolled version produces. It stays out of the scroll viewport, inherits the page gutters and the `measure` cap so it lines up with the title and the body, and is entirely absent from the DOM when the prop is omitted.","DO: Know the `toolbar` band draws NO bottom rule by default \u2014 `--page-toolbar-divider` is unset and falls back to `--page-header-divider` (itself `none`), so a service that opts into the page-header divider gets a consistent band rule in ONE declaration, and `--page-toolbar-divider: none` silences just the band. `variant='ghost'` keeps both quiet unless a theme opts one in explicitly, which it then lets through.","DO: Give the `toolbar` band its own SURFACE from the theme when the design separates it from the page ground \u2014 `--page-toolbar-background: hsl(var(--card));` declared once (`:root` or a scoped `[data-tenant]`) paints the whole band, page gutters and `measure` cap included, and stays re-themeable per tenant. It is the `background` shorthand, so a gradient works too. The default is `transparent`, i.e. the band looks exactly as it did before the knob existed (rule #44).","DO: Set `--page-toolbar-pad-block` in the SAME theme declaration that paints or rules the band. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (chrome is attached \u2014 a ruled, painted band adrift in two 16px voids divides nothing), so there is no outside space to tune. The default is `0` and stays `0`: a transparent band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height comes straight off the scroll viewport the slot exists to protect. Once the band is painted or ruled it DOES have an inside, and `--page-toolbar-pad-block: var(--space-2)` is where that inset belongs. The CALL SITE never sets it \u2014 a strip padded at the call site pads only the strip, not the band.","DO: Silence the `footer` band's top rule with `--page-footer-divider: none` when the footer content already carries its own frame \u2014 a chat composer is a bordered Card, and the shell's full-width rule then lands directly above it as a SECOND line (a pixel diff against a consumer chat design caught a 100%-wide rule at y=701 the design does not have). This is the ONE page-chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where that line separates the actions from the content. Unset is byte-identical to the literal the rule used to hard-code. All three page bands are now one contract: `--page-header-divider` / `--page-toolbar-divider` / `--page-footer-divider`, each read at the CALL SITE with a fallback, none of them a `border-*` utility at the call site.","DON'T: Style the `toolbar` band from the call site. `toolbar={<div className='bg-card py-1.5'>\u2026</div>}` is hand-laid page chrome: the utility paints the STRIP, not the band, so it stops at the content box instead of running the full page width (and full-bleed under `variant='flush'`); it is invisible to per-tenant theming; and it puts geometry the shell owns back into the app. The band's ground, inset and rule are `--page-toolbar-background` / `--page-toolbar-pad-block` / `--page-toolbar-divider` \u2014 three theme declarations, zero call-site classes.","DO: Set `headerScale='chrome'` when the page's top row is CHROME rather than a document title \u2014 a chat channel header, a mail thread's subject line, an IDE tab, a conversation view. The `<h1>` drops to the body type step (--page-title-font-size-chrome) at every width, so the header band stops eating the height the content needs: a consumer chat page measured a 61px band with a 24px channel name where the design asked for ~40px at the `sm` step. Pair it with `variant='ghost'` for the full quiet chrome header \u2014 ghost drops the header's bottom pad and lets no divider inherit in \u2014 and with `fill` + `toolbar` + `footer`/`stickyFooter` for the canonical chat surface. The heading stays an `<h1>`: this is a type step, never a heading-level downgrade. The same attribute also drops the page's top padding to `--page-pad-block-start-chrome` (0), so the band sits ON the frame instead of floating in a document's top margin \u2014 four consequences of ONE fact (this row is furniture), not four props a call site has to keep in lockstep: the subtitle drops to `--page-subtitle-font-size-chrome` (~11px) so the caption under a channel name stops matching the name's own size, and the `extra` cluster centres on the bar instead of top-packing against a heading that is no longer tall. If a design wants its chrome inset or a different caption step, the theme retunes those tokens once; never pad, negative-margin or `self-center` the page at the call site.","DON'T: Reach for `headerScale='chrome'` just because a title \"looks too big\" on an ordinary document page (a record detail, a form, a collection, a report) \u2014 the page title is the document's headline and the h1 step is the system's answer for it; shrinking it there only breaks the type rhythm the rest of the page is measured against. And NEVER override `--page-title-font-size` (or put a `text-sm` / `text-base` utility on the title) at the call site to fake it: that re-themes every page in the subtree, is invisible to the 720px responsive step, and puts page-chrome geometry back in the app. If a service wants a different chrome step, it retunes `--page-title-font-size-chrome` (or `--page-subtitle-font-size-chrome`) once in its theme. Same for the header actions: never hang `self-center` / `items-center` on the node you pass to `extra` to fix an off-centre icon row \u2014 that aligns one call site's box while every other chrome page keeps the document's top-packed row.","DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageContainer.Inset.","DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.","DON'T: Use `density` to change individual control sizes \u2014 it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.","DO: Use `preset='admin-collection'` for canonical Admin list pages. It owns the toolbar/search/control/table composition once at PageContainer level; do not repeat widths, heights, cell padding or media queries on child fields and rows.","DO: Use `subtitle` (not `description`) and `extra` (not `actions`) \u2014 those are the canonical page-header names. If you see `description` / `actions` in old code, migrate them.","DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",'DO: Reach for `headerLayout="responsive-inline"` when a SINGLE compact header control (a member search, one primary action) must stay beside the title at 390px instead of wrapping under the subtitle. Its measure is the token `--page-header-extra-measure` (11rem) \u2014 never a consumer `w-[176px]` or a media query in app CSS. Keep the default `stack` when `extra` holds a toolbar of several buttons; squeezing those into the compact measure only makes them wrap in a narrower box.',"DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title. `variant='ghost'` does NOT overrule the token: it blocks a divider from INHERITING in (so an unset token stays silent) but an explicit `--page-header-divider` still draws on a ghost page \u2014 the same shape as `--page-toolbar-divider` on the band. Ghost's real quiet half is the header's bottom pad, which it drops.",'DO: Bound a readable/feed page with `measure="medium"` (720px visible surface) or `measure="narrow"` (624px) \u2014 NEVER a page-local `max-w-[720px]`, a wrapper div, or a consumer CSS variable override. `measure` caps the HEADER and the BODY together, which is the whole point: with `variant="narrow"` the header action stays out at the page edge while the body is 624px, so the action and the card do not share an end edge. Retune the presets once in a service theme via `--page-measure-narrow` / `--page-measure-medium`.','DO: Compose the axes \u2014 `variant="ghost" measure="medium" headerLayout="responsive-inline"` is the canonical quiet notification/inbox feed: ghost owns the quiet header rhythm (no divider, no header bottom pad, tighter title\u2192body gap), `measure` owns the shared 720px measure, `headerLayout` keeps one compact control on the title row at 390px. They are independent props precisely so chrome and measure are no longer one variant axis. DON\'T stack `variant="narrow"` on top of `measure` \u2014 the measure rule simply wins on the body (verified in Chromium: variant="narrow" + measure="medium" resolves the body to 768px, not the intersection), so the `variant="narrow"` is dead weight that only misleads the next reader. `variant="narrow"` is the legacy body-only cap; `measure="narrow"` is the same 624px surface with the header included.'],useCases:["A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body \u2014 use `variant='flush'` + `<PageContainer.Inset>` for the Toolbar above the table.","A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Flex direction='row' justify='between' className='w-full'><Button variant='outline'>\u524A\u9664</Button><Button>\u4FDD\u5B58</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view \u2014 the canonical edit/create pattern.","A settings or narrow-form page (e.g. account profile, entity configuration) where `variant='narrow'` constrains content to a readable column width and `stickyFooter` pins the submit bar.","A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Flex direction='col' gap='lg'>\u2026</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.","Any deep-nav page in a multi-level admin (e.g. Accounting > Ledger > Journal Entry #42) where a 3-segment breadcrumb trail provides back-navigation without browser history dependence.","A high-density data reconciliation page where an analyst needs to see maximum rows \u2014 use `density='compact'` to tighten all spacing across the DataTable, Toolbar, and controls in a single prop.","A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom \u2014 use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport.","A Slack-like chat channel, a mail thread, or an IDE-style tab view whose top row is the SURFACE's name rather than a document title \u2014 `headerScale='chrome'` (usually with `variant='ghost'`) puts the `<h1>` on the body type step so the header reads as a channel label and the band collapses to roughly the height of one control row, leaving the vertical space to the conversation.","A chat channel page where a fixed band (channel workflow / pinned-message / connection status) must sit between the page header and the scrolling transcript \u2014 `toolbar={<Toolbar>\u2026</Toolbar>}` with `fill` + `footer={<Composer/>}` + `stickyFooter`. The band is outside the scroller, so the transcript never travels under it and the composer stays pinned; a collection page uses the same slot for its filter strip above a full-bleed DataTable (`variant='flush'`)."],related:["PageContainer.Inset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.","PageContainer \u2014 always use PageContainer for new pages; it supports `children`, `toolbar`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) should be migrated to PageContainer.","AppShell \u2014 the outer shell that owns the sidebar/topbar layout grid; PageContainer lives inside AppShell's `children` slot. Do not put AppShell inside PageContainer \u2014 the nesting order is AppShell \u2192 PageContainer.","SplitPane \u2014 use instead of PageContainer when the page body needs a fixed-width aside panel alongside main content (e.g. a detail drawer next to a list). PageContainer has no aside slot; SplitPane fills that gap and can itself be placed inside PageContainer's children."],example:`import { PageContainer, Flex } from "@godxjp/ui/layout";
|
|
3
3
|
import { Button } from "@godxjp/ui/general";
|
|
4
4
|
|
|
5
5
|
export default function OrdersPage() {
|
|
@@ -13,7 +13,7 @@ export default function OrdersPage() {
|
|
|
13
13
|
<Flex direction="col" gap="lg">{/* page content */}</Flex>
|
|
14
14
|
</PageContainer>
|
|
15
15
|
);
|
|
16
|
-
}`,storyPath:"layout/PageContainer.stories.tsx",rules:[23]},{name:"Flex",group:"layout",tagline:"Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls.",props:[{name:"direction",type:'"row" | "col"',defaultValue:'"row"',description:"Main axis direction. Defaults to the CSS platform initial value, row; use col explicitly for vertical stacks."},{name:"gap",type:'"xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:
|
|
16
|
+
}`,storyPath:"layout/PageContainer.stories.tsx",rules:[23]},{name:"Flex",group:"layout",tagline:"Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls.",props:[{name:"as",type:'"div" | "span"',defaultValue:'"div"',description:'Render element. Swaps the TAG only \u2014 `.ui-flex` carries `display: flex`, so the box is identical either way. Pass "span" whenever the Flex sits in a PHRASING context where a <div> is invalid HTML: inside a TabsTrigger, a PopoverTrigger or a Button (each renders a <button>, whose content model is phrasing content only), inside a <label>, or inside a <p>. Nest consistently \u2014 a <div> inside a "span" Flex is invalid again.'},{name:"direction",type:'"row" | "col"',defaultValue:'"row"',description:"Main axis direction. Defaults to the CSS platform initial value, row; use col explicitly for vertical stacks."},{name:"gap",type:'"none" | "xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:'Token gap between children, shared with other layout primitives. "none" is a DELIBERATE zero for two lines that read as one block \u2014 a name over its role, a weekday over its date, a tab bar with no seam \u2014 not a way to opt out of the token scale.'},{name:"align",type:'"start" | "center" | "end" | "stretch" | "baseline"',description:"Cross-axis alignment, emitted as a data attribute for the layout CSS."},{name:"justify",type:'"start" | "center" | "end" | "between" | "around" | "evenly"',description:"Main-axis distribution, emitted as a data attribute for the layout CSS."},{name:"wrap",type:"boolean",defaultValue:"false",description:"Allows children to wrap onto additional flex lines."},{name:"hideBelow",type:'"sm" | "md" | "lg" | "xl"',description:'Drop this region below a canonical breakpoint step (sm 40rem, md 48rem, lg 64rem, xl 80rem). THE public way to make a region responsive without a page-local media query \u2014 a public header hides its anchor navigation with hideBelow="md". Omitted by default, so no attribute is emitted and no rule matches. display:none also removes it from the a11y tree, so keep its destinations reachable elsewhere (a footer nav) at that width.'},{name:"hideFrom",type:'"sm" | "md" | "lg" | "xl"',description:"Inverse of hideBelow \u2014 drop the region FROM that step upwards, i.e. keep it only on the narrow side (a compact-only affordance)."}],usage:['DO import from `@godxjp/ui/layout` and reach for Flex when the axis, alignment, justification, or wrap behavior is part of the component contract: `import { Flex } from "@godxjp/ui/layout"`.',"DO keep spacing on the `gap` prop instead of raw `gap-*`, `space-*`, or padding utilities. Flex uses the same token scale as other layout primitives, so spacing remains tied to the design system.",'DO use `direction="row"` with `wrap` for responsive control rows, chip clusters, and action groups that need more control than simple row composition.','DO use `direction="col"` for vertical groupings that need explicit `align` or `justify` behavior. For pure vertical stacking without alignment control, `direction="col"` is sufficient.',"DON'T override the axis with `className` after choosing a direction prop. Keep the layout intent in props so catalog guidance and data attributes stay accurate.",'Flex is a plain div with React.HTMLAttributes<HTMLDivElement>; pass `id`, `role`, `aria-*`, `data-*`, and structural className values as needed, but do not use it as a semantic form or button wrapper. When the parent only accepts phrasing content \u2014 a TabsTrigger, PopoverTrigger or Button, all of which render a <button> \u2014 pass `as="span"` rather than reaching for a raw `<span className="flex \u2026">`.',"NAMED FLEX = GROUP: a role-less div may not carry a naming attribute (axe aria-allowed-attr), so a Flex given `aria-label`/`aria-labelledby` \u2014 e.g. by FormField wrapping a composite range/\u5E74\u6708 field \u2014 automatically renders `role='group'`, folds `aria-errormessage` into `aria-describedby`, and drops the widget-only `aria-required`/`aria-invalid`. Passing an explicit `role` opts out of all of this and the caller owns the attribute set."],useCases:["Toolbar internals where controls should sit in a row, wrap on narrow widths, and stay vertically centered.","Card headers that need title content on the left and actions on the right via `justify='between'` without hand-rolling flex utility classes.","Empty-state or loading blocks that center content on both axes using `align='center'` and `justify='center'`.","Form sub-sections where a vertical group needs stretched children or centered helper content beyond what a plain column Flex provides.","Badge, chip, or tag clusters where wrapping is required but the caller also needs explicit gap control.","Low-level layout composition inside custom components where raw flex classes would duplicate the primitive."],related:["Flex `direction='col'` \u2014 the standard pattern for ordinary vertical block spacing; use explicit `align`, `justify`, or `wrap` props when you need more control.","Flex `direction='row'` \u2014 the standard pattern for simple horizontal groups; add `wrap` and `align='center'` for the typical row with wrapped centered items.","ResponsiveGrid \u2014 use for equal-width, multi-column tile layouts. Flex arranges children on one flex axis and does not provide column-count behavior.","PageContainer \u2014 page scaffold and padding context. Flex is an inner layout primitive used inside page sections, cards, dialogs, and toolbars."],example:`import { Flex } from "@godxjp/ui/layout";
|
|
17
17
|
import { Button } from "@godxjp/ui/general";
|
|
18
18
|
|
|
19
19
|
<Flex direction="row" gap="sm" align="center" justify="between" wrap>
|
|
@@ -22,7 +22,7 @@ import { Button } from "@godxjp/ui/general";
|
|
|
22
22
|
<Button variant="outline">\u30EA\u30BB\u30C3\u30C8</Button>
|
|
23
23
|
<Button>\u9069\u7528</Button>
|
|
24
24
|
</Flex>
|
|
25
|
-
</Flex>`,storyPath:"layout/Flex.stories.tsx",rules:[2,40]},{name:"ResponsiveGrid",group:"layout",tagline:"Auto-responsive card grid \u2014 columns collapse to 1 on mobile, scale up on wider breakpoints.",props:[{name:"columns",type:"2 | 3 | 4",defaultValue:"3",description:"Target column count at desktop; collapses to 1 on mobile."},{name:"children",type:"ReactNode",required:!0,description:"Grid items \u2014 typically Card or StatCard."}],usage:["DO place StatCard tiles directly as immediate children \u2014 StatCard IS already a bordered card; never wrap it in an extra <Card><CardContent>. The canonical pattern is <ResponsiveGrid columns={4}><StatCard .../><StatCard .../></ResponsiveGrid>.","DO use columns={2|3|4} to declare the target desktop column count \u2014 the grid collapses automatically to 1 column on narrow containers (mobile-first via CSS container queries), via 2-column intermediate at \u2265640px, then full target count at \u22651024px. There is no 'columns={1}' \u2014 omit the grid for single-column flows.","DO NOT place a DataTable inside a ResponsiveGrid column beside a card or chart. DataTable must occupy its own full-width row in a Card with CardContent flush. Nesting a multi-column table in a grid column squeezes CJK text to one character per line (see rule 37).","DO use ResponsiveGrid for page-level spacing \u2014 it applies the correct gap token (--space-stack-md) automatically. Never add raw gap-* / p-* / space-* utilities to the page layout around tiles; compose spacing through this component instead (rule 40).","DO render SkeletonStat children in place of StatCard tiles while KPIs are loading \u2014 same columns prop, same count as the real tiles. Switch to real StatCard once data resolves.","The grid uses CSS container queries, not viewport media queries \u2014 it responds to its containing block width, not the window. Ensure the container is not artificially constrained (e.g. inside a narrow SplitPane column) or column expansion will never trigger."],useCases:["Dashboard KPI row: rendering 3\u20134 StatCard tiles (revenue, member count, active invoices, overdue amount) that reflow to a 2-column stacked grid on tablet and a single column on mobile.","Summary header above a list page: a 2-column grid of two StatCard totals (e.g. total payable vs total paid) sitting above a Toolbar and DataTable.","Accounting period overview: 4 StatCard tiles (opening balance, total debits, total credits, closing balance) that collapse gracefully on narrow viewports without any custom CSS.","Loading state for a KPI row: identical <ResponsiveGrid columns={4}> wrapping four <SkeletonStat /> placeholders rendered while async data is in flight, swapped for real StatCard tiles once resolved.","Settings or profile summary cards: 2- or 3-column grid of Card+CardContent blocks (not StatCard) showing categorized read-only data groups before a detail form below.","Entity comparison panel: a columns={3} grid comparing three legal entities side-by-side with a Card+CardContent per entity, which collapses to 2-up on tablet and stacks on mobile."],related:["Flex \u2014 use Flex (direction col or row) for sequential blocks of mixed-width content (forms, description lists, button rows). Use ResponsiveGrid only when you want equal-width, auto-reflowing tile columns.","SplitPane \u2014 use SplitPane for a fixed two-panel side-by-side layout with a defined primary/secondary ratio that does NOT collapse to stacked tiles. Use ResponsiveGrid when you want automatic column count collapse on narrow screens.","StatCard \u2014 the canonical direct child of ResponsiveGrid for KPI tiles. StatCard is self-contained (draws its own bordered card); never wrap it in Card/CardContent when placing it inside ResponsiveGrid.","SkeletonStat \u2014 the loading-state sibling of StatCard, used as a drop-in placeholder child of ResponsiveGrid with the same columns count while KPI data is in flight."],example:`import { ResponsiveGrid } from "@godxjp/ui/layout";
|
|
25
|
+
</Flex>`,storyPath:"layout/Flex.stories.tsx",rules:[2,40]},{name:"ResponsiveGrid",group:"layout",tagline:"Auto-responsive card grid \u2014 columns collapse to 1 on mobile, scale up on wider breakpoints.",props:[{name:"columns",type:"2 | 3 | 4",defaultValue:"3",description:"Target column count at desktop; collapses to 1 on mobile."},{name:"gap",type:'"none" | "xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:'Token gap between cells, the same steps as Flex. "none" is a DELIBERATE zero for tiles that must read as one continuous surface (a segmented bar, a seamless tile strip) \u2014 not a way to opt out of the token scale.'},{name:"children",type:"ReactNode",required:!0,description:"Grid items \u2014 typically Card or StatCard."}],usage:["DO place StatCard tiles directly as immediate children \u2014 StatCard IS already a bordered card; never wrap it in an extra <Card><CardContent>. The canonical pattern is <ResponsiveGrid columns={4}><StatCard .../><StatCard .../></ResponsiveGrid>.","DO use columns={2|3|4} to declare the target desktop column count \u2014 the grid collapses automatically to 1 column on narrow containers (mobile-first via CSS container queries), via 2-column intermediate at \u2265640px, then full target count at \u22651024px. There is no 'columns={1}' \u2014 omit the grid for single-column flows.","DO NOT place a DataTable inside a ResponsiveGrid column beside a card or chart. DataTable must occupy its own full-width row in a Card with CardContent flush. Nesting a multi-column table in a grid column squeezes CJK text to one character per line (see rule 37).","DO use ResponsiveGrid for page-level spacing \u2014 it applies the correct gap token (--space-stack-md) automatically. Never add raw gap-* / p-* / space-* utilities to the page layout around tiles; compose spacing through this component instead (rule 40).","DO render SkeletonStat children in place of StatCard tiles while KPIs are loading \u2014 same columns prop, same count as the real tiles. Switch to real StatCard once data resolves.","The grid uses CSS container queries, not viewport media queries \u2014 it responds to its containing block width, not the window. Ensure the container is not artificially constrained (e.g. inside a narrow SplitPane column) or column expansion will never trigger."],useCases:["Dashboard KPI row: rendering 3\u20134 StatCard tiles (revenue, member count, active invoices, overdue amount) that reflow to a 2-column stacked grid on tablet and a single column on mobile.","Summary header above a list page: a 2-column grid of two StatCard totals (e.g. total payable vs total paid) sitting above a Toolbar and DataTable.","Accounting period overview: 4 StatCard tiles (opening balance, total debits, total credits, closing balance) that collapse gracefully on narrow viewports without any custom CSS.","Loading state for a KPI row: identical <ResponsiveGrid columns={4}> wrapping four <SkeletonStat /> placeholders rendered while async data is in flight, swapped for real StatCard tiles once resolved.","Settings or profile summary cards: 2- or 3-column grid of Card+CardContent blocks (not StatCard) showing categorized read-only data groups before a detail form below.","Entity comparison panel: a columns={3} grid comparing three legal entities side-by-side with a Card+CardContent per entity, which collapses to 2-up on tablet and stacks on mobile."],related:["Flex \u2014 use Flex (direction col or row) for sequential blocks of mixed-width content (forms, description lists, button rows). Use ResponsiveGrid only when you want equal-width, auto-reflowing tile columns.","SplitPane \u2014 use SplitPane for a fixed two-panel side-by-side layout with a defined primary/secondary ratio that does NOT collapse to stacked tiles. Use ResponsiveGrid when you want automatic column count collapse on narrow screens.","StatCard \u2014 the canonical direct child of ResponsiveGrid for KPI tiles. StatCard is self-contained (draws its own bordered card); never wrap it in Card/CardContent when placing it inside ResponsiveGrid.","SkeletonStat \u2014 the loading-state sibling of StatCard, used as a drop-in placeholder child of ResponsiveGrid with the same columns count while KPI data is in flight."],example:`import { ResponsiveGrid } from "@godxjp/ui/layout";
|
|
26
26
|
import { StatCard } from "@godxjp/ui/data-display";
|
|
27
27
|
|
|
28
28
|
<ResponsiveGrid columns={4}>
|
|
@@ -48,7 +48,7 @@ const sidebar = (
|
|
|
48
48
|
|
|
49
49
|
export function CrmLayout({ children }: { content: React.ReactNode }) {
|
|
50
50
|
return <AppShell sidebar={sidebar}>{children}</AppShell>;
|
|
51
|
-
}`,storyPath:"layout/AppShell.stories.tsx",rules:[23]},{name:"AuthShell",group:"layout",tagline:"Centred auth/login page shell \u2014 brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred content \u2014 typically a single auth <Card> holding the form."},{name:"brand",type:"ReactNode",description:"Brand bar slot pinned to the top (e.g. a <Logo> / product mark)."},{name:"footer",type:"ReactNode",description:"Footer slot pinned to the bottom (legal links, locale switch, support)."},{name:"variant",type:'"default" | "canonical"',defaultValue:'"default"',description:'Visual contract for the auth surface. "canonical" applies the shared DXS compact geometry (36px controls, 22.5rem/360px card measure, responsive page insets, tighter field labels) through component tokens. "default" keeps the comfortable 44px shell with the 24rem card.'},{name:"preset",type:'"default" | "login" | "registration" | "device-authorization" | "context-selection" | "account-recovery"',defaultValue:'"default"',description:'Named flow GEOMETRY \u2014 the package-owned layout contract for a canonical hosted-identity flow. "device-authorization" = a 380px card at 1440/1024 with a 5px inline page gutter at 390, AND the code field itself: the preset hands --otp-slot-{inline,block}-size the canonical 27.5x52 device-grant slot, so two 4-slot grouped boxes measure 112x54 instead of the 146x38 the square --control-height tier produced. "registration" = the 360px sign-up measure with a 15px inline gutter at 390 (the same page rhythm as "login", so sign-in to sign-up never jumps on a phone). START-aligned like login: a sign-up card is the tallest surface in the set (name/email/password/confirm/strength/consent/submit/providers) and a vertically centred tall card overflows ABOVE the scroll origin on a short viewport, putting its first field out of reach.'},{name:"density",type:'"comfortable" | "compact"',description:'Vertical density scoped to auth-card descendants. Defaults to "compact" under variant="canonical" and "comfortable" otherwise.'},{name:"className",type:"string",description:"Extra CSS classes merged onto the shell root."}],usage:["DO pass a single <Card> (with the form inside <CardContent>) as `children` \u2014 AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.",'DO use `variant="canonical" preset="login"` for SCR-001 and pass <AuthIdentity>, <Card>, <AuthFooter> as direct children in that order (an anchor may wrap AuthIdentity). The preset owns the identity slot, card anchor, 20px section rhythm and compact card block inset for standalone and real requester states. Do not wrap the three sections in a consumer Flex/Stack or the semantic grid cannot anchor them.','DO select a `preset` instead of overriding geometry: `preset="login"` for the stable SCR-001 identity/card/footer anchor, `preset="registration"` for the sign-up form and its pending-email state, `preset="device-authorization"` for the 380px OAuth device-grant measure, `preset="context-selection"` for the 25rem organisation/context picker, `preset="account-recovery"` for the 432px SCR-008 recovery/MFA panel. Page-local width/inset/vertical-offset variables are the exact anti-pattern these presets replace.','DO build the SOCIAL / PROVIDER ACTION row as a COMPOSITION \u2014 there is NO SocialLinks component: `<AuthDivider label="or" />` followed by a `Flex direction="col" gap="sm"` of real `Button variant="outline"` with the provider glyph as an aria-hidden icon. The package deliberately does not own it: which providers a product offers, in what order, and what consent they imply are product decisions, and a component would have to invent them. `disabled` / `loading` are the Button\'s own props \u2014 do not add a provider-specific API.','DO build the ORGANIZATION CHOICE LIST as a COMPOSITION \u2014 there is NO OrganizationChoiceList component: `Card` > `CardContent flush` > a `<ul>` of `ListRow as="li"` (leading Avatar, title, description, trailing Button). `CardContent flush` is what gives shared row dividers instead of a card outline per row. Its states are existing exports, never bespoke markup: Skeleton rows for loading, `EmptyState` for no invitations, `Alert tone="destructive"` for a failed fetch and `Alert tone="warning"` for permission-denied. See the auth-shell-context and auth-shell-registration frames.','DO build the password-recovery and sign-in MFA CHALLENGE panels as a COMPOSITION inside `preset="account-recovery"` \u2014 there is NO PasswordRecoveryPanel and NO MfaChallengePanel component: Card > CardHeader(CardTitle + CardDescription, INSIDE the bordered surface) > CardContent > AuthStack[ Alert notice \xB7 FormField fields \xB7 Button fullWidth \xB7 Flex justify="between" wrap fallback row ]. Do NOT put AuthIdentity above the panel there (it always renders the hosted mark), and NEVER reuse TwoFactorSetup \u2014 that is the ENROLLMENT dialog, not a sign-in challenge. See the `auth-recovery-panels` pattern.','DO combine `variant` and `preset` \u2014 they are orthogonal: `variant` owns control density + heading size, `preset` owns the page measure. `variant="canonical" preset="device-authorization"` is the canonical device screen.','DO let `preset="context-selection"` space the auth column: it turns the card slot into a flex column with a tokenized `--auth-shell-card-stack-gap`, so an intro (<AuthIdentity>), the choice <Card> and a trailing "remember" row pass as three siblings with NO page-local spacing.',"DO NOT add a page-local width, inset or colour to hit an artboard \u2014 if a measure is missing, it is a library gap: a new preset or token, never consumer CSS (rules #44/#45).","DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) \u2014 it renders as the top banner landmark; omit it and the banner is not rendered.","DO use `footer` for compliance/legal/support links or a locale switch \u2014 it renders as the contentinfo landmark below the card.","DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) \u2014 Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.","DO NOT re-scope control height or heading size in the app \u2014 AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.","DO tune the COMPACT auth card through its three independent knobs, never a consumer selector on `[data-slot=\"card-content\"]`: `--auth-shell-compact-card-inset` = the inline column, `--auth-shell-card-padding-block-compact` = the card's block (top/bottom) padding \u2014 it is wired to the card's `--card-space-shell-y` and really moves CardContent's block edges \u2014 and `--auth-shell-card-body-gap-compact` = the header\u2194body gap. Each defaults to today's canonical rhythm, so overriding one moves ONLY that axis.","DO NOT nest AuthShell inside AppShell (or vice-versa) \u2014 AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."],useCases:['Canonical Login (SCR-001): <AuthShell variant="canonical" preset="login"> with direct AuthIdentity \xB7 Card \xB7 AuthFooter children; the card remains anchored when requester is absent, one line or wraps to two lines.',"Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.","MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.",'OAuth device-grant screen (SCR-004): <AuthShell variant="canonical" preset="device-authorization"> \u2014 a 380px card at 1440/1024 and a 5px inline gutter at 390, with zero page-local CSS.','Organisation / context selection (/select-context): <AuthShell variant="canonical" preset="context-selection" brand={<Logo mark="godx" />}> with an <AuthIdentity> intro, a <Card><CardContent flush> list of <ListRow as="li"> organisations, and a trailing "remember this choice" <Checkbox> \u2014 the preset spaces the three sections.',"Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",'SSO landing / success confirmation: pair with an <EmptyState tone="success"> inside the card for an approved-device confirmation.'],related:["AppShell \u2014 the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.","Reveal \u2014 wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.","Card \u2014 the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",'EmptyState \u2014 with `tone="success"` for a confirmation card inside the shell (e.g. device approved).','AuthIdentity / AuthFooter / AuthStack / AuthDivider \u2014 the auth composites that fill the shell: the GoDX identity mark + heading + requesting-client line, the mono legal footer (its `locale` slot takes an <AppSettingPicker kind="locale" appearance="labeled" compact />), the 12px section rhythm, and the labelled "or" rule.','ListRow \u2014 compose the organisation choice list for `preset="context-selection"` as <Card><CardContent flush><ul> of <ListRow as="li">: shared row dividers, no per-row card outline. There is no separate OrganizationChoiceList component \u2014 that is a composition pattern (rule #46), not a framework component.'],example:`import { AuthShell, AuthIdentity, AuthFooter } from "@godxjp/ui/layout";
|
|
51
|
+
}`,storyPath:"layout/AppShell.stories.tsx",rules:[23]},{name:"AuthShell",group:"layout",tagline:"Centred auth/login page shell \u2014 brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred content \u2014 typically a single auth <Card> holding the form."},{name:"brand",type:"ReactNode",description:"Brand bar slot pinned to the top (e.g. a <Logo> / product mark)."},{name:"actions",type:"ReactNode",description:'Page-level controls pinned to the TOP-RIGHT of the same banner row as `brand` \u2014 a locale <Select>/<AppSettingPicker>, a theme <ToggleGroup>, a "need help?" link. They belong to the PAGE, not the auth form, so they sit in the bar, not in the card. The banner renders as soon as `brand` OR `actions` is present.'},{name:"footer",type:"ReactNode",description:"Footer slot pinned to the bottom (legal links, locale switch, support)."},{name:"variant",type:'"default" | "canonical"',defaultValue:'"default"',description:'Visual contract for the auth surface. "canonical" applies the shared DXS compact geometry (36px controls, 22.5rem/360px card measure, responsive page insets, tighter field labels) through component tokens. "default" keeps the comfortable 44px shell with the 24rem card.'},{name:"preset",type:'"default" | "login" | "registration" | "device-authorization" | "context-selection" | "account-recovery"',defaultValue:'"default"',description:'Named flow GEOMETRY \u2014 the package-owned layout contract for a canonical hosted-identity flow. "device-authorization" = a 380px card at 1440/1024 with a 5px inline page gutter at 390, AND the code field itself: the preset hands --otp-slot-{inline,block}-size the canonical 27.5x52 device-grant slot, so two 4-slot grouped boxes measure 112x54 instead of the 146x38 the square --control-height tier produced. "registration" = the 360px sign-up measure with a 15px inline gutter at 390 (the same page rhythm as "login", so sign-in to sign-up never jumps on a phone). START-aligned like login: a sign-up card is the tallest surface in the set (name/email/password/confirm/strength/consent/submit/providers) and a vertically centred tall card overflows ABOVE the scroll origin on a short viewport, putting its first field out of reach.'},{name:"measure",type:'"default" | "wide"',defaultValue:'"default"',description:'Inline MEASURE of the shell content slot. "default" is the single auth card (24rem, or 22.5rem under variant="canonical"). "wide" opens the slot to --auth-shell-wide-card-max-width (64rem) for a SPLIT login: a brand/marketing panel beside the auth card, laid out with <ResponsiveGrid columns={{ sm: 1, lg: 2 }}>. The wide slot centres with auto margins, so a tall two-column layout starts at the top instead of overflowing above the scroll origin. Ignored under a `preset` \u2014 a preset already owns its flow geometry.'},{name:"density",type:'"comfortable" | "compact"',description:'Vertical density scoped to auth-card descendants. Defaults to "compact" under variant="canonical" and "comfortable" otherwise.'},{name:"className",type:"string",description:"Extra CSS classes merged onto the shell root."}],usage:["DO pass a single <Card> (with the form inside <CardContent>) as `children` \u2014 AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.",'DO use `variant="canonical" preset="login"` for SCR-001 and pass <AuthIdentity>, <Card>, <AuthFooter> as direct children in that order (an anchor may wrap AuthIdentity). The preset owns the identity slot, card anchor, 20px section rhythm and compact card block inset for standalone and real requester states. Do not wrap the three sections in a consumer Flex/Stack or the semantic grid cannot anchor them.','DO select a `preset` instead of overriding geometry: `preset="login"` for the stable SCR-001 identity/card/footer anchor, `preset="registration"` for the sign-up form and its pending-email state, `preset="device-authorization"` for the 380px OAuth device-grant measure, `preset="context-selection"` for the 25rem organisation/context picker, `preset="account-recovery"` for the 432px SCR-008 recovery/MFA panel. Page-local width/inset/vertical-offset variables are the exact anti-pattern these presets replace.','DO build the SOCIAL / PROVIDER ACTION row as a COMPOSITION \u2014 there is NO SocialLinks component: `<AuthDivider label="or" />` followed by a `Flex direction="col" gap="sm"` of real `Button variant="outline"` with the provider glyph as an aria-hidden icon. The package deliberately does not own it: which providers a product offers, in what order, and what consent they imply are product decisions, and a component would have to invent them. `disabled` / `loading` are the Button\'s own props \u2014 do not add a provider-specific API.','DO build the ORGANIZATION CHOICE LIST as a COMPOSITION \u2014 there is NO OrganizationChoiceList component: `Card` > `CardContent flush` > a `<ul>` of `ListRow as="li"` (leading Avatar, title, description, trailing Button). `CardContent flush` is what gives shared row dividers instead of a card outline per row. Its states are existing exports, never bespoke markup: Skeleton rows for loading, `EmptyState` for no invitations, `Alert tone="destructive"` for a failed fetch and `Alert tone="warning"` for permission-denied. See the auth-shell-context and auth-shell-registration frames.','DO build the password-recovery and sign-in MFA CHALLENGE panels as a COMPOSITION inside `preset="account-recovery"` \u2014 there is NO PasswordRecoveryPanel and NO MfaChallengePanel component: Card > CardHeader(CardTitle + CardDescription, INSIDE the bordered surface) > CardContent > AuthStack[ Alert notice \xB7 FormField fields \xB7 Button fullWidth \xB7 Flex justify="between" wrap fallback row ]. Do NOT put AuthIdentity above the panel there (it always renders the hosted mark), and NEVER reuse TwoFactorSetup \u2014 that is the ENROLLMENT dialog, not a sign-in challenge. See the `auth-recovery-panels` pattern.','DO combine `variant` and `preset` \u2014 they are orthogonal: `variant` owns control density + heading size, `preset` owns the page measure. `variant="canonical" preset="device-authorization"` is the canonical device screen.','DO let `preset="context-selection"` space the auth column: it turns the card slot into a flex column with a tokenized `--auth-shell-card-stack-gap`, so an intro (<AuthIdentity>), the choice <Card> and a trailing "remember" row pass as three siblings with NO page-local spacing.',"DO NOT add a page-local width, inset or colour to hit an artboard \u2014 if a measure is missing, it is a library gap: a new preset or token, never consumer CSS (rules #44/#45).","DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) \u2014 it renders as the top banner landmark; omit it and the banner is not rendered.",'DO put page-level controls in `actions` \u2014 the locale picker, the theme <ToggleGroup>, a "need help?" link. They land at the banner\'s inline end at a tokenized gap (`--auth-shell-bar-gap`), and the banner appears even with no `brand`. Do NOT hand-roll a top-right row with `ms-auto` on the `brand` content, and do NOT reach for CenteredShell just to get a topbar with actions: CenteredShell is the AUTHENTICATED shell.','DO use `measure="wide"` for the SPLIT login \u2014 a brand/marketing panel beside the auth card. The content slot opens to 64rem (`--auth-shell-wide-card-max-width`) and centres with auto margins, so the tall two-column layout starts at the top instead of overflowing above the scroll origin; lay the two halves out with <ResponsiveGrid columns={{ sm: 1, lg: 2 }}> and hide the panel below `lg`. It is IGNORED under a `preset` (a preset owns its flow geometry), so pick one or the other.',"DO use `footer` for compliance/legal/support links or a locale switch \u2014 it renders as the contentinfo landmark below the card.","DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) \u2014 Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.","DO NOT re-scope control height or heading size in the app \u2014 AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.","DO tune the COMPACT auth card through its three independent knobs, never a consumer selector on `[data-slot=\"card-content\"]`: `--auth-shell-compact-card-inset` = the inline column, `--auth-shell-card-padding-block-compact` = the card's block (top/bottom) padding \u2014 it is wired to the card's `--card-space-shell-y` and really moves CardContent's block edges \u2014 and `--auth-shell-card-body-gap-compact` = the header\u2194body gap. Each defaults to today's canonical rhythm, so overriding one moves ONLY that axis.","DO NOT nest AuthShell inside AppShell (or vice-versa) \u2014 AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."],useCases:['Canonical Login (SCR-001): <AuthShell variant="canonical" preset="login"> with direct AuthIdentity \xB7 Card \xB7 AuthFooter children; the card remains anchored when requester is absent, one line or wraps to two lines.',"Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.","MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.",'OAuth device-grant screen (SCR-004): <AuthShell variant="canonical" preset="device-authorization"> \u2014 a 380px card at 1440/1024 and a 5px inline gutter at 390, with zero page-local CSS.','Organisation / context selection (/select-context): <AuthShell variant="canonical" preset="context-selection" brand={<Logo mark="godx" />}> with an <AuthIdentity> intro, a <Card><CardContent flush> list of <ListRow as="li"> organisations, and a trailing "remember this choice" <Checkbox> \u2014 the preset spaces the three sections.','Split product login: <AuthShell measure="wide" brand={<Logo/>} actions={<><Select locale/><ToggleGroup theme/></>}> around a <ResponsiveGrid columns={{ sm: 1, lg: 2 }}> whose first cell is the brand/value panel (hidden below lg) and whose second is the auth <Card>. See docs/showcase/case4-login.',"Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",'SSO landing / success confirmation: pair with an <EmptyState tone="success"> inside the card for an approved-device confirmation.'],related:["AppShell \u2014 the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.","Reveal \u2014 wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.","Card \u2014 the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",'EmptyState \u2014 with `tone="success"` for a confirmation card inside the shell (e.g. device approved).','AuthIdentity / AuthFooter / AuthStack / AuthDivider \u2014 the auth composites that fill the shell: the GoDX identity mark + heading + requesting-client line, the mono legal footer (its `locale` slot takes an <AppSettingPicker kind="locale" appearance="labeled" compact />), the 12px section rhythm, and the labelled "or" rule.','ListRow \u2014 compose the organisation choice list for `preset="context-selection"` as <Card><CardContent flush><ul> of <ListRow as="li">: shared row dividers, no per-row card outline. There is no separate OrganizationChoiceList component \u2014 that is a composition pattern (rule #46), not a framework component.'],example:`import { AuthShell, AuthIdentity, AuthFooter } from "@godxjp/ui/layout";
|
|
52
52
|
import { Reveal, Logo, Button } from "@godxjp/ui/general";
|
|
53
53
|
import { Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
54
54
|
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
@@ -83,7 +83,7 @@ export function DeviceAuthorizationPage() {
|
|
|
83
83
|
</Reveal>
|
|
84
84
|
</AuthShell>
|
|
85
85
|
);
|
|
86
|
-
}`,storyPath:"layout/AuthShell.stories.tsx",rules:[23]},{name:"CenteredShell",group:"layout",tagline:"Authenticated, no-sidebar, centred-column page shell (hosted-ID My Page / account / standalone settings) \u2014 padded topbar with real actions + a width-tiered centred column, zero custom CSS.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred column content \u2014 page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like AuthShell's card."},{name:"topbar",type:"ReactNode",description:"Top bar slot (banner) \u2014 a <Topbar> with brand + real actions (AppSettingPicker, user menu, sign-out). Wrapped in the SAME padded chrome as AppShell's topbar (inline padding, border, backdrop) WITHOUT a sidebar; omit \u2192 no banner. Never hand-roll a bar (the bare Topbar ships no padding \u2014 the .ui-topbar zero-inset footgun)."},{name:"footer",type:"ReactNode",description:"Footer slot (contentinfo) pinned to the bottom (legal links, locale switch, support). Omit \u2192 no footer."},{name:"width",type:'"sm" | "md" | "lg"',description:"Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem \u2014 all wider than AuthShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*."},{name:"align",type:'"start" | "center"',description:'Block alignment of the centred column inside the 100dvh shell. "start" (default) keeps the top-aligned flowing/scrolling page shape. Overflowing content still scrolls from the top, so a long localized message is never clipped.'},{name:"preset",type:'"default" | "public-landing"',defaultValue:'"default"',description:'Whole-page shell contract. "default" emits no attribute and keeps the exact box. "public-landing" owns the PUBLIC landing geometry: ONE content measure shared by the header bar, the centred column and the footer (--centered-shell-landing-max-width, 67.5rem), the section rhythm between page sections, the flat elevation-free card chrome (--centered-shell-landing-card-shadow: none) and the hero h1 tier \u2014 plus the compact step at 40rem. A landing composition therefore needs no page-local CSS, no max-width wrapper and no descendant selector against shell internals.'}],usage:["DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar \u2014 the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) \xB7 AuthShell (unauthenticated narrow card) \xB7 CenteredShell (authenticated centred column).","DO put a <Topbar start={<brand/>} end={<actions/>}/> in `topbar` \u2014 CenteredShell wraps it in the padded `.app-topbar` chrome, so you get inline padding + border + backdrop with zero custom CSS. Do NOT hand-roll a bar with raw `padding-inline` \u2014 the bare Topbar primitive ships no inset (the .ui-topbar zero-padding footgun) and content sits flush to the edge.","DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than AuthShell's 24rem card.","DO wrap an individual section in <Reveal> for entrance motion \u2014 CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as AuthShell).",
|
|
86
|
+
}`,storyPath:"layout/AuthShell.stories.tsx",rules:[23]},{name:"CenteredShell",group:"layout",tagline:"Authenticated, no-sidebar, centred-column page shell (hosted-ID My Page / account / standalone settings) \u2014 padded topbar with real actions + a width-tiered centred column, zero custom CSS.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred column content \u2014 page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like AuthShell's card."},{name:"topbar",type:"ReactNode",description:"Top bar slot (banner) \u2014 a <Topbar> with brand + real actions (AppSettingPicker, user menu, sign-out). Wrapped in the SAME padded chrome as AppShell's topbar (inline padding, border, backdrop) WITHOUT a sidebar; omit \u2192 no banner. Never hand-roll a bar (the bare Topbar ships no padding \u2014 the .ui-topbar zero-inset footgun)."},{name:"footer",type:"ReactNode",description:"Footer slot (contentinfo) pinned to the bottom (legal links, locale switch, support). Omit \u2192 no footer."},{name:"width",type:'"sm" | "md" | "lg"',description:"Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem \u2014 all wider than AuthShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*."},{name:"align",type:'"start" | "center"',description:'Block alignment of the centred column inside the 100dvh shell. "start" (default) keeps the top-aligned flowing/scrolling page shape. Overflowing content still scrolls from the top, so a long localized message is never clipped.'},{name:"preset",type:'"default" | "public-landing"',defaultValue:'"default"',description:'Whole-page shell contract. "default" emits no attribute and keeps the exact box. "public-landing" owns the PUBLIC landing geometry: ONE content measure shared by the header bar, the centred column and the footer (--centered-shell-landing-max-width, 67.5rem), the section rhythm between page sections, the flat elevation-free card chrome (--centered-shell-landing-card-shadow: none) and the hero h1 tier \u2014 plus the compact step at 40rem. A landing composition therefore needs no page-local CSS, no max-width wrapper and no descendant selector against shell internals.'}],usage:["DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar \u2014 the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) \xB7 AuthShell (unauthenticated narrow card) \xB7 CenteredShell (authenticated centred column).","DO put a <Topbar start={<brand/>} end={<actions/>}/> in `topbar` \u2014 CenteredShell wraps it in the padded `.app-topbar` chrome, so you get inline padding + border + backdrop with zero custom CSS. Do NOT hand-roll a bar with raw `padding-inline` \u2014 the bare Topbar primitive ships no inset (the .ui-topbar zero-padding footgun) and content sits flush to the edge.","DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than AuthShell's 24rem card.","DO wrap an individual section in <Reveal> for entrance motion \u2014 CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as AuthShell).",'DO NOT use AuthShell for an authenticated page (it is the UNAUTHENTICATED root and imposes auth-card geometry), and DO NOT force AppShell with an empty sidebar \u2014 use CenteredShell. Conversely, do NOT reach for CenteredShell to build a login page: AuthShell has its own `actions` slot for the locale/theme controls and a `measure="wide"` for the split brand-panel login. Never nest it inside AppShell/AuthShell (or vice-versa); it is a ROOT shell.','DO use `align="center"` (+ `width="sm"`) for a SYSTEM-level standalone page \u2014 a 500/503 error surface, a maintenance notice. It centres the column in the 100dvh shell at 1440/1024/390 with no consumer `min-h-dvh` / flex CSS and no className; the knob is --centered-shell-column-offset-block. For an actual 403/404/500/503 page do NOT wire this by hand \u2014 use `ErrorSurface`, which renders this shell itself in `mode="system"`.'],useCases:[`Hosted GoDX ID 'My Page': <CenteredShell topbar={<Topbar start={<Brand/>} end={<><AppSettingPicker kind="locale"/><UserMenu/></>}/>} footer={<Footer/>} width="md"> with an identity hero, an org picker, a service-launcher grid and a team list.`,"Account / self-service settings surface (no admin sidebar): stacked <Card> sections (profile, security, sessions) in a centred `md` column under a topbar with a user menu.",'Standalone single settings page: `width="sm"` with one <Card> of <Field>s and a save action.','System error / maintenance page (500 \xB7 503): `<CenteredShell align="center" width="sm">` around the canonical error body (status code <Text mono tabular> + <EmptyState icon tone title description action> + optional request-ID / maintenance line). See the `error-pages` pattern.','Service launcher / app picker after sign-in: `width="lg"` with a <ResponsiveGrid> of app cards under the brand topbar.'],related:["AppShell \u2014 the shell for authenticated app pages WITH a sidebar nav rail. CenteredShell is its no-sidebar sibling (same padded topbar chrome, a centred column instead of a full-bleed main).",'AuthShell \u2014 the UNAUTHENTICATED root shell (login/mfa/reset): a ~24rem card centred vertically, with its own banner `actions` slot and a `measure="wide"` split-login measure. CenteredShell is the AUTHENTICATED centred-page counterpart. Never nest the two.',"Topbar \u2014 compose it into `topbar`; CenteredShell supplies the padded chrome the bare Topbar lacks.","PageContainer \u2014 for a titled section INSIDE the column; or compose <Card>/<ResponsiveGrid> sections directly."],example:`import { CenteredShell, Topbar, Flex } from "@godxjp/ui/layout";
|
|
87
87
|
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
88
88
|
import { Avatar, AvatarFallback, Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
89
89
|
import { Button, Text } from "@godxjp/ui/general";
|
|
@@ -117,7 +117,50 @@ export function MyPage() {
|
|
|
117
117
|
</Flex>
|
|
118
118
|
</CenteredShell>
|
|
119
119
|
);
|
|
120
|
-
}`,storyPath:"layout/CenteredShell.stories.tsx",rules:[23]},{name:"Sidebar",group:"layout",tagline:"Data-driven vertical nav rail with collapsible submenu groups and a collapsed icon-only mode \u2014 never build nav manually with raw buttons.",props:[{name:"activeId",type:"string",required:!0,description:"The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches."},{name:"sections",type:"SidebarSectionProp[]",required:!0,description:'Ordered list of nav sections. Each section has an optional string label and a required items array of SidebarItemProp. A row\'s count pill is `item.badge` (CONTENT ONLY \u2014 a number, a string, "9+"; never a <Badge> element, which would nest a pill inside the pill the row already draws) and its emphasis is `item.badgeTone`: "neutral" (default, the quiet unread pill) or "destructive" (the count is addressed to the user \u2014 an @mention, a DM, a failure waiting on them).'},{name:"onSelect",type:"(id: string) => void",description:"Called with the item id when a leaf nav item is clicked. Not called for group triggers or disabled items."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"When true, renders the icon-only collapsed rail. Labels become Tooltips on hover; group items open a portaled flyout popover on click. Section labels are hidden."},{name:"product",type:"SidebarProductProp",description:"Renders a product/app chip at the top of the sidebar (name, optional role subtitle, optional color swatch). Mutually exclusive with brand \u2014 brand takes precedence."},{name:"onProductClick",type:"() => void",description:"Click handler for the product chip button. Use to open an entity/workspace switcher sheet or dropdown."},{name:"brand",type:"ReactNode",description:"Custom brand slot rendered above the nav scroll area. When provided, the product chip is not rendered."},{name:"footer",type:"ReactNode",description:"Slot pinned to the bottom of the sidebar below the scrollable nav area. Commonly used for user identity, online status, or version info."},{name:"linkComponent",type:"SidebarLinkComponentProp",description:"THE framework-router contract. Supply only the link ELEMENT TYPE; the library keeps composing every row \u2014 icon slot, label, badge, data-active/aria-current, the icon-only collapsed rail and its tooltip name \u2014 and passes it to the link as SidebarLinkProp children. Applied to every row that carries an href: top-level leaves, submenu children, collapsed-rail leaves and collapsed flyout entries. Build it with createSidebarLink(Link, 'to') for React Router / TanStack, createSidebarLink(Link) or inertiaSidebarLink(Link) from @godxjp/ui/inertia for Inertia / Next.js."},{name:"renderItem",type:"(item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode",description:"DEPRECATED \u2014 use linkComponent, or asChild on SidebarItem. Legacy escape hatch that left row CONTENT to the caller, which is how a <Link>{item.label}</Link> silently dropped every icon and badge in production. Still supported and still wins over linkComponent; rowProps now also carries the library-composed children, so spreading rowProps (or rendering rowProps.children) restores the canonical row."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Main navigation"). Required when more than one Sidebar renders at once (e.g. a docked sidebar + its mobile-drawer twin) \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections \u2014 never hand-roll nav buttons alongside or instead of the Sidebar.","DO: Add children: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The parent item's icon is required even for groups. The group auto-opens and highlights when activeId matches any descendant.","DO: Mirror the collapsed boolean between AppShell's sidebarCollapsed prop and Sidebar's collapsed prop \u2014 they must stay in sync so the shell layout grid adjusts correctly.","DO: Use the footer prop for user info or status \u2014 it is pinned below the scroll area and does not scroll away.","DO: Give every navigable item an `item.href` and let the library render it. A plain `href` becomes a real <a> (context-menu open-in-new-tab, middle-click); with `linkComponent` that same href drives your framework router's <Link>. Either way the link IS the row and the ONLY interactive element (no nested <button>). Never put a <button>/<a> inside a default row.","DO: Wire a framework router with `linkComponent` \u2014 `createSidebarLink(Link, 'to')` (React Router / TanStack), `createSidebarLink(Link)` (Next.js), or `inertiaSidebarLink(Link)` from `@godxjp/ui/inertia`. That is the WHOLE integration: you pass the element type, the library composes the row. It threads through leaves, submenu children, the collapsed rail and the collapsed flyout. When you compose rows by hand, the same contract is `<SidebarItem item={item} asChild><Link to=\u2026 /></SidebarItem>` \u2014 write NO children; the library injects the icon, label and badge.","DON'T: Use `renderItem` in new code \u2014 it is DEPRECATED. It hands you a className + active state and leaves the row CONTENT to you, so `renderItem={(item) => <Link href={\u2026}>{item.label}</Link>}` renders a row with NO icon and NO badge. That is the exact production regression that motivated `linkComponent`. If you must keep it, render `rowProps.children` (the library-composed row) instead of hand-writing `.sb-icon` / `.sb-label` spans, which are internal class names and not a public contract.","DO: Rely on route-synchronized group expansion \u2014 a group OPENS automatically whenever `activeId` moves to one of its children (e.g. after a deep-link navigation), revealing the newly-active child; users can still collapse/expand manually.","DO: Theme the nav ICON and the row/label SEPARATELY with tokens \u2014 the icon reads `--sidebar-nav-icon-foreground` (+ `-hover-`/`-active-`/`-disabled-` variants) and the row/label reads `--sidebar-nav-item-foreground` (+ `-hover-`/`-disabled-`). Defaults are unchanged (both = `hsl(var(--muted-foreground))`, hover/active = `hsl(var(--foreground))`), so setting `--sidebar-nav-icon-foreground: hsl(var(--foreground))` in your theme is all it takes to get canonical darker 16px icons beside muted labels. NEVER write a page-local `.sb-nav-item svg { color: \u2026 }` rule and never re-tint `--muted-foreground` globally to fix sidebar icons.","DO: Give every item an `icon` \u2014 it is required by SidebarItemProp and by the canonical rail (the collapsed mode is icon-only). An item whose data arrives without one no longer crashes the shell; it renders an EMPTY 16px icon slot so the row keeps its geometry and label column, but it reads as a hole in the rail.","DO: Distinguish an UNREAD count from one ADDRESSED TO THE USER with `item.badgeTone` \u2014 'neutral' (the default, the pill unchanged) versus 'destructive' for an @mention, a direct message or a failure awaiting them. It emits `data-tone=\"destructive\"` on the existing `.sb-badge` and swaps two colour tokens (`--sidebar-badge-destructive-background` / `-foreground`); the pill's min-width, radius, inline pad and font size are shared by both tones, so mention rows and unread rows stay aligned in the same column. Retune all four `--sidebar-badge-*` knobs in your theme rather than styling the pill.","DON'T: Put a `<Badge>` (or anything else that draws its own pill) inside `item.badge` to colour a count \u2014 the row ALREADY wraps whatever you pass in a `.sb-badge` pill, so you get two nested pills with two borders (measured: a 37.11x19.14 `.sb-badge` wrapping a 25.11x19.14 `<Badge>`). Pass the CONTENT only (`badge: 3`, `badge: '9+'`) and say what it MEANS with `badgeTone`.","DON'T: Change icon SIZE or row geometry through these colour knobs \u2014 icon size stays `--sidebar-nav-icon-size` (16px) and row geometry stays `--sidebar-nav-item-height` / `--sidebar-nav-item-gap` / `--sidebar-nav-item-padding-x`. The active row's fill/label keep `--sidebar-item-active-background` / `--sidebar-item-active-foreground`.","DON'T: Manage collapse state inside the Sidebar \u2014 it is stateless. Hoist the boolean to your shell/page state and pass it down via both AppShell.sidebarCollapsed and Sidebar.collapsed.","DON'T: Nest children more than one level deep \u2014 only top-level items can have children; grandchild items are not rendered."],useCases:["Admin application shell nav with grouped sections (e.g. Operations / Fulfillment / Administration) where the sidebar can be collapsed to an icon rail for more content space.","Accounting app with a collapsible 'Ledger' group containing Journal, Chart of Accounts, and Period Close sub-pages \u2014 activeId reflects the current sub-page and the group stays open automatically.","Multi-tenant SaaS where onProductClick opens an entity/legal-entity switcher sheet and product.role shows the active tenant name beneath the product logo.","Any app using AppShell where navigation must degrade gracefully to an icon-only rail on narrow viewports or via a user toggle in the Topbar.","Apps with infrequent-access admin pages (Users, Roles, Password) grouped in a dedicated section that appears below primary operations sections.","A chat / messaging rail listing channels, where most rows carry a neutral unread count and only the channels that @mentioned the user carry `badgeTone: 'destructive'` \u2014 the rail answers \"does anything need me personally?\" at a glance, without a second pill or a hand-styled dot."],related:["AppShell \u2014 the shell that hosts Sidebar in its sidebar slot and owns the sidebarCollapsed layout grid; always compose Sidebar inside AppShell, not standalone in a page.","Topbar \u2014 the horizontal bar that renders the collapse toggle (onToggleCollapsed) and its collapsed prop must mirror the sidebar's collapsed state.","PageContainer \u2014 used for page-level title/subtitle/extra/breadcrumb inside AppShell's children slot, not inside Sidebar."],example:`
|
|
120
|
+
}`,storyPath:"layout/CenteredShell.stories.tsx",rules:[23]},{name:"MobileShell",group:"layout",tagline:"Handheld app shell (gh#354) \u2014 status band \xB7 app bar \xB7 the ONE scroll region \xB7 sticky action bar \xB7 bottom tab bar, with device safe-area insets and a document that never scrolls.",props:[{name:"children",type:"ReactNode",required:!0,description:"The scrolling screen body \u2014 the ONLY scroll container in the shell and its only elastic band. Everything else is fixed chrome, so a long list scrolls under a stationary app bar and tab bar instead of taking them off screen with it."},{name:"header",type:"ReactNode",description:"App bar (banner) pinned to the top: the screen title plus its inline actions. Absorbs the top safe-area inset when there is no statusBar. Omit \u2192 no banner. For a screen MODE (multi-select, search, edit) swap the whole node rather than stacking a second strip under it \u2014 replacing the bar's contents is the platform pattern on iOS and Android alike."},{name:"statusBar",type:"ReactNode",description:"The band that sits IN the OS status-bar strip above the app bar \u2014 the carrier/clock row of a display-mode:standalone PWA, or the simulated one in a device-frame preview. It owns the top safe-area inset when present. Omit it in an ordinary browser tab, where the OS already paints that strip and the header takes the inset instead."},{name:"actions",type:"ReactNode",description:"The sticky action bar pinned above the tab bar \u2014 the screen's primary verb (Scan, Save, Hand over) and at most one secondary. It sits OUTSIDE the scroll region, so it is always reachable with no `position: sticky` and no scroll-padding hack, and it takes the home-indicator inset whenever no tabBar follows it."},{name:"tabBar",type:"ReactNode",description:"Bottom tab bar (navigation) \u2014 the app's top-level destinations. Children TILE: equal width, no seam, no page gutter, so you never hand-roll a grid with a column count. Always the last band, so it owns the home-indicator inset."},{name:"height",type:'"viewport" | "fill"',defaultValue:'"viewport"',description:`Where the shell's one-screen height comes from. "viewport" (default) is the real app: exactly 100dvh, so the DOCUMENT never scrolls and the tab bar cannot slide away under a collapsing URL bar. "fill" fills a BOUNDED parent instead \u2014 a device-frame preview, or a phone view embedded in a wider page \u2014 where a viewport-tall root would overflow its frame. Nothing else differs between the two.`}],usage:["DO use MobileShell for a HANDHELD app screen \u2014 a warehouse/handy terminal, a driver app, a field-work PWA. It is the fourth root shell: AppShell (needs a sidebar) \xB7 AuthShell (unauthenticated card) \xB7 CenteredShell (authenticated scrolling document) \xB7 MobileShell (a phone app that does NOT scroll its document).","DO let `children` be the only thing that scrolls. Put the primary verb in `actions` and navigation in `tabBar` \u2014 both sit outside the scroll region, so neither needs `position: sticky`, a z-index, or bottom padding to clear the other.","DO NOT compose one out of <Card> + `ui-card-inset*` + `overflow-y-auto` (what docs/showcase/case6 did before gh#354). That reproduces the look and neither behaviour that matters on a device: the document still scrolls, and nothing pads out of env(safe-area-inset-*), so the notch covers the app bar and the home indicator covers the primary button.","DO swap the `header` node for a screen MODE (select mode, search mode) instead of stacking a second contextual strip below it \u2014 one bar to read, and the platform pattern on both iOS and Android.",'DO use `height="fill"` ONLY when the shell is inside a bounded parent (a device-frame preview). In a real app leave it at "viewport": that is what keeps the document from scrolling.',"DO NOT nest MobileShell inside AppShell / AuthShell / CenteredShell (or the reverse) \u2014 it is a ROOT shell. Retune the page gutter and the three band heights from the theme (--mobile-shell-inset-inline, --mobile-shell-header-bar-height, --mobile-shell-tab-bar-height, --mobile-shell-status-bar-height); never fork .ui-mobile-shell-* CSS."],useCases:["Warehouse handheld (\u4EE3\u7406\u5E97\u30CF\u30F3\u30C7\u30A3): a status band, an app bar with a select-mode text action, a scrolling item list, a dominant Scan button in `actions`, and a three-destination `tabBar` (inbound \xB7 packing \xB7 outbound). See the case6-agency-handy showcase.","Driver / delivery app: route list in the scroll region, 'Arrived' as the single `actions` verb, tabs for today \xB7 history \xB7 profile.","Field-inspection PWA installed to the home screen: `statusBar` paints the standalone strip, `header` carries the site name, and the form scrolls under both.",'A phone view embedded in a desktop device-frame preview: the same composition with `height="fill"` inside a fixed-size frame.'],related:["AppShell \u2014 the authenticated shell WITH a sidebar rail and a mobile drawer at the 900px step. Use it for an admin console that happens to be viewed on a phone; use MobileShell when the phone IS the product.","CenteredShell \u2014 the authenticated no-sidebar shell whose main scrolls the PAGE. MobileShell is its handheld counterpart: same 'no rail' shape, opposite scroll contract.","AuthShell \u2014 the unauthenticated root. A login screen inside a handheld app still belongs to AuthShell, not MobileShell.",'Sheet \u2014 `side="bottom"` is the handheld modal: scanners, pickers and forms open from the bottom over MobileShell rather than navigating away.'],example:`import { Flex, MobileShell } from "@godxjp/ui/layout";
|
|
121
|
+
import { Button, Heading, Text } from "@godxjp/ui/general";
|
|
122
|
+
import { Inbox, Package, ScanLine, Truck } from "lucide-react";
|
|
123
|
+
|
|
124
|
+
export function HandyInbound() {
|
|
125
|
+
return (
|
|
126
|
+
<MobileShell
|
|
127
|
+
statusBar={<Text size="sm" tabular>9:41</Text>}
|
|
128
|
+
header={
|
|
129
|
+
<Flex align="center" justify="between" gap="xs" className="w-full">
|
|
130
|
+
<Heading level={3} as="h1">\u5165\u5EAB</Heading>
|
|
131
|
+
<Button variant="ghost" size="sm">\u9078\u629E</Button>
|
|
132
|
+
</Flex>
|
|
133
|
+
}
|
|
134
|
+
actions={
|
|
135
|
+
<Button className="flex-[2]">
|
|
136
|
+
<ScanLine aria-hidden="true" />
|
|
137
|
+
\u30B9\u30AD\u30E3\u30F3
|
|
138
|
+
</Button>
|
|
139
|
+
}
|
|
140
|
+
tabBar={
|
|
141
|
+
<>
|
|
142
|
+
<Button variant="ghost" aria-current="page" className="h-full flex-col rounded-none">
|
|
143
|
+
<Inbox aria-hidden="true" />
|
|
144
|
+
<Text size="2xs">\u5165\u5EAB</Text>
|
|
145
|
+
</Button>
|
|
146
|
+
<Button variant="ghost" className="h-full flex-col rounded-none">
|
|
147
|
+
<Package aria-hidden="true" />
|
|
148
|
+
<Text size="2xs">\u68B1\u5305</Text>
|
|
149
|
+
</Button>
|
|
150
|
+
<Button variant="ghost" className="h-full flex-col rounded-none">
|
|
151
|
+
<Truck aria-hidden="true" />
|
|
152
|
+
<Text size="2xs">\u51FA\u5EAB</Text>
|
|
153
|
+
</Button>
|
|
154
|
+
</>
|
|
155
|
+
}
|
|
156
|
+
>
|
|
157
|
+
<Flex direction="col" gap="sm">
|
|
158
|
+
<Text>\u6D17\u9854\u30D5\u30A9\u30FC\u30E0</Text>
|
|
159
|
+
<Text>\u65E5\u713C\u3051\u6B62\u3081</Text>
|
|
160
|
+
</Flex>
|
|
161
|
+
</MobileShell>
|
|
162
|
+
);
|
|
163
|
+
}`,storyPath:"layout/MobileShell.stories.tsx",rules:[23,24,45]},{name:"Sidebar",group:"layout",tagline:"Data-driven vertical nav rail with collapsible submenu groups and a collapsed icon-only mode \u2014 never build nav manually with raw buttons.",props:[{name:"activeId",type:"string",required:!0,description:"The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches."},{name:"sections",type:"SidebarSectionProp[]",required:!0,description:'Ordered list of nav sections. Each section has an optional string label and a required items array of SidebarItemProp. A row\'s count pill is `item.badge` (CONTENT ONLY \u2014 a number, a string, "9+"; never a <Badge> element, which would nest a pill inside the pill the row already draws) and its emphasis is `item.badgeTone`: "neutral" (default, the quiet unread pill) or "destructive" (the count is addressed to the user \u2014 an @mention, a DM, a failure waiting on them).'},{name:"onSelect",type:"(id: string) => void",description:"Called with the item id when a leaf nav item is clicked. Not called for group triggers or disabled items."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"When true, renders the icon-only collapsed rail. Labels become Tooltips on hover; group items open a portaled flyout popover on click. Section labels are hidden."},{name:"product",type:"SidebarProductProp",description:"Renders a product/app chip at the top of the sidebar (name, optional role subtitle, optional color swatch). Mutually exclusive with brand \u2014 brand takes precedence."},{name:"onProductClick",type:"() => void",description:"Click handler for the product chip button. Use to open an entity/workspace switcher sheet or dropdown."},{name:"brand",type:"ReactNode",description:"Custom brand slot rendered above the nav scroll area. When provided, the product chip is not rendered."},{name:"footer",type:"ReactNode",description:"Slot pinned to the bottom of the sidebar below the scrollable nav area. Commonly used for user identity, online status, or version info."},{name:"linkComponent",type:"SidebarLinkComponentProp",description:"THE framework-router contract. Supply only the link ELEMENT TYPE; the library keeps composing every row \u2014 icon slot, label, badge, data-active/aria-current, the icon-only collapsed rail and its tooltip name \u2014 and passes it to the link as SidebarLinkProp children. Applied to every row that carries an href: top-level leaves, submenu children, collapsed-rail leaves and collapsed flyout entries. Build it with createSidebarLink(Link, 'to') for React Router / TanStack, createSidebarLink(Link) or inertiaSidebarLink(Link) from @godxjp/ui/inertia for Inertia / Next.js."},{name:"renderItem",type:"(item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode",description:"DEPRECATED \u2014 use linkComponent, or asChild on SidebarItem. Legacy escape hatch that left row CONTENT to the caller, which is how a <Link>{item.label}</Link> silently dropped every icon and badge in production. Still supported and still wins over linkComponent; rowProps now also carries the library-composed children, so spreading rowProps (or rendering rowProps.children) restores the canonical row."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Main navigation"). Required when more than one Sidebar renders at once (e.g. a docked sidebar + its mobile-drawer twin) \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections \u2014 never hand-roll nav buttons alongside or instead of the Sidebar.","DO: Add children: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The parent item's icon is required even for groups. The group auto-opens and highlights when activeId matches any descendant.","DO: Mirror the collapsed boolean between AppShell's sidebarCollapsed prop and Sidebar's collapsed prop \u2014 they must stay in sync so the shell layout grid adjusts correctly.","DO: Use the footer prop for user info or status \u2014 it is pinned below the scroll area and does not scroll away.","DO: Give every navigable item an `item.href` and let the library render it. A plain `href` becomes a real <a> (context-menu open-in-new-tab, middle-click); with `linkComponent` that same href drives your framework router's <Link>. Either way the link IS the row and the ONLY interactive element (no nested <button>). Never put a <button>/<a> inside a default row.","DO: Wire a framework router with `linkComponent` \u2014 `createSidebarLink(Link, 'to')` (React Router / TanStack), `createSidebarLink(Link)` (Next.js), or `inertiaSidebarLink(Link)` from `@godxjp/ui/inertia`. That is the WHOLE integration: you pass the element type, the library composes the row. It threads through leaves, submenu children, the collapsed rail and the collapsed flyout. When you compose rows by hand, the same contract is `<SidebarItem item={item} asChild><Link to=\u2026 /></SidebarItem>` \u2014 write NO children; the library injects the icon, label and badge.","DON'T: Use `renderItem` in new code \u2014 it is DEPRECATED. It hands you a className + active state and leaves the row CONTENT to you, so `renderItem={(item) => <Link href={\u2026}>{item.label}</Link>}` renders a row with NO icon and NO badge. That is the exact production regression that motivated `linkComponent`. If you must keep it, render `rowProps.children` (the library-composed row) instead of hand-writing `.sb-icon` / `.sb-label` spans, which are internal class names and not a public contract.","DO: Rely on route-synchronized group expansion \u2014 a group OPENS automatically whenever `activeId` moves to one of its children (e.g. after a deep-link navigation), revealing the newly-active child; users can still collapse/expand manually.","DO: Theme the nav ICON and the row/label SEPARATELY with tokens \u2014 the icon reads `--sidebar-nav-icon-foreground` (+ `-hover-`/`-active-`/`-disabled-` variants) and the row/label reads `--sidebar-nav-item-foreground` (+ `-hover-`/`-disabled-`). Defaults are unchanged (both = `hsl(var(--muted-foreground))`, hover/active = `hsl(var(--foreground))`), so setting `--sidebar-nav-icon-foreground: hsl(var(--foreground))` in your theme is all it takes to get canonical darker 16px icons beside muted labels. NEVER write a page-local `.sb-nav-item svg { color: \u2026 }` rule and never re-tint `--muted-foreground` globally to fix sidebar icons.","DO: Give every item an `icon` \u2014 it is required by SidebarItemProp and by the canonical rail (the collapsed mode is icon-only). An item whose data arrives without one no longer crashes the shell; it renders an EMPTY 16px icon slot so the row keeps its geometry and label column, but it reads as a hole in the rail.","DO: Distinguish an UNREAD count from one ADDRESSED TO THE USER with `item.badgeTone` \u2014 'neutral' (the default, the pill unchanged) versus 'destructive' for an @mention, a direct message or a failure awaiting them. It emits `data-tone=\"destructive\"` on the existing `.sb-badge` and swaps two colour tokens (`--sidebar-badge-destructive-background` / `-foreground`); the pill's min-width, radius, inline pad and font size are shared by both tones, so mention rows and unread rows stay aligned in the same column. Retune all four `--sidebar-badge-*` knobs in your theme rather than styling the pill.","DON'T: Put a `<Badge>` (or anything else that draws its own pill) inside `item.badge` to colour a count \u2014 the row ALREADY wraps whatever you pass in a `.sb-badge` pill, so you get two nested pills with two borders (measured: a 37.11x19.14 `.sb-badge` wrapping a 25.11x19.14 `<Badge>`). Pass the CONTENT only (`badge: 3`, `badge: '9+'`) and say what it MEANS with `badgeTone`.","DON'T: Change icon SIZE or row geometry through these colour knobs \u2014 icon size stays `--sidebar-nav-icon-size` (16px) and row geometry stays `--sidebar-nav-item-height` / `--sidebar-nav-item-gap` / `--sidebar-nav-item-padding-x`. The active row's fill/label keep `--sidebar-item-active-background` / `--sidebar-item-active-foreground`.","DON'T: Manage collapse state inside the Sidebar \u2014 it is stateless. Hoist the boolean to your shell/page state and pass it down via both AppShell.sidebarCollapsed and Sidebar.collapsed.","DON'T: Nest children more than one level deep \u2014 only top-level items can have children; grandchild items are not rendered."],useCases:["Admin application shell nav with grouped sections (e.g. Operations / Fulfillment / Administration) where the sidebar can be collapsed to an icon rail for more content space.","Accounting app with a collapsible 'Ledger' group containing Journal, Chart of Accounts, and Period Close sub-pages \u2014 activeId reflects the current sub-page and the group stays open automatically.","Multi-tenant SaaS where onProductClick opens an entity/legal-entity switcher sheet and product.role shows the active tenant name beneath the product logo.","Any app using AppShell where navigation must degrade gracefully to an icon-only rail on narrow viewports or via a user toggle in the Topbar.","Apps with infrequent-access admin pages (Users, Roles, Password) grouped in a dedicated section that appears below primary operations sections.","A chat / messaging rail listing channels, where most rows carry a neutral unread count and only the channels that @mentioned the user carry `badgeTone: 'destructive'` \u2014 the rail answers \"does anything need me personally?\" at a glance, without a second pill or a hand-styled dot."],related:["AppShell \u2014 the shell that hosts Sidebar in its sidebar slot and owns the sidebarCollapsed layout grid; always compose Sidebar inside AppShell, not standalone in a page.","Topbar \u2014 the horizontal bar that renders the collapse toggle (onToggleCollapsed) and its collapsed prop must mirror the sidebar's collapsed state.","PageContainer \u2014 used for page-level title/subtitle/extra/breadcrumb inside AppShell's children slot, not inside Sidebar."],example:`
|
|
121
164
|
{\`import { useState } from "react";
|
|
122
165
|
import { LayoutDashboard, FileText, Users, Shield, CreditCard, BookOpen } from "lucide-react";
|
|
123
166
|
import { Link } from "react-router-dom";
|
|
@@ -197,7 +240,7 @@ export default function Shell() {
|
|
|
197
240
|
</AppShell>
|
|
198
241
|
);
|
|
199
242
|
}\`}
|
|
200
|
-
`,storyPath:"layout/Sidebar.preview.tsx",rules:[3,6,23,31]},{name:"Topbar",group:"layout",tagline:"A PURE SLOT bar for the app shell \u2014 positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",props:[{name:"start",type:"ReactNode",defaultValue:"undefined",description:"Inline-start cluster \u2014 typically the sidebar toggle (a `Button` with a `PanelLeftClose` icon), the brand mark (an `Avatar`), and primary nav."},{name:"center",type:"ReactNode",defaultValue:"undefined",description:"Center cluster (grows + centers) \u2014 optional. e.g. a search trigger (`Button` + `Search` icon opening your command palette) or a page/entity switcher (`DropdownMenu`). \u26A0 HIDDEN AT 1100px AND BELOW by default: the slot follows `--topbar-center-compact-display` (default `none`) so it cannot cover the start breadcrumb/title or the end utilities when a 16rem sidebar is docked. A global search placed here is therefore invisible on tablets and every phone until you set `--topbar-center-compact-display: flex`."},{name:"end",type:"ReactNode",defaultValue:"undefined",description:'Inline-end cluster \u2014 settings pickers (`AppSettingPicker kind="locale"|"theme"`), a notifications `Button`, the user-menu `DropdownMenu`.'},{name:"children",type:"ReactNode",defaultValue:"undefined",description:"Escape hatch \u2014 render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored."},{name:"className",type:"string",description:"Merged onto the bar element; arbitrary props (aria-*, etc.) are forwarded."}],usage:["DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",'DO build the sidebar toggle as a `Button variant="ghost" size="icon-sm"` with a `PanelLeftClose`/`PanelLeftOpen` icon and your own `t()` aria-label, wired to AppShell\'s `sidebarCollapsed`. There is no baked toggle.',"DO put a locale/theme switcher in `end` using `AppSettingPicker` (or your own control) \u2014 icon-only vs labelled, bordered vs not, is THAT component's prop, not Topbar's. Topbar does not ship or force a language picker.","DON'T look for `product`/`project`/`onSearchOpen`/`onNotificationsOpen`/`collapsed` props \u2014 they were removed. A chrome control only exists if YOU put it in a slot, so there is never a dead dropdown / empty search with nothing behind it.","DO render Topbar inside `AppShell`'s `topbar` slot (or any `<header>`). For a non-three-cluster layout, pass `children` and lay it out yourself.","DO decide, explicitly, what happens to the `center` slot at 1100px and below. It is REMOVED there by default (`--topbar-center-compact-display: none
|
|
243
|
+
`,storyPath:"layout/Sidebar.preview.tsx",rules:[3,6,23,31]},{name:"Topbar",group:"layout",tagline:"A PURE SLOT bar for the app shell \u2014 positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",props:[{name:"start",type:"ReactNode",defaultValue:"undefined",description:"Inline-start cluster \u2014 typically the sidebar toggle (a `Button` with a `PanelLeftClose` icon), the brand mark (an `Avatar`), and primary nav."},{name:"center",type:"ReactNode",defaultValue:"undefined",description:"Center cluster (grows + centers) \u2014 optional. e.g. a search trigger (`Button` + `Search` icon opening your command palette) or a page/entity switcher (`DropdownMenu`). \u26A0 HIDDEN AT 1100px AND BELOW by default: the slot follows `--topbar-center-compact-display` (default `none`) so it cannot cover the start breadcrumb/title or the end utilities when a 16rem sidebar is docked. A global search placed here is therefore invisible on tablets and every phone until you set `--topbar-center-compact-display: flex`."},{name:"end",type:"ReactNode",defaultValue:"undefined",description:'Inline-end cluster \u2014 settings pickers (`AppSettingPicker kind="locale"|"theme"`), a notifications `Button`, the user-menu `DropdownMenu`.'},{name:"children",type:"ReactNode",defaultValue:"undefined",description:"Escape hatch \u2014 render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored."},{name:"className",type:"string",description:"Merged onto the bar element; arbitrary props (aria-*, etc.) are forwarded."}],usage:["DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",'DO build the sidebar toggle as a `Button variant="ghost" size="icon-sm"` with a `PanelLeftClose`/`PanelLeftOpen` icon and your own `t()` aria-label, wired to AppShell\'s `sidebarCollapsed`. There is no baked toggle.',"DO put a locale/theme switcher in `end` using `AppSettingPicker` (or your own control) \u2014 icon-only vs labelled, bordered vs not, is THAT component's prop, not Topbar's. Topbar does not ship or force a language picker.","DON'T look for `product`/`project`/`onSearchOpen`/`onNotificationsOpen`/`collapsed` props \u2014 they were removed. A chrome control only exists if YOU put it in a slot, so there is never a dead dropdown / empty search with nothing behind it.","DO render Topbar inside `AppShell`'s `topbar` slot (or any `<header>`). For a non-three-cluster layout, pass `children` and lay it out yourself.","DO decide, explicitly, what happens to the `center` slot at 1100px and below. It is REMOVED there by default (`--topbar-center-compact-display: none`) so it cannot cover the start or end clusters when a 16rem sidebar is docked \u2014 which also means a global search trigger in `center` is gone on tablets AND phones. This default arrived in 18.6.0 and changed behaviour for consumers who touched nothing but their lockfile. If your center content already has a compact presentation (an icon-only search trigger), opt back in globally with `:root { --topbar-center-compact-display: flex; }`; if it does not, move the trigger into `end` for compact widths. Never re-create either behaviour with a page-local media query.","DO rely on the built-in shrink contract instead of hand-tuning widths: the bar never exceeds its shell allocation, `start` shrinks first and `center` yields its whole box, each cluster CLIPS its own overflow (so a long tenant/brand string can never spill over a sibling or leak a horizontal document scroll), and `end` keeps its natural width anchored inline-end \u2014 the locale picker and user menu stay visible at 1024px with a 16rem sidebar. If a label must degrade gracefully rather than be cut, give THAT element `truncate`/`text-overflow` yourself; don't add `overflow`/`flex` overrides to the slots.",'KNOW the shrink contract reaches only the LAST child of `start` \u2014 and `Button` ships `shrink-0`, so any Button you put mid-slot (the classic entity switcher, with a brand mark before it and a screen title after) keeps its full width while the cluster clips it. Clipped, but still focusable: a keyboard user tabs to a control they cannot see (SC 2.4.7). Give such a control `min-w-11 flex-1` at compact widths so it takes the leftover room without dropping under the 44px touch floor, and wrap its label in `<span className="truncate">`. Budget the `end` cluster too \u2014 it is `flex: 0 0 auto`, so an ambient status chip there is subtracted from `start` before `start` gets a say (a 93px environment Badge left `start` 25px of a 198px bar at 320). Hide ambient chips below `sm`.'],useCases:["Admin shell: `start` = sidebar toggle + brand mark (`Avatar`) + an entity switcher (`DropdownMenu` around a `Button`); `center` = a `Button` search trigger; `end` = `AppSettingPicker` (locale) + a notifications `Button` + a user `DropdownMenu`.","Minimal shell (no search, no notifications): pass only `start` (brand mark) and `end` (user menu). Nothing else renders \u2014 no empty chrome.","Marketing / docs header: pass `children` with a fully custom flex layout when the three-cluster model doesn't fit."],related:["AppShell \u2014 place Topbar in its `topbar` slot. AppShell also exposes its own `logo`/`topbarLeft`/`topbarRight` slots if you don't want a separate Topbar at all.",'Avatar \u2014 the brand mark for the `start` slot (use `className="rounded-md"` for a square-ish product glyph).',"AppSettingPicker \u2014 locale/theme/timezone/currency picker; the consumer drops it into `end`. Its appearance (icon-only, labelled, bordered) is configured on IT, not on Topbar.","DropdownMenu \u2014 wrap a `Button` to build an entity switcher or user menu yourself, then place it in a slot."],example:`import { Topbar, AppShell } from "@godxjp/ui/layout";
|
|
201
244
|
import { Button } from "@godxjp/ui/general";
|
|
202
245
|
import { Avatar, AvatarFallback } from "@godxjp/ui/data-display";
|
|
203
246
|
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
@@ -264,7 +307,7 @@ import { PanelLeftClose, Search } from "lucide-react";
|
|
|
264
307
|
// Collapsible rail \u2014 closing with \`null\` does NOT remount <MessageList />.
|
|
265
308
|
<SplitPane asideLabel="Thread" aside={threadOpen ? <Thread /> : null}>
|
|
266
309
|
<MessageList />
|
|
267
|
-
</SplitPane>`,storyPath:"layout/SplitPane.stories.tsx",rules:[24]},{name:"ErrorSurface",group:"layout",tagline:'Package-owned semantic exception surface for 400 / 403 / 404 / 500 / 503. `mode` is the SHELL CONTRACT: "application" renders the body you put inside the AppShell the route already provides (chrome PRESERVED, never reconstructed); "system" owns the whole page via CenteredShell align="center" (package-owned 1440/1024/390 geometry). Exactly one recovery action, plus semantic request-id / permission / organization / maintenance metadata slots.',props:[{name:"mode",type:'"application" | "system"',required:!0,description:`The shell contract, not a skin. "application" (400/403/404) renders ONLY the surface block \u2014 put it in AppShell's children (usually inside a PageContainer) so the sidebar/topbar/breadcrumb stay mounted; it cannot build chrome, because nav data and the user menu are consumer-owned. "system" (500/503) renders the whole page: CenteredShell align="center", so no consumer min-h-dvh / flex class / media query.`},{name:"status",type:"400 | 403 | 404 | 500 | 503",required:!0,description:`The HTTP status (a NUMBER, not a string). It is the input that drives the default icon (TriangleAlert 400 \xB7 ShieldAlert 403 \xB7 SearchX 404 \xB7 ServerCrash 500 \xB7 Wrench 503) and tone (warning 400/403/503 \xB7 muted 404 \xB7 destructive 500). Also rendered as the compact tabular status code, announced as 'HTTP status 403' rather than the cardinal number. 400 is the malformed-request page \u2014 a route reached with parameters the server refuses to interpret (a bad id, a missing launch parameter) \u2014 and belongs in mode="application" like 403/404.`},{name:"title",type:"ReactNode",required:!0,description:"Headline. Consumer-owned copy from the APP's own t() \u2014 @godxjp/ui ships no product text."},{name:"action",type:"ReactNode",required:!0,description:"The ONE recovery action (a Button, or Button asChild wrapping a router Link). A single slot IS the enforcement: pass a fragment with two buttons and only the first renders, with a development-time console error. Support contact goes in `description`, never in a second CTA."},{name:"description",type:"ReactNode",description:"Supporting sentence under the title. Put support-contact guidance here. Its measure is owned by --empty-state-description-max-width."},{name:"requestId",type:"string",description:"Support correlation id, rendered as a <dt>/<dd> metadata row with a localized 'Request ID' label and a mono/tabular value so it can be read out or copied accurately. Pass the bare id \u2014 never write it into `description` as prose."},{name:"permission",type:"ReactNode",description:"The permission/role the viewer is missing (403). Pass the bare permission name ('reports.view'); the localized 'Required permission' label is the surface's."},{name:"organization",type:"ReactNode",description:"The organization/tenant the failed request was scoped to. Together with `permission` this is what distinguishes a wrong-workspace 403 from a missing-role 403."},{name:"maintenance",type:"{ start: string; end?: string; timeZone?: string; progress?: number }",description:"Planned-outage timing (503). `start`/`end` are ISO-8601 INSTANTS and `timeZone` an IANA id: the surface formats them with Intl.DateTimeFormat(locale).formatRange() and keeps the ISO value in <time dateTime>. NEVER pass a pre-formatted '18:00 - 20:00 JST' string. `progress` (0-100) adds a labelled Progress meter named via Intl.NumberFormat percent style; it is SERVER-SENT on purpose \u2014 deriving it from the client clock breaks SSR hydration."},{name:"icon",type:"ComponentType<{ className?: string }>",description:"Override the status-derived icon. Use only when the product has a truer glyph for the failure, never to change perceived severity."},{name:"tone",type:'"muted" | "info" | "success" | "warning" | "destructive"',description:"Override the status-derived tone (same union as EmptyState.tone). Defaults: 400/403/503 warning \xB7 404 muted \xB7 500 destructive."},{name:"titleLevel",type:"1 | 2 | 3 | 4",description:"Semantic heading level of `title`. Defaults to 2 in application mode (a PageContainer h1 sits above) and 1 in system mode (the surface IS the page). Choose it to keep the outline valid, never for size."},{name:"brand",type:"ReactNode",description:"system mode ONLY \u2014 brand slot above the status code (a Logo). Ignored in application mode, where the shell already shows the brand."},{name:"footer",type:"ReactNode",description:"system mode ONLY \u2014 the page footer (contentinfo): copyright, status page link, locale switch."},{name:"width",type:'"sm" | "md" | "lg"',defaultValue:'"sm"',description:"system mode ONLY \u2014 measure of the centred column (the CenteredShell width tier)."},{name:"id",type:"string",description:"Root element id."},{name:"className",type:"string",description:"Root class override (rarely needed)."}],usage:["DO use ErrorSurface for ANY 400 / 403 / 404 / 500 / 503 page. It is a real import from @godxjp/ui/layout \u2014 never hand-compose an error page from AuthShell + a generic Card, and never add a consumer-local `.canonical-auth-card`-style class (that workaround IS the
|
|
310
|
+
</SplitPane>`,storyPath:"layout/SplitPane.stories.tsx",rules:[24]},{name:"ErrorSurface",group:"layout",tagline:'Package-owned semantic exception surface for 400 / 403 / 404 / 500 / 503. `mode` is the SHELL CONTRACT: "application" renders the body you put inside the AppShell the route already provides (chrome PRESERVED, never reconstructed); "system" owns the whole page via CenteredShell align="center" (package-owned 1440/1024/390 geometry). Exactly one recovery action, plus semantic request-id / permission / organization / maintenance metadata slots.',props:[{name:"mode",type:'"application" | "system"',required:!0,description:`The shell contract, not a skin. "application" (400/403/404) renders ONLY the surface block \u2014 put it in AppShell's children (usually inside a PageContainer) so the sidebar/topbar/breadcrumb stay mounted; it cannot build chrome, because nav data and the user menu are consumer-owned. "system" (500/503) renders the whole page: CenteredShell align="center", so no consumer min-h-dvh / flex class / media query.`},{name:"status",type:"400 | 403 | 404 | 500 | 503",required:!0,description:`The HTTP status (a NUMBER, not a string). It is the input that drives the default icon (TriangleAlert 400 \xB7 ShieldAlert 403 \xB7 SearchX 404 \xB7 ServerCrash 500 \xB7 Wrench 503) and tone (warning 400/403/503 \xB7 muted 404 \xB7 destructive 500). Also rendered as the compact tabular status code, announced as 'HTTP status 403' rather than the cardinal number. 400 is the malformed-request page \u2014 a route reached with parameters the server refuses to interpret (a bad id, a missing launch parameter) \u2014 and belongs in mode="application" like 403/404.`},{name:"title",type:"ReactNode",required:!0,description:"Headline. Consumer-owned copy from the APP's own t() \u2014 @godxjp/ui ships no product text."},{name:"action",type:"ReactNode",required:!0,description:"The ONE recovery action (a Button, or Button asChild wrapping a router Link). A single slot IS the enforcement: pass a fragment with two buttons and only the first renders, with a development-time console error. Support contact goes in `description`, never in a second CTA."},{name:"description",type:"ReactNode",description:"Supporting sentence under the title. Put support-contact guidance here. Its measure is owned by --empty-state-description-max-width."},{name:"requestId",type:"string",description:"Support correlation id, rendered as a <dt>/<dd> metadata row with a localized 'Request ID' label and a mono/tabular value so it can be read out or copied accurately. Pass the bare id \u2014 never write it into `description` as prose."},{name:"permission",type:"ReactNode",description:"The permission/role the viewer is missing (403). Pass the bare permission name ('reports.view'); the localized 'Required permission' label is the surface's."},{name:"organization",type:"ReactNode",description:"The organization/tenant the failed request was scoped to. Together with `permission` this is what distinguishes a wrong-workspace 403 from a missing-role 403."},{name:"maintenance",type:"{ start: string; end?: string; timeZone?: string; progress?: number }",description:"Planned-outage timing (503). `start`/`end` are ISO-8601 INSTANTS and `timeZone` an IANA id: the surface formats them with Intl.DateTimeFormat(locale).formatRange() and keeps the ISO value in <time dateTime>. NEVER pass a pre-formatted '18:00 - 20:00 JST' string. `progress` (0-100) adds a labelled Progress meter named via Intl.NumberFormat percent style; it is SERVER-SENT on purpose \u2014 deriving it from the client clock breaks SSR hydration."},{name:"icon",type:"ComponentType<{ className?: string }>",description:"Override the status-derived icon. Use only when the product has a truer glyph for the failure, never to change perceived severity."},{name:"tone",type:'"muted" | "info" | "success" | "warning" | "destructive"',description:"Override the status-derived tone (same union as EmptyState.tone). Defaults: 400/403/503 warning \xB7 404 muted \xB7 500 destructive."},{name:"titleLevel",type:"1 | 2 | 3 | 4",description:"Semantic heading level of `title`. Defaults to 2 in application mode (a PageContainer h1 sits above) and 1 in system mode (the surface IS the page). Choose it to keep the outline valid, never for size."},{name:"brand",type:"ReactNode",description:"system mode ONLY \u2014 brand slot above the status code (a Logo). Ignored in application mode, where the shell already shows the brand."},{name:"footer",type:"ReactNode",description:"system mode ONLY \u2014 the page footer (contentinfo): copyright, status page link, locale switch."},{name:"width",type:'"sm" | "md" | "lg"',defaultValue:'"sm"',description:"system mode ONLY \u2014 measure of the centred column (the CenteredShell width tier)."},{name:"id",type:"string",description:"Root element id."},{name:"className",type:"string",description:"Root class override (rarely needed)."}],usage:["DO use ErrorSurface for ANY 400 / 403 / 404 / 500 / 503 page. It is a real import from @godxjp/ui/layout \u2014 never hand-compose an error page from AuthShell + a generic Card, and never add a consumer-local `.canonical-auth-card`-style class (that workaround IS the regression).",'DO put mode="application" INSIDE the shell the route already renders: <AppShell \u2026><PageContainer \u2026><ErrorSurface mode="application" \u2026/></PageContainer></AppShell>. The surface returns only its own block on purpose, so the sidebar/topbar/breadcrumb survive and the user can navigate away.','DO use mode="system" for 500/503 and pass NOTHING for geometry \u2014 the surface renders CenteredShell align="center" itself. A className="min-h-dvh flex items-center" is always wrong.',"DO pass ONE action. A second CTA is dropped with a development error; put 'contact support' in `description`.","DO pass `maintenance` as ISO-8601 instants + an IANA timeZone and let Intl format it. A hand-built window string cannot localize, and a client-derived `progress` breaks SSR hydration.","DO let `status` pick the icon and tone. Override them only for a truer product glyph \u2014 never to recolour a status into a different severity.","DO NOT retune the geometry with a className: --error-surface-max-width, --error-surface-gap, --error-surface-padding-block(-compact), --error-surface-meta-* and --error-surface-progress-max-width are the knobs. The metadata divider defaults to `none` (rule #44); opt in with --error-surface-meta-border.","DO NOT write the request id, the missing permission or the maintenance window into `description` as prose \u2014 each is a semantic <dt>/<dd> slot, and prose loses the label\u2194value relationship for a screen reader.","DO NOT use AuthShell for an error page: it is the UNAUTHENTICATED root and imposes auth-card geometry."],useCases:['Inertia/Laravel exception page (SCR-006): one Error.tsx receives `status` from Handler::render() and renders <ErrorSurface mode={status >= 500 ? "system" : "application"} status={status} \u2026 />, with Error.layout keeping the authenticated shell for 403/404 only.','Forbidden report inside the console: <ErrorSurface mode="application" status={403} permission="reports.view" organization="Acme KK" action={<Button asChild><Link href="/">Back</Link></Button>} /> inside the existing AppShell + PageContainer.','Planned maintenance page: <ErrorSurface mode="system" status={503} maintenance={{ start, end, timeZone: "Asia/Tokyo", progress: 40 }} brand={<Logo glyph="G" />} footer={\u2026} />.','Unexpected failure with a support correlation id: <ErrorSurface mode="system" status={500} requestId="01J9Z0\u2026" action={<Button onClick={reload}>Reload</Button>} />.'],related:['CenteredShell \u2014 the viewport-centred page shell that ErrorSurface renders internally for mode="system". Use it directly only for a standalone surface that is NOT one of the four HTTP statuses.','AppShell + PageContainer \u2014 what you wrap around a mode="application" surface. ErrorSurface never builds them: nav sections and the user menu are consumer-owned data.',"EmptyState \u2014 the zero-state primitive ErrorSurface composes for its icon/title/description/action body. Use it directly for an empty LIST or an empty section, not for an HTTP exception page.","AlertQueryError / humanError \u2014 inline, in-page query failure feedback. ErrorSurface is the whole PAGE; an inline alert is the right pick when the surrounding page still works.","Progress \u2014 the meter ErrorSurface renders for maintenance.progress."],example:`import { Button, Logo, Text } from "@godxjp/ui/general";
|
|
268
311
|
import { AppShell, ErrorSurface, PageContainer, Sidebar } from "@godxjp/ui/layout";
|
|
269
312
|
|
|
270
313
|
// 403 \u2014 APPLICATION mode: the body inside the shell the route ALREADY renders.
|
|
@@ -388,7 +431,7 @@ import { Card, CardContent } from "@godxjp/ui/data-display";
|
|
|
388
431
|
<Activity variant="bar" tone="info" label={t("sync.running")} />
|
|
389
432
|
|
|
390
433
|
// a reconnect notice that must actually be heard, announced once and politely
|
|
391
|
-
<Activity announce="polite" tone="warning" label={t("realtime.reconnecting")} />`,storyPath:"general/Activity.stories.tsx",rules:[]},{name:"DataTable",group:"data-display",tagline:"The one TanStack-powered compound admin list \u2014 sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack).",props:[{name:"data",type:"T[]",required:!0,description:"Array of row data. When empty and loading is false, a built-in EmptyState renders automatically inside the table body \u2014 no external guard needed."},{name:"columns",type:"ColumnDef<T>[]",required:!0,description:"Lean column definitions (adapted to TanStack internally \u2014 `meta.lean` is the declared home for every custom column option, so `priority` needs no second TanStack channel). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean; enableHiding?: boolean; pin?: 'end'; priority?: 'primary'|'secondary'|'meta'|'actions' }. priority is the column-priority contract read by preset=\"action-collection\" \u2014 DataTable stamps it as data-priority on the <th> AND every <td> of the column, so the preset can allocate the narrow-frame measure; leave the free-text column unmarked (it takes the remaining space), and prefer priority over width under the preset because an explicit width utility wins the cascade and defeats the measure. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow \u2014 pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' \u2014 an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header)."},{name:"getRowId",type:"(row: T) => string",defaultValue:"(row) => String(row.id)",description:"Extracts a stable unique string key per row. Required when selectable is true or rows lack an 'id' field. Falls back to row.id cast to string."},{name:"selectable",type:"boolean",defaultValue:"false",description:"Adds a checkbox column and a SelectAll header checkbox. Use with selected + onSelectChange for controlled selection, or omit both for uncontrolled."},{name:"selected",type:"Set<string>",description:"Controlled set of selected row IDs. Pair with onSelectChange. Omit for uncontrolled."},{name:"onSelectChange",type:"(next: Set<string>) => void",description:"Called with the full new selection set after any checkbox interaction."},{name:"onRowClick",type:"(row: T) => void",description:"Makes rows clickable for navigation. Row click is suppressed when the user clicks an interactive descendant (button, a, input, select, textarea, [role=menuitem])."},{name:"density",type:"'compact' | 'default' | 'comfortable'",defaultValue:"'compact'",description:"Controlled row density across all three tiers (compact 28 / default 36 / comfortable 48) \u2014 drive it from a \u8868\u793A\u5BC6\u5EA6 radio. Omit to let DataTable manage it internally (DataTable.DensityToggle flips compact\u2194comfortable)."},{name:"onDensityChange",type:"(density: 'compact' | 'default' | 'comfortable') => void",description:"Called when the user toggles density. Only needed when density is controlled."},{name:"striped",type:"boolean",defaultValue:"false",description:"Zebra-stripe the body rows (even rows get a subtle muted fill)."},{name:"hoverable",type:"boolean",defaultValue:"false",description:"Highlight a row on hover even when it is not clickable. onRowClick already implies hover; use this for read-only tables that still want the hover affordance."},{name:"stickyHeader",type:"boolean",defaultValue:"true",description:"Pin the header to the top while the body scrolls (\u30D8\u30C3\u30C0\u8FFD\u5F93). Set false to let it scroll away with the rows."},{name:"preset",type:"'default' | 'action-collection'",defaultValue:"'default'",description:"Named collection contract \u2014 the SAME preset the Table primitive owns, forwarded to the table DataTable renders. 'default' emits NO attribute and matches no selector, so an existing DataTable is byte-identical. 'action-collection' is the canonical dense approval/action queue: below collapseBelow the desktop INTRINSIC column widths give way to the token-owned column-PRIORITY measures (--table-action-collection-*) under table-layout: fixed, cells wrap, and the bordered surface drops its --table-surface-min-inline-size floor \u2014 so requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions all stay inside a 390px frame with no horizontal scroll. Mark each column with `priority` on its ColumnDef. Semantics are untouched (no display change, no role rewriting, no card swap), so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440. Measured: table 1182 / 766 / 388px at 1440 / 1024 / 390, document scrollWidth === clientWidth at every width, LTR and RTL."},{name:"collapseBelow",type:"'sm' | 'md' | 'lg' | 'xl'",defaultValue:"'sm'",description:`Step at which preset="action-collection" switches to the compact priority measures, measured against the TABLE'S OWN container (a container query on sm 40rem \xB7 md 48rem \xB7 lg 64rem \xB7 xl 80rem), not the viewport \u2014 a table inside a master rail collapses before the page does. Ignored while preset is 'default'.`},{name:"sort",type:"{ key: string; direction: 'asc' | 'desc' }",description:"Active sort state (controlled/server surface). When provided alongside onSortChange, sortable columns show directional arrow icons and clicking the active column twice clears sort (calls onSortChange(undefined)). Omit both sort and onSortChange to sort client-side via TanStack."},{name:"onSortChange",type:"(sort: { key: string; direction: 'asc' | 'desc' } | undefined) => void",description:"Called when a sortable column header is clicked. Receives undefined when sort is cleared (third click on same column). Providing sort or onSortChange opts into the controlled (server) sort surface; omit both and the table sorts client-side via TanStack."},{name:"globalFilter / onGlobalFilterChange",type:"string / (next: string) => void",description:"Global search term surfaced by DataTable.Search. Omit both for client-side filtering; pass them to drive a server query (with manualFiltering)."},{name:"pagination / onPaginationChange / rowCount",type:"{ pageIndex: number; pageSize: number } / OnChangeFn / number",description:"Numbered-pagination state surfaced by DataTable.Pagination (page-size form). For server pagination pass all three (rowCount = total) with manualPagination; omit for client pagination."},{name:"columnVisibility / onColumnVisibilityChange",type:"VisibilityState / OnChangeFn<VisibilityState>",description:"Column show/hide state surfaced by DataTable.ViewOptions ('set view'). Internal if omitted."},{name:"manualSorting / manualFiltering / manualPagination",type:"boolean",defaultValue:"false",description:"Default false so the simple data+columns case sorts/filters/paginates in-browser. Set the relevant flag true and drive the matching state from your query for server-side behaviour."},{name:"loading",type:"boolean",defaultValue:"false",description:"When true, swaps the body for SHAPED skeleton rows rendered inside the table's own grid (one border, aligned columns) \u2014 never a separate <SkeletonTable> in a Card (that double-borders). With React Query keepPreviousData, drive this off isPlaceholderData (pagination/search) || isLoading (first load), NOT isLoading alone. Suppresses the empty state while true."},{name:"empty",type:"ReactNode",description:"Custom content rendered inside the table body when data is empty and loading is false. Defaults to a built-in EmptyState with a localised 'No data' message. Pass a custom <EmptyState title='...' description='...' action={...}/> to tailor the message."},{name:"error",type:"ReactNode",description:"FAILURE state. Pass error={isError} \u2014 `true` renders the built-in localized destructive EmptyState ('Couldn't load this list') announced with role='alert'; any other node REPLACES that copy (e.g. an <Alert> carrying an error code + request id). `false`/`undefined` means the read succeeded. NEVER pass a raw Error object (it is not renderable)."},{name:"denied",type:"ReactNode",description:"PERMISSION-DENIED state \u2014 the read was REFUSED (403), not failed. `true` renders the built-in localized warning EmptyState ('You don't have access to this list') with NO retry, announced politely (aria-live) because a permission boundary is expected information, not a fault. Takes precedence over `error`. Any other node replaces the copy."},{name:"onRetry",type:"() => void",description:"Retry handler surfaced as a Retry button inside the BUILT-IN error state only. Omit it to render the error without a retry affordance; it is intentionally never offered for `denied` (repeating a 403 cannot succeed)."},{name:"className",type:"string",description:"Extra classes applied to the root wrapper div (ui-data-table-root)."},{name:"children",type:"ReactNode",description:"Compound sub-parts: DataTable.Toolbar, DataTable.Search (global filter), DataTable.ViewOptions (column show/hide), DataTable.SelectAll, DataTable.BulkActions (ReactNode children OR a (count)=>node render-prop), DataTable.DensityToggle, DataTable.Pagination (cursor first/next when given cursor+hasMore+onChange, else numbered page-size form), DataTable.RowActions (kebab trigger), DataTable.Content. If no DataTable.Content is present in children, one is auto-rendered."}],usage:["DO pass loading={isFetching} during data fetches \u2014 it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.","DO NOT add a data.length===0 conditional around DataTable. When data is empty and loading is false, the built-in EmptyState renders automatically. Pass empty={<EmptyState title='...'/>} only when you need a custom message.","SIX STATES, ZERO HAND-ROLLING: loading (`loading`), empty (automatic / `empty`), error (`error` + optional `onRetry`), denied (`denied`), pagination (`DataTable.Pagination`), row actions (`DataTable.RowActions`). Wire them straight off the query \u2014 `<DataTable loading={isPending} error={isError} denied={status === 403} onRetry={refetch} \u2026/>` \u2014 and never branch the page around the table to render your own alert/empty/forbidden block. Precedence is loading > denied > error > empty > rows, so exactly one state ever shows.","DO provide getRowId when selectable is true or when rows do not have a string/number 'id' field \u2014 the default falls back to row.id and silently returns '' for missing IDs, which breaks selection.","DO use DataTable.Toolbar as the immediate child that wraps search/filter controls on the left and DataTable.DensityToggle/action buttons on the right. DataTable.BulkActions inside the toolbar auto-hides when selection count is 0; it accepts either plain ReactNode children (built-in 'N selected' status bar) or a (count)=>node render-prop (you own the whole bar).","DO reach for the grid chrome (DataTable.Search, DataTable.ViewOptions, DataTable.Pagination pageSizeOptions) when you need global search, a column 'set view' picker, or numbered pagination \u2014 these are the merged former-DataGrid features, now on the one DataTable. Drive them client-side by default; pass the matching state + manual* flag for a server query.","DataTable.Pagination OWNS ITS OWN INSET. The footer is a self-contained slot: it declares `padding-block` + `padding-inline` from `--table-pagination-padding-{y,x}` (block default = `--space-stack-sm`, inline default = `--table-cell-space-x`, so the 'rows per page' label lands on the same optical axis as the first column's text). Before the fix it declared `padding-top` only, so inside the documented flush container (`<Card><CardContent flush><DataTable/>`) the label and page-size Select sat flush against the container edge and border. DON'T ship a local `.ui-data-table-pagination { padding: \u2026 }` override in an app \u2014 retune the two tokens in your theme instead.",'RESPONSIVE APPROVAL / ACTION QUEUE: reach for `preset="action-collection"` when a dense five-column queue (requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions) must stay readable at 390px, and give every column a `priority` on its ColumnDef \u2014 `primary` (the row subject), `secondary` (its target), `meta` (a timestamp/id), `actions` (the row-action affordance, whose measure is reserved FIRST so it can never be pushed off-screen). Leave the free-text column unmarked; it takes the remaining space. This is the SAME contract, the SAME `--table-action-collection-*` tokens and the SAME container query as `Table preset="action-collection"` \u2014 there is no separate DataTable family. Never add a consumer width, a hidden column, `hiddenOnMobile` or a page-local breakpoint to make a table fit: retune the tokens instead.',"DON'T set `width` on a column that also has a `priority` \u2014 an explicit width utility wins the cascade over the priority measure and re-opens the horizontal scroll. Under the preset, `pin: 'end'` is also redundant: nothing scrolls sideways, so the actions column is already in frame.",'The DataTable surface\'s narrow-viewport width floor is `--table-surface-min-inline-size` (default 640px, released at the `sm` viewport step). It used to be a hard-coded `min-w-[640px] sm:min-w-0` utility pair on the surface \u2014 the literal that forced the horizontal scroll at 390. Retune (or zero) the token in your theme; `preset="action-collection"` already opts out of it.',"DO use ColumnDef.render for custom cell content (Badge, Link, RowActions). For plain string/number fields render can be omitted \u2014 DataTable falls back to String(row[key]).","DO give every visually-empty column an accessible header via `ariaLabel` \u2014 a row-actions column (`header: ''`, `pin: 'end'`) sets `ariaLabel: t('actions')`, so screen readers announce the column and axe reports no `empty-table-header`. DataTable dev-warns any column that renders a `<th>` with neither visible text nor an `ariaLabel`. The selection column added by `selectable` is already named by its SelectAll checkbox \u2014 no `ariaLabel` needed there.","COLUMN SEMANTICS + KEYBOARD: a `sortable` header renders as a real <button> inside the <th> with `aria-sort` (ascending/descending/none) on the <th>; it is Tab-reachable and toggles asc \u2192 desc \u2192 cleared on Enter/Space/click. A selection column exposes a header 'select all' Checkbox (indeterminate when a subset is selected) and a per-row Checkbox, each keyboard-operable with Space. An action column is visually empty but carries an `ariaLabel`; its per-row controls (kebab menu / buttons) own their own accessible names and keyboard behavior. Row click (`onRowClick`) is suppressed when the user activates an interactive descendant.","DO NOT nest DataTable.Content in a conditional \u2014 it is already guarded internally. If you need to override the table body slot, drop exactly one <DataTable.Content /> in children; DataTable auto-detects it by displayName and skips the default."],useCases:["Admin list pages (invoices, customers, orders, accounts) where rows are clickable for detail navigation via onRowClick.","Bulk-action workflows (e.g. mark invoices paid, export selected rows) \u2014 use selectable + DataTable.BulkActions to show contextual action buttons only when something is selected.","Server-side sorted tables: pass sort + onSortChange and update the data prop after the API call; DataTable renders asc/desc/neutral icons on the header automatically.","Cursor-paginated lists: add DataTable.Pagination with cursor + hasMore + onChange inside children to get First/Next navigation without offset arithmetic. For page-size + numbered prev/next instead, use DataTable.Pagination with pageSizeOptions (no cursor/onChange) driven by the internal TanStack pagination.","Full grid screens (global search + column 'set view' + numbered pagination): compose DataTable.Search, DataTable.ViewOptions, DataTable.DensityToggle in the toolbar and DataTable.Pagination pageSizeOptions={[\u2026]} \u2014 client-side by default, or server-side by passing globalFilter/pagination/sort state with the matching manual* flag.",'Responsive admin tables where lower-priority columns (e.g. internal IDs, dates) should collapse below mobile breakpoints \u2014 set hiddenOnMobile: true on those ColumnDef entries. When the columns must all stay DISCOVERABLE at 390 instead (an approval/action queue), use preset="action-collection" + ColumnDef.priority rather than hiding anything.',`Access-approval / action queues at 390px (SCR-105, gh#253): preset="action-collection" + a priority on each ColumnDef keeps requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions inside the initial narrow frame with no page-local CSS, no consumer width, no hidden column and no horizontal scroll \u2014 see the DataTable 'Approval queue' example page.`,"Loading skeletons during initial page load or filter change: set loading={true} alongside an empty data={[]} to show the loading row without flashing an empty state."],related:["Table \u2014 raw primitive (TableHeader/TableBody/TableRow/TableCell). Use DataTable instead; only reach for Table directly when you need a non-standard layout that DataTable cannot express.","SkeletonTable \u2014 standalone skeleton placeholder rendered before any DataTable mounts (e.g. in a Suspense fallback or deferred-prop skeleton slot). DataTable.loading covers in-table loading; SkeletonTable covers pre-mount skeletons.","EmptyState \u2014 standalone empty state for non-table lists. DataTable already embeds EmptyState in its body; only use bare EmptyState for card content, non-tabular lists, or zero-state pages outside a DataTable.","LineChart / BarChart / AreaChart / PieChart (@godxjp/ui/charts) \u2014 when the SHAPE or trend of aggregated data matters more than exact per-row figures, visualize it with a chart instead of (or alongside) the table; keep DataTable when users need to read, sort, or act on individual rows.","DataState / InfiniteQueryState \u2014 TanStack Query lifecycle widgets from @godxjp/ui/query. Prefer these over DataTable when your list is driven by useQuery/useInfiniteQuery and you want automatic skeleton/empty/error handling at the query level rather than at the table level."],example:`import { useState } from "react";
|
|
434
|
+
<Activity announce="polite" tone="warning" label={t("realtime.reconnecting")} />`,storyPath:"general/Activity.stories.tsx",rules:[]},{name:"DataTable",group:"data-display",tagline:"The one TanStack-powered compound admin list \u2014 sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack).",props:[{name:"data",type:"T[]",required:!0,description:"Array of row data. When empty and loading is false, a built-in EmptyState renders automatically inside the table body \u2014 no external guard needed."},{name:"columns",type:"ColumnDef<T>[]",required:!0,description:"Lean column definitions (adapted to TanStack internally \u2014 `meta.lean` is the declared home for every custom column option, so `priority` needs no second TanStack channel). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean; enableHiding?: boolean; pin?: 'end'; priority?: 'primary'|'secondary'|'meta'|'actions' }. priority is the column-priority contract read by preset=\"action-collection\" \u2014 DataTable stamps it as data-priority on the <th> AND every <td> of the column, so the preset can allocate the narrow-frame measure; leave the free-text column unmarked (it takes the remaining space), and prefer priority over width under the preset because an explicit width utility wins the cascade and defeats the measure. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow \u2014 pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' \u2014 an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header)."},{name:"getRowId",type:"(row: T) => string",defaultValue:"(row) => String(row.id)",description:"Extracts a stable unique string key per row. Required when selectable is true or rows lack an 'id' field. Falls back to row.id cast to string."},{name:"selectable",type:"boolean",defaultValue:"false",description:"Adds a checkbox column and a SelectAll header checkbox. Use with selected + onSelectChange for controlled selection, or omit both for uncontrolled."},{name:"selected",type:"Set<string>",description:"Controlled set of selected row IDs. Pair with onSelectChange. Omit for uncontrolled."},{name:"onSelectChange",type:"(next: Set<string>) => void",description:"Called with the full new selection set after any checkbox interaction."},{name:"onRowClick",type:"(row: T) => void",description:"Makes rows clickable for navigation. Row click is suppressed when the user clicks an interactive descendant (button, a, input, select, textarea, [role=menuitem])."},{name:"density",type:"'compact' | 'default' | 'comfortable'",defaultValue:"'compact'",description:"Controlled row density across all three tiers (compact 28 / default 36 / comfortable 48) \u2014 drive it from a \u8868\u793A\u5BC6\u5EA6 radio. Omit to let DataTable manage it internally (DataTable.DensityToggle flips compact\u2194comfortable)."},{name:"onDensityChange",type:"(density: 'compact' | 'default' | 'comfortable') => void",description:"Called when the user toggles density. Only needed when density is controlled."},{name:"striped",type:"boolean",defaultValue:"false",description:"Zebra-stripe the body rows (even rows get a subtle muted fill)."},{name:"hoverable",type:"boolean",defaultValue:"false",description:"Highlight a row on hover even when it is not clickable. onRowClick already implies hover; use this for read-only tables that still want the hover affordance."},{name:"stickyHeader",type:"boolean",defaultValue:"true",description:"Pin the header to the top while the body scrolls (\u30D8\u30C3\u30C0\u8FFD\u5F93). Set false to let it scroll away with the rows."},{name:"preset",type:"'default' | 'action-collection'",defaultValue:"'default'",description:"Named collection contract \u2014 the SAME preset the Table primitive owns, forwarded to the table DataTable renders. 'default' emits NO attribute and matches no selector, so an existing DataTable is byte-identical. 'action-collection' is the canonical dense approval/action queue: below collapseBelow the desktop INTRINSIC column widths give way to the token-owned column-PRIORITY measures (--table-action-collection-*) under table-layout: fixed, cells wrap, and the bordered surface drops its --table-surface-min-inline-size floor \u2014 so requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions all stay inside a 390px frame with no horizontal scroll. Mark each column with `priority` on its ColumnDef. Semantics are untouched (no display change, no role rewriting, no card swap), so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440. Measured: table 1182 / 766 / 388px at 1440 / 1024 / 390, document scrollWidth === clientWidth at every width, LTR and RTL."},{name:"collapseBelow",type:"'sm' | 'md' | 'lg' | 'xl'",defaultValue:"'sm'",description:`Step at which preset="action-collection" switches to the compact priority measures, measured against the TABLE'S OWN container (a container query on sm 40rem \xB7 md 48rem \xB7 lg 64rem \xB7 xl 80rem), not the viewport \u2014 a table inside a master rail collapses before the page does. Ignored while preset is 'default'.`},{name:"sort",type:"{ key: string; direction: 'asc' | 'desc' }",description:"Active sort state (controlled/server surface). When provided alongside onSortChange, sortable columns show directional arrow icons and clicking the active column twice clears sort (calls onSortChange(undefined)). Omit both sort and onSortChange to sort client-side via TanStack."},{name:"onSortChange",type:"(sort: { key: string; direction: 'asc' | 'desc' } | undefined) => void",description:"Called when a sortable column header is clicked. Receives undefined when sort is cleared (third click on same column). Providing sort or onSortChange opts into the controlled (server) sort surface; omit both and the table sorts client-side via TanStack."},{name:"globalFilter / onGlobalFilterChange",type:"string / (next: string) => void",description:"Global search term surfaced by DataTable.Search. Omit both for client-side filtering; pass them to drive a server query (with manualFiltering)."},{name:"pagination / onPaginationChange / rowCount",type:"{ pageIndex: number; pageSize: number } / OnChangeFn / number",description:"Numbered-pagination state surfaced by DataTable.Pagination (page-size form). For server pagination pass all three (rowCount = total) with manualPagination; omit for client pagination."},{name:"columnVisibility / onColumnVisibilityChange",type:"VisibilityState / OnChangeFn<VisibilityState>",description:"Column show/hide state surfaced by DataTable.ViewOptions ('set view'). Internal if omitted."},{name:"manualSorting / manualFiltering / manualPagination",type:"boolean",defaultValue:"false",description:"Default false so the simple data+columns case sorts/filters/paginates in-browser. Set the relevant flag true and drive the matching state from your query for server-side behaviour."},{name:"loading",type:"boolean",defaultValue:"false",description:"When true, swaps the body for SHAPED skeleton rows rendered inside the table's own grid (one border, aligned columns) \u2014 never a separate <SkeletonTable> in a Card (that double-borders). With React Query keepPreviousData, drive this off isPlaceholderData (pagination/search) || isLoading (first load), NOT isLoading alone. Suppresses the empty state while true."},{name:"empty",type:"ReactNode",description:"Custom content rendered inside the table body when data is empty and loading is false. Defaults to a built-in EmptyState with a localised 'No data' message. Pass a custom <EmptyState title='...' description='...' action={...}/> to tailor the message."},{name:"error",type:"ReactNode",description:"FAILURE state. Pass error={isError} \u2014 `true` renders the built-in localized destructive EmptyState ('Couldn't load this list') announced with role='alert'; any other node REPLACES that copy (e.g. an <Alert> carrying an error code + request id). `false`/`undefined` means the read succeeded. NEVER pass a raw Error object (it is not renderable)."},{name:"denied",type:"ReactNode",description:"PERMISSION-DENIED state \u2014 the read was REFUSED (403), not failed. `true` renders the built-in localized warning EmptyState ('You don't have access to this list') with NO retry, announced politely (aria-live) because a permission boundary is expected information, not a fault. Takes precedence over `error`. Any other node replaces the copy."},{name:"onRetry",type:"() => void",description:"Retry handler surfaced as a Retry button inside the BUILT-IN error state only. Omit it to render the error without a retry affordance; it is intentionally never offered for `denied` (repeating a 403 cannot succeed)."},{name:"className",type:"string",description:"Extra classes applied to the root wrapper div (ui-data-table-root)."},{name:"children",type:"ReactNode",description:"Compound sub-parts: DataTable.Toolbar, DataTable.Search (global filter), DataTable.ViewOptions (column show/hide), DataTable.SelectAll, DataTable.BulkActions (ReactNode children OR a (count)=>node render-prop), DataTable.DensityToggle, DataTable.Pagination (cursor first/next when given cursor+hasMore+onChange, else numbered page-size form), DataTable.RowActions (kebab trigger), DataTable.Content. If no DataTable.Content is present in children, one is auto-rendered."}],usage:["DO pass loading={isFetching} during data fetches \u2014 it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.","DO NOT add a data.length===0 conditional around DataTable. When data is empty and loading is false, the built-in EmptyState renders automatically. Pass empty={<EmptyState title='...'/>} only when you need a custom message.","SIX STATES, ZERO HAND-ROLLING: loading (`loading`), empty (automatic / `empty`), error (`error` + optional `onRetry`), denied (`denied`), pagination (`DataTable.Pagination`), row actions (`DataTable.RowActions`). Wire them straight off the query \u2014 `<DataTable loading={isPending} error={isError} denied={status === 403} onRetry={refetch} \u2026/>` \u2014 and never branch the page around the table to render your own alert/empty/forbidden block. Precedence is loading > denied > error > empty > rows, so exactly one state ever shows.","DO provide getRowId when selectable is true or when rows do not have a string/number 'id' field \u2014 the default falls back to row.id and silently returns '' for missing IDs, which breaks selection.","DO use DataTable.Toolbar as the immediate child that wraps search/filter controls on the left and DataTable.DensityToggle/action buttons on the right. DataTable.BulkActions inside the toolbar auto-hides when selection count is 0; it accepts either plain ReactNode children (built-in 'N selected' status bar) or a (count)=>node render-prop (you own the whole bar).","DO reach for the grid chrome (DataTable.Search, DataTable.ViewOptions, DataTable.Pagination pageSizeOptions) when you need global search, a column 'set view' picker, or numbered pagination \u2014 these are the merged former-DataGrid features, now on the one DataTable. Drive them client-side by default; pass the matching state + manual* flag for a server query.","DataTable.Pagination OWNS ITS OWN INSET. The footer is a self-contained slot: it declares `padding-block` + `padding-inline` from `--table-pagination-padding-{y,x}` (block default = `--space-stack-sm`, inline default = `--table-cell-space-x`, so the 'rows per page' label lands on the same optical axis as the first column's text). Before the fix it declared `padding-top` only, so inside the documented flush container (`<Card><CardContent flush><DataTable/>`) the label and page-size Select sat flush against the container edge and border. DON'T ship a local `.ui-data-table-pagination { padding: \u2026 }` override in an app \u2014 retune the two tokens in your theme instead.",'RESPONSIVE APPROVAL / ACTION QUEUE: reach for `preset="action-collection"` when a dense five-column queue (requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions) must stay readable at 390px, and give every column a `priority` on its ColumnDef \u2014 `primary` (the row subject), `secondary` (its target), `meta` (a timestamp/id), `actions` (the row-action affordance, whose measure is reserved FIRST so it can never be pushed off-screen). Leave the free-text column unmarked; it takes the remaining space. This is the SAME contract, the SAME `--table-action-collection-*` tokens and the SAME container query as `Table preset="action-collection"` \u2014 there is no separate DataTable family. Never add a consumer width, a hidden column, `hiddenOnMobile` or a page-local breakpoint to make a table fit: retune the tokens instead.',"DON'T set `width` on a column that also has a `priority` \u2014 an explicit width utility wins the cascade over the priority measure and re-opens the horizontal scroll. Under the preset, `pin: 'end'` is also redundant: nothing scrolls sideways, so the actions column is already in frame.",'The DataTable surface\'s narrow-viewport width floor is `--table-surface-min-inline-size` (default 640px, released at the `sm` viewport step). It used to be a hard-coded `min-w-[640px] sm:min-w-0` utility pair on the surface \u2014 the literal that forced the horizontal scroll at 390. Retune (or zero) the token in your theme; `preset="action-collection"` already opts out of it.',"DO use ColumnDef.render for custom cell content (Badge, Link, RowActions). For plain string/number fields render can be omitted \u2014 DataTable falls back to String(row[key]).","DO give every visually-empty column an accessible header via `ariaLabel` \u2014 a row-actions column (`header: ''`, `pin: 'end'`) sets `ariaLabel: t('actions')`, so screen readers announce the column and axe reports no `empty-table-header`. DataTable dev-warns any column that renders a `<th>` with neither visible text nor an `ariaLabel`. The selection column added by `selectable` is already named by its SelectAll checkbox \u2014 no `ariaLabel` needed there.","COLUMN SEMANTICS + KEYBOARD: a `sortable` header renders as a real <button> inside the <th> with `aria-sort` (ascending/descending/none) on the <th>; it is Tab-reachable and toggles asc \u2192 desc \u2192 cleared on Enter/Space/click. A selection column exposes a header 'select all' Checkbox (indeterminate when a subset is selected) and a per-row Checkbox, each keyboard-operable with Space. An action column is visually empty but carries an `ariaLabel`; its per-row controls (kebab menu / buttons) own their own accessible names and keyboard behavior. Row click (`onRowClick`) is suppressed when the user activates an interactive descendant.","DO NOT nest DataTable.Content in a conditional \u2014 it is already guarded internally. If you need to override the table body slot, drop exactly one <DataTable.Content /> in children; DataTable auto-detects it by displayName and skips the default."],useCases:["Admin list pages (invoices, customers, orders, accounts) where rows are clickable for detail navigation via onRowClick.","Bulk-action workflows (e.g. mark invoices paid, export selected rows) \u2014 use selectable + DataTable.BulkActions to show contextual action buttons only when something is selected.","Server-side sorted tables: pass sort + onSortChange and update the data prop after the API call; DataTable renders asc/desc/neutral icons on the header automatically.","Cursor-paginated lists: add DataTable.Pagination with cursor + hasMore + onChange inside children to get First/Next navigation without offset arithmetic. For page-size + numbered prev/next instead, use DataTable.Pagination with pageSizeOptions (no cursor/onChange) driven by the internal TanStack pagination.","Full grid screens (global search + column 'set view' + numbered pagination): compose DataTable.Search, DataTable.ViewOptions, DataTable.DensityToggle in the toolbar and DataTable.Pagination pageSizeOptions={[\u2026]} \u2014 client-side by default, or server-side by passing globalFilter/pagination/sort state with the matching manual* flag.",'Responsive admin tables where lower-priority columns (e.g. internal IDs, dates) should collapse below mobile breakpoints \u2014 set hiddenOnMobile: true on those ColumnDef entries. When the columns must all stay DISCOVERABLE at 390 instead (an approval/action queue), use preset="action-collection" + ColumnDef.priority rather than hiding anything.',`Access-approval / action queues at 390px (SCR-105): preset="action-collection" + a priority on each ColumnDef keeps requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions inside the initial narrow frame with no page-local CSS, no consumer width, no hidden column and no horizontal scroll \u2014 see the DataTable 'Approval queue' example page.`,"Loading skeletons during initial page load or filter change: set loading={true} alongside an empty data={[]} to show the loading row without flashing an empty state."],related:["Table \u2014 raw primitive (TableHeader/TableBody/TableRow/TableCell). Use DataTable instead; only reach for Table directly when you need a non-standard layout that DataTable cannot express.","SkeletonTable \u2014 standalone skeleton placeholder rendered before any DataTable mounts (e.g. in a Suspense fallback or deferred-prop skeleton slot). DataTable.loading covers in-table loading; SkeletonTable covers pre-mount skeletons.","EmptyState \u2014 standalone empty state for non-table lists. DataTable already embeds EmptyState in its body; only use bare EmptyState for card content, non-tabular lists, or zero-state pages outside a DataTable.","LineChart / BarChart / AreaChart / PieChart (@godxjp/ui/charts) \u2014 when the SHAPE or trend of aggregated data matters more than exact per-row figures, visualize it with a chart instead of (or alongside) the table; keep DataTable when users need to read, sort, or act on individual rows.","DataState / InfiniteQueryState \u2014 TanStack Query lifecycle widgets from @godxjp/ui/query. Prefer these over DataTable when your list is driven by useQuery/useInfiniteQuery and you want automatic skeleton/empty/error handling at the query level rather than at the table level."],example:`import { useState } from "react";
|
|
392
435
|
import { Badge, DataTable, type ColumnDef } from "@godxjp/ui/data-display";
|
|
393
436
|
import { EmptyState } from "@godxjp/ui/data-display";
|
|
394
437
|
|
|
@@ -512,7 +555,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
|
|
|
512
555
|
title={t("addFromCatalog")}
|
|
513
556
|
action={<Button variant="outline">{t("viewCatalog")}</Button>}
|
|
514
557
|
/>
|
|
515
|
-
</ResponsiveGrid>`,storyPath:"data-display/ServiceLauncherCard.stories.tsx",rules:[40]},{name:"Badge",group:"data-display",tagline:"Plain or lifecycle badge. Use `variant` for static chips, or `status` to auto-map lifecycle keys to semantic tone + icon. Labels never wrap.",props:[{name:"variant",type:'"default" | "secondary" | "outline" | "dashed"',defaultValue:'"default"',description:"STRUCTURAL emphasis only (fill/border style) \u2014 NOT colour. Use `tone` for semantic colour. `dashed` = dashed border."},{name:"tone",type:'"default" | "primary" | "success" | "warning" | "destructive" | "info" | "muted" | "neutral"',description:'SEMANTIC colour intent (BadgeTone = ToneProp + `primary`). This is the colour knob \u2014 success/warning/destructive/info/etc. `primary` is a SOFT brand pill (tinted brand fill + brand text) for the dashboard role-pill pattern; for a SOLID brand fill use `variant="default"`. Keep variant for structure, tone for meaning.'},{name:"shape",type:'"default" | "pill" | "sharp"',defaultValue:'"default"',description:"Corner radius from the tokens \u2014 `default` (badge radius), `pill` (fully rounded), `sharp` (square). Use the prop instead of a `rounded-*` className."},{name:"color",type:"string",description:"The entity's OWN colour as a CSS colour \u2014 DATA, not a semantic tone: a status colour, an issue type, a tag, whatever a person picked in a settings screen. A third axis beside `variant` (structure) and `tone` (meaning), and it wins over both. The chip is WASHED rather than filled (`--badge-tint-fill` into `--badge-tint-surface`, label from `--badge-tint-foreground`) because no foreground clears WCAG AA against every colour a picker can produce \u2014 near-black and white measure equal at luminance 0.2029, both 4.15:1. Washed, the ratio is a function of the tokens instead: 8.52:1 worst case across the sRGB cube, both themes."},{name:"status",type:"string",description:"Lifecycle key. Known keys auto-map to tone + icon + i18n label; unknown keys fall back to neutral."},{name:"icon",type:"React.ComponentType<{ className?: string }> | null",description:"Leading icon override. Pass null to suppress the auto status icon."},{name:"children",type:"ReactNode",description:"Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status."}],usage:["DO pick the correct variant semantically: `success` (approved/paid), `warning` (pending/overdue), `destructive` (rejected/error), `secondary` (neutral category), `outline` (subtle label), `default` (primary accent). Never force a colour just for aesthetics \u2014 agents and screen readers read the variant as intent.","DO use `status` for entity lifecycle statuses (active, draft, pending, cancelled, failed, scheduled, etc.) so the component resolves the correct tone, icon, and i18n label.","DO pass `variant` explicitly for localized labels or categorical tiers, and pass `icon={null}` when a lifecycle glyph would be misleading.",
|
|
558
|
+
</ResponsiveGrid>`,storyPath:"data-display/ServiceLauncherCard.stories.tsx",rules:[40]},{name:"Badge",group:"data-display",tagline:"Plain or lifecycle badge. Use `variant` for static chips, or `status` to auto-map lifecycle keys to semantic tone + icon. Labels never wrap.",props:[{name:"as",type:'"div" | "span"',defaultValue:'"div"',description:'Render element. Swaps the TAG only \u2014 the chip keeps its own inline-flex box, icon and label. Pass "span" when the chip sits in a PHRASING context where a <div> is invalid HTML: inside a TabsTrigger, a PopoverTrigger or a Button (each renders a <button>, whose content model is phrasing content only), inside a <label>, or inside a <p>.'},{name:"variant",type:'"default" | "secondary" | "outline" | "dashed"',defaultValue:'"default"',description:"STRUCTURAL emphasis only (fill/border style) \u2014 NOT colour. Use `tone` for semantic colour. `dashed` = dashed border."},{name:"tone",type:'"default" | "primary" | "success" | "warning" | "destructive" | "info" | "muted" | "neutral"',description:'SEMANTIC colour intent (BadgeTone = ToneProp + `primary`). This is the colour knob \u2014 success/warning/destructive/info/etc. `primary` is a SOFT brand pill (tinted brand fill + brand text) for the dashboard role-pill pattern; for a SOLID brand fill use `variant="default"`. Keep variant for structure, tone for meaning.'},{name:"shape",type:'"default" | "pill" | "sharp"',defaultValue:'"default"',description:"Corner radius from the tokens \u2014 `default` (badge radius), `pill` (fully rounded), `sharp` (square). Use the prop instead of a `rounded-*` className."},{name:"color",type:"string",description:"The entity's OWN colour as a CSS colour \u2014 DATA, not a semantic tone: a status colour, an issue type, a tag, whatever a person picked in a settings screen. A third axis beside `variant` (structure) and `tone` (meaning), and it wins over both. The chip is WASHED rather than filled (`--badge-tint-fill` into `--badge-tint-surface`, label from `--badge-tint-foreground`) because no foreground clears WCAG AA against every colour a picker can produce \u2014 near-black and white measure equal at luminance 0.2029, both 4.15:1. Washed, the ratio is a function of the tokens instead: 8.52:1 worst case across the sRGB cube, both themes."},{name:"status",type:"string",description:"Lifecycle key. Known keys auto-map to tone + icon + i18n label; unknown keys fall back to neutral."},{name:"icon",type:"React.ComponentType<{ className?: string }> | null",description:"Leading icon override. Pass null to suppress the auto status icon."},{name:"children",type:"ReactNode",description:"Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status."}],usage:["DO pick the correct variant semantically: `success` (approved/paid), `warning` (pending/overdue), `destructive` (rejected/error), `secondary` (neutral category), `outline` (subtle label), `default` (primary accent). Never force a colour just for aesthetics \u2014 agents and screen readers read the variant as intent.","DO use `status` for entity lifecycle statuses (active, draft, pending, cancelled, failed, scheduled, etc.) so the component resolves the correct tone, icon, and i18n label.","DO pass `variant` explicitly for localized labels or categorical tiers, and pass `icon={null}` when a lifecycle glyph would be misleading.",'Badge renders as a `<div>` by default (HTMLAttributes<HTMLDivElement>) \u2014 pass `as="span"` when it sits inside a <button>, <label> or <p>, where a <div> is invalid HTML. It carries no interactive semantics either way. If you need a clickable chip, wrap it in a `<button>` or use a Button with a matching variant \u2014 never add an `onClick` directly to Badge without an accessible role.',"Badge is a leaf \u2014 pass plain text or a short ReactNode as children. Do NOT nest another Badge, a Button, or interactive controls inside it; that breaks focus order and creates invalid HTML (div-in-inline-context).","Use semantic tokens for any className overrides (`text-muted-foreground`, `bg-destructive`) \u2014 never raw Tailwind palette classes like `bg-green-500`.","DO pass `color` \u2014 never an inline `backgroundColor` \u2014 when the colour belongs to the RECORD rather than to its meaning (a status an administrator coloured, an issue type, a tag). A hand-filled chip has to choose a foreground, and no choice is readable for every colour a picker can produce; `color` moves the ground instead and keeps the label on the surface's own foreground."],useCases:['Category or tier labels on table rows \u2014 e.g. plan tier (`<Badge variant="secondary">Pro</Badge>`), document type (`<Badge variant="outline">Invoice</Badge>`), or locale tag (`<Badge variant="secondary">EN</Badge>`).','Approval or review state in an accounting list where the value is not a lifecycle key in Badge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge tone="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge tone="warning">\u8981\u78BA\u8A8D</Badge>`.',"Inline count or highlight next to a heading or nav item \u2014 e.g. `<Badge variant=\"destructive\">3</Badge>` beside 'Overdue invoices' to draw attention to a non-zero count.",'Feature flags or experiment variant labels on admin records \u2014 e.g. `<Badge variant="outline">A/B</Badge>` alongside a campaign row to indicate it is in a split test.',"Read-only metadata chips inside a Descriptions.Item or Card header where a lifecycle icon would be visually heavy \u2014 e.g. currency code, payment method, or region tag.",'A status or category an administrator coloured themselves, in an issue tracker or a CRM \u2014 `<Badge shape="pill" color={status.color}>{status.name}</Badge>`. The colour comes out of a picker and is stored on the row, so it cannot be mapped to a tone.'],related:["Button \u2014 use instead of Badge when the chip must be interactive (clickable, toggleable). Badge carries no button role or keyboard handler; a naked `onClick` on Badge is inaccessible."],example:`import { Badge } from "@godxjp/ui/data-display";
|
|
516
559
|
|
|
517
560
|
<Badge variant="secondary">A/B</Badge>
|
|
518
561
|
<Badge status="active">\u516C\u958B\u4E2D</Badge>
|
|
@@ -629,11 +672,33 @@ import remarkGfm from "remark-gfm";
|
|
|
629
672
|
{ title: "\u8ACB\u6C42\u66F8\u3092\u767A\u884C", status: "done" },
|
|
630
673
|
{ title: "\u627F\u8A8D\u5F85\u3061", status: "current" },
|
|
631
674
|
{ title: "\u6D88\u8CBB\u7A0E\u3092\u8A08\u4E0A", status: "pending" },
|
|
632
|
-
]} />`,storyPath:"data-display/Timeline.stories.tsx",rules:[]},{name:"
|
|
675
|
+
]} />`,storyPath:"data-display/Timeline.stories.tsx",rules:[]},{name:"TimelineGrid",group:"data-display",tagline:"The time-axis half of the Timeline family: a vertical hour axis, one column per day (or room, or machine) and event blocks placed by start time and duration. Overlapping events are laid out side by side automatically. NOT a calendar \u2014 no month view, no navigation, no drag-to-create, no recurrence, no timezone conversion.",props:[{name:"label",type:"string",required:!0,description:"Accessible name of the grid. Required, and a plain string: the grid is a focusable scrolling region, so its name has to survive as an `aria-label`."},{name:"columns",type:"TimelineGridColumnProp[]",required:!0,description:"`{ id, label, description?, current? }` in render order. `label` is the column head AND the accessible name of that column's event list, so keep it text. `current: true` marks today's column: it takes the tint and hosts the `now` marker."},{name:"events",type:"TimelineGridEventProp[]",required:!0,description:'`{ id, columnId, start, end, title, description?, color? }` in any order. `start`/`end` are clock times in the column\'s own day, `"HH:MM"` 24-hour (`"24:00"` = end of day). An `end` at or before `start` continues into the next day (22:00\u201306:00 \u591C\u52E4). `color` is the record\'s own colour, washed like `Badge color`.'},{name:"start",type:"string",defaultValue:"the earliest event, on the hour",description:'First clock time on the axis, `"HH:MM"`. Left off, the axis is derived from the events, so a block can only fall outside an axis you PINNED.'},{name:"end",type:"string",defaultValue:"the latest event, on the hour",description:'Last clock time on the axis, `"HH:MM"`. A block the pinned axis cuts is drawn to the edge and carries `data-clipped`, and still prints its real range as text.'},{name:"interval",type:"number",defaultValue:"1",description:"Hours between hour rules and axis labels. Use 2 or 3 when a long axis has to fit one screen."},{name:"now",type:"string",description:'Current clock time, `"HH:MM"`. Draws the now marker in every column marked `current`. The marker is decorative (`aria-hidden`): the hours it points at are already text on every block.'},{name:"onEventSelect",type:"(event: TimelineGridEventProp) => void",description:"Block click handler. Its PRESENCE turns every block into a real `button`, which is what makes the blocks keyboard-reachable; without it the grid is a read-only board."},{name:"className",type:"string",description:"Extra classes on the scroll region."},{name:"id",type:"string",description:"DOM id on the scroll region."}],usage:['DO import from `@godxjp/ui/data-display`: `import { TimelineGrid } from "@godxjp/ui/data-display";`',"DO give a shift/booking board one column per day and let the grid place the blocks. DON'T hand-roll a `relative` container with absolutely-positioned divs and percentage offsets \u2014 that is the exact shape this primitive replaces (#354 item 7), and it hides one of two overlapping shifts.","DO re-tune the rhythm with the tokens instead of a className: `--timeline-grid-hour-height` is the height of one hour (the knob that fits a 24-hour axis on one screen), `--timeline-grid-column-min-width` is the floor below which the grid scrolls instead of collapsing, `--timeline-grid-axis-width` is the hour rail, and `--timeline-grid-event-color` / `--timeline-grid-now-color` / `--timeline-grid-current-tint` are role-mirror colour knobs.",'DO express a shift that crosses midnight as `start: "22:00", end: "06:00"` \u2014 an `end` at or before `start` means the next day. The block is drawn to the end of the window and marked `data-clipped`, and the text still reads 22:00\u201306:00.',"DON'T reach for TimelineGrid for a MONTH calendar: a month grid is a real `Table`, one `TableRow` per week and one `TableCell` per day. TimelineGrid owns the continuous time axis, not the day matrix.","DON'T expect navigation, drag-to-create, recurrence or timezone conversion \u2014 none of it ships. `start`/`end` are clock times in the column's own day; which day a column stands for is yours to decide.","DON'T pass an event whose `columnId` matches no column, or one that falls entirely outside a pinned `start`/`end`: it is not drawn. Leave the axis unpinned and it is derived from the events instead."],useCases:["A weekly shift board (\u30B7\u30D5\u30C8\u30AB\u30EC\u30F3\u30C0\u30FC): seven day columns on a 06:00\u201324:00 axis, \u65E9\u756A / \u9045\u756A / \u591C\u52E4 blocks in the decorative wa-iro palette, `now` drawing the current-time line on today's column.","A day view of one team: one column per staff member, `interval={1}`, `onEventSelect` opening the shift detail in a Sheet.","Meeting-room booking for a single day: one column per room, the axis derived from the bookings themselves, overlapping holds side by side so a double-booking is visible instead of hidden.","Machine / bay utilisation on a shop floor: one column per machine, blocks coloured by job, the clipped marker showing a run that continues past the shift window."],related:["Timeline \u2014 one lane of ordered events with no scale (an audit trail, a shipment history). TimelineGrid is the same family with a measured time axis and one lane per column.","Table \u2014 the MONTH calendar is a real table (`bordered`, one row per week, one cell per day), not a TimelineGrid.","Calendar \u2014 the single-date PICKER in data-entry. Use it to jump to a month; it does not show events.","Sheet \u2014 the usual destination of `onEventSelect`: the block is the affordance, the drawer is the detail.","Badge \u2014 the same `color` wash for a record's own colour, on a chip instead of a block."],example:`import { TimelineGrid } from "@godxjp/ui/data-display";
|
|
676
|
+
|
|
677
|
+
<TimelineGrid
|
|
678
|
+
label="\u9031\u30B7\u30D5\u30C8 5\u670811\u65E5\u301C17\u65E5"
|
|
679
|
+
start="06:00"
|
|
680
|
+
end="24:00"
|
|
681
|
+
interval={2}
|
|
682
|
+
now="14:35"
|
|
683
|
+
columns={[
|
|
684
|
+
{ id: "05-13", label: "\u6C34 13" },
|
|
685
|
+
{ id: "05-14", label: "\u6728 14", current: true },
|
|
686
|
+
]}
|
|
687
|
+
events={[
|
|
688
|
+
{ id: "a", columnId: "05-14", start: "09:00", end: "17:30", title: "\u65E9\u756A", description: "\u7530\u4E2D" },
|
|
689
|
+
{ id: "b", columnId: "05-14", start: "13:00", end: "22:00", title: "\u9045\u756A", description: "\u9AD8\u6A4B" },
|
|
690
|
+
{ id: "c", columnId: "05-14", start: "22:00", end: "06:00", title: "\u591C\u52E4", description: "\u4F0A\u85E4" },
|
|
691
|
+
]}
|
|
692
|
+
onEventSelect={(event) => openShift(event.id)}
|
|
693
|
+
/>`,storyPath:"data-display/TimelineGrid.stories.tsx",rules:[42,45]},{name:"Table",group:"data-display",tagline:"Primitive table shell (Table/TableHeader/TableBody/TableRow/TableHead/TableCell). Prefer DataTable for admin lists; use these for custom one-off tables.",props:[{name:"children",type:"ReactNode",required:!0,description:"TableHeader / TableBody composition."},{name:"className",type:"string",description:"Extra classes on the table element."},{name:"scrollable",type:"boolean",description:"Whether Table owns its own horizontal-scroll region (default true). Leave true for a standalone table so a table wider than its container scrolls in a keyboard-reachable wrapper. Set false only when an ancestor already provides the scroll region (DataTable does) to avoid a redundant nested scroller + duplicate keyboard tab stop."},{name:"bordered",type:"boolean",defaultValue:"false",description:"Draw the full cell GRID: an outer frame plus vertical rules between columns (the horizontal row rules already come from TableRow). Reach for it whenever the table carries rowSpan/colSpan merged cells \u2014 without column rules the merge relationships are unreadable. Colour comes from --table-border-color (default --border). Default false emits nothing."},{name:"preset",type:'"default" | "action-collection"',defaultValue:'"default"',description:'Named collection contract. "default" emits no attribute and keeps the plain table. "action-collection" is the canonical dense approval/action queue: the desktop INTRINSIC column widths (which make a five-column queue wider than its card and force a horizontal scroll at 390) are replaced by table-layout: fixed plus the token-owned column PRIORITY measures (--table-action-collection-*), and cells wrap. Mark each column with `priority` on its TableHead AND its TableCell. Semantics are untouched \u2014 no display change, no role rewriting, no card transformation \u2014 so header association, aria-sort and screen-reader table navigation are identical at 390 and 1440.'},{name:"collapseBelow",type:'"sm" | "md" | "lg" | "xl"',defaultValue:'"sm"',description:`Step at which preset="action-collection" switches to the compact priority measures, measured against the TABLE'S OWN container (a container query), not the viewport \u2014 a table inside a master rail collapses before the page does. Ignored while preset is "default".`}],usage:["DO compose all six sub-parts in order: wrap with `<Table>`, then `<TableHeader>` containing `<TableRow><TableHead>\u2026</TableRow>`, then `<TableBody>` containing one or more `<TableRow><TableCell>\u2026` rows. Skipping any layer (e.g. bare `<th>` inside `<Table>`) bypasses the design tokens and hover/border styles.",'DO use `TableHead` (not `TableCell`) for header cells \u2014 it renders `<th>` with `data-slot="table-head"` and the `--table-row-height` CSS variable for consistent header sizing across the design system. `TableCell` renders `<td>` with `data-slot="table-cell"` and is for body rows only.','DO apply numeric alignment via `className` on individual `TableHead`/`TableCell` elements (e.g. `className="text-right"`). There are no built-in alignment props \u2014 all styling goes through Tailwind class overrides.',"DO NOT hand-roll empty-state handling inside a Table composition. When data can be empty, switch to `DataTable` (which has a built-in empty state) or wrap the `<Table>` with a conditional that renders `<EmptyState>` \u2014 never leave a table with only a header and zero rows.","DO NOT use Table for lists that need sorting, filtering, pagination, or row selection \u2014 those features are only in `DataTable`. Table is intentionally stateless: it owns no TanStack Table instance, no column definitions, and no toolbar.",'DO reach for `preset="action-collection"` for a dense approval / action queue (requester \xB7 target \xB7 reason \xB7 requested date \xB7 row actions) that must stay readable at 390px, and mark every column with `priority` on BOTH its `TableHead` and its `TableCell`: `primary` (the row subject), `secondary` (its target), `meta` (a timestamp/id), `actions` (the row-action affordance, whose measure is reserved first so it can never be pushed off-screen). Leave the free-text column unmarked \u2014 it takes the remaining space. Never add a consumer width, a hidden column or a page-local breakpoint to make a table fit; retune `--table-action-collection-*` instead. The IDENTICAL preset exists on `DataTable` (`preset` + `collapseBelow` on the table, `priority` on the `ColumnDef`) sharing these same tokens \u2014 use DataTable when the queue is data-driven and needs sorting/selection/pagination, and reach for the raw `Table` only for a hand-authored queue.',"DO reach for `<TableCell flush>` when the cell's CONTENT owns its inset \u2014 an expanded detail panel under a row (`<TableCell flush colSpan={n}>`), a nested table, a full-bleed media strip. It drops the cell's own padding so the child spans the whole cell; without it the panel is indented by `--table-cell-space-x` and the only route was a `p-0` utility, which no service theme can reach.","DO express hierarchy with `<TableCell indent={depth}>` \u2014 a grouped table's detail rows under their subtotal header, or a tree row under its parent. The measure is `--table-cell-space-x + depth x --table-cell-indent-space-step`, so level 0 sits on the column's own text axis and a service retunes (or flattens) the step in one token. Never hand-roll `style={{ paddingInlineStart }}` at the call site \u2014 that is a per-page constant no theme can reach, and it breaks in RTL unless you remember the logical property.","DO place `<Table>` inside a `<CardContent flush>` (or `p-0` card) when embedding in a Card, so the built-in `overflow-auto` wrapper sits flush to the card edges. Wrapping with plain `<CardContent>` adds padding that clips the horizontal scroll shadow."],useCases:["Invoice line-item breakdowns \u2014 a fixed, read-only list of product/quantity/unit-price/total rows where columns are predefined and will never need sort or filter controls.","Summary/comparison tables inside a detail panel or Dialog, such as showing two payment plans side-by-side, where the structure is hand-authored and not driven by a data array.","Print or PDF-export views where a minimal, stateless `<table>` element with predictable markup is required and DataTable's JS-driven features would interfere with server-side rendering or CSS print rules.","Embedded sub-tables inside a DataTable expanded row (the inner table uses Table primitives because nesting a full DataTable instance inside another is unsupported).","Static reference tables in documentation, onboarding, or settings pages \u2014 e.g. a permission matrix or feature comparison \u2014 where every cell is literal JSX content, not from a data array."],related:["DataTable \u2014 choose DataTable for any data array that needs sorting, filtering, pagination, row selection, bulk actions, or density toggle. DataTable internally renders Table primitives, so switching up is non-breaking. Default to DataTable for all admin list pages.","SkeletonTable \u2014 use as a loading placeholder before a Table or DataTable mounts. Drop it in the `skeleton` slot of DataState, or render it directly while data is fetching. Do not show a Table with empty rows as a loading state.","Descriptions \u2014 choose Descriptions when content is label\u2192value pairs (two columns, no repeated rows of the same type). Table is better when every row shares the same typed columns.","DataState \u2014 when your Table's data comes from `useQuery`, wrap it in DataState to handle loading/error/empty states declaratively instead of writing conditional logic around the Table yourself."],example:`import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@godxjp/ui/data-display";
|
|
633
694
|
|
|
634
695
|
<Table>
|
|
635
696
|
<TableHeader><TableRow><TableHead>\u9805\u76EE</TableHead><TableHead className="text-right">\u91D1\u984D</TableHead></TableRow></TableHeader>
|
|
636
|
-
<TableBody
|
|
697
|
+
<TableBody>
|
|
698
|
+
<TableRow><TableCell>\u9001\u6599</TableCell><TableCell className="text-right">\xA5500</TableCell></TableRow>
|
|
699
|
+
<TableRow><TableCell indent={1}>\u3046\u3061\u96E2\u5CF6\u52A0\u7B97</TableCell><TableCell className="text-right">\xA5200</TableCell></TableRow>
|
|
700
|
+
<TableRow><TableCell flush colSpan={2}><ShippingBreakdown /></TableCell></TableRow>
|
|
701
|
+
</TableBody>
|
|
637
702
|
</Table>`,storyPath:"data-display/Table.stories.tsx",rules:[]},{name:"DataState",group:"data-display",tagline:"TanStack Query lifecycle widget \u2014 skeleton / error / empty / success for one useQuery block. Import from @godxjp/ui/query.",props:[{name:"query",type:"UseQueryResult<T>",required:!0,description:"The useQuery result."},{name:"skeleton",type:"ReactNode",required:!0,description:"Shown while loading."},{name:"prerequisite",type:"ReactNode",description:"Shown when the query is disabled/unstarted (pending + fetchStatus idle)."},{name:"showRetry",type:"boolean",defaultValue:"false",description:"Force Retry even for non-transient causes. Retry is offered automatically for transient/network/5xx errors regardless of this flag."},{name:"onAuthError",type:"() => void",description:"Recovery for 401 / expired-token errors: renew the session or sign in again. A 401 renders this action instead of Retry."},{name:"children",type:"(data) => ReactNode",required:!0,description:"Render function with resolved data."},{name:"empty",type:"ReactNode",description:"Shown when isEmpty(data) is true."},{name:"isEmpty",type:"(data) => boolean",description:"Custom empty check."}],usage:["DO: pass a `UseQueryResult<T>` directly from `useQuery` \u2014 DataState reads `isPending`, `isError`, `isFetching`, `data`, and `error` off it; never destructure those fields manually and branch yourself.","DO: always provide a `skeleton` \u2014 it renders during both the initial pending phase and during a re-fetch after an error; pass `<SkeletonTable />` for tabular data or `<SkeletonStat />` for stat card lists \u2014 never `null` or a spinner div.",'DO: provide `empty` + `isEmpty` together when the data can legitimately return 0 items \u2014 e.g. `isEmpty={(d) => d.items.length === 0}` paired with `empty={<EmptyState title="\u2026" />}`. Omitting `empty` means an empty array still falls through to `children`, silently rendering a blank table.',"DON'T: wrap DataState in your own conditional \u2014 e.g. `{query.isSuccess && <DataState \u2026>}`. DataState IS the conditional; the outer guard is redundant and breaks the retry/refetch skeleton.","DON'T: use DataState for `useInfiniteQuery` results. The `query` prop type is `UseQueryResult<T>`, not `UseInfiniteQueryResult`. Use `InfiniteQueryState` (from `@godxjp/ui/query`) instead, which accepts `flatten` and renders a load-more footer.","DO: classify errors by cause. Use session renewal/sign-in for 401, access guidance for 403, contextual correction for domain errors, and opt into showRetry only for transient network/5xx errors.","DO: pass prerequisite for enabled:false queries. Pending + fetchStatus idle is unstarted, not loading, and never renders a skeleton.","DO: rely on the localized, cause-specific error message \u2014 the raw backend/token/stack text is never shown. For a domain-specific message (e.g. a 422 field error) pass a custom errorRenderer.","DO: expect a background refetch over existing data to keep the content on screen with a polite sr-only busy status \u2014 it does not flash the skeleton. Only the initial fetch (isPending) shows the skeleton."],useCases:["A detail page that loads a single invoice/journal entry via `useQuery` \u2014 DataState renders the skeleton row while fetching, an error alert with retry if the API fails, and the `<InvoiceCard>` only when data is confirmed non-null.","A list page that shows a `DataTable` of members/partners \u2014 wrap the table in DataState so the skeleton matches the column count while loading and `EmptyState` appears when the filtered result set is empty.","A sidebar panel that lazily loads related transactions for the selected entity \u2014 DataState keeps the panel in skeleton state during the background fetch without any manual `isPending` branching in the parent.","A dashboard stat card that calls a summary API \u2014 DataState handles the loading/error/empty lifecycle so `<StatCard>` is only rendered with fully resolved numbers, preventing NaN or undefined rendering.","Any page using `useQuery` where the empty state and loading state are visually different \u2014 DataState enforces the correct visual for each phase without scattered `if` statements across the component tree."],related:["InfiniteQueryState \u2014 use instead of DataState when the query is `useInfiniteQuery`; it accepts a `flatten` function to reduce pages and adds a load-more footer. DataState cannot accept `UseInfiniteQueryResult`.","SkeletonTable / SkeletonStat \u2014 pass as the `skeleton` prop of DataState; they are not standalone replacements for DataState, only the loading slot inside it.","EmptyState \u2014 pass as the `empty` prop of DataState alongside a matching `isEmpty` predicate; do not hand-roll an empty-check outside DataState by inspecting `query.data` yourself.","AlertMutationFeedback \u2014 sibling widget for mutation (not query) lifecycle; use it below a form submit button to surface `useMutation` errors, not DataState which only handles `useQuery`."],example:`import { DataState } from "@godxjp/ui/query";
|
|
638
703
|
|
|
639
704
|
<DataState query={membersQuery} skeleton={<SkeletonTable />} isEmpty={(d) => d.items.length === 0} empty={<EmptyState title="\u4F1A\u54E1\u306A\u3057" />}>
|
|
@@ -936,7 +1001,7 @@ import { Toaster } from "@godxjp/ui/feedback";
|
|
|
936
1001
|
// anywhere \u2014 import toast from "sonner"
|
|
937
1002
|
import { toast } from "sonner";
|
|
938
1003
|
toast.success("\u30AF\u30FC\u30DD\u30F3\u3092\u516C\u958B\u3057\u307E\u3057\u305F");
|
|
939
|
-
toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,storyPath:"feedback/Toaster.stories.tsx",rules:[]},{name:"Tabs",group:"navigation",tagline:"Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",props:[{name:"items",type:"{ value: string; label: React.ReactNode; content: React.ReactNode; disabled?: boolean }[]",description:"Optional data-driven tab list. When provided, Tabs renders all triggers and content panels. When Tabs owns the initial selection (no `value`, and no `defaultValue` naming an existing ENABLED item), it falls back to the first item that is NOT `disabled` \u2014 never a disabled one \u2014 and selects nothing if every item is disabled."},{name:"value",type:"string",description:"Controlled active tab key."},{name:"defaultValue",type:"string",description:"Uncontrolled initial tab key. Ignored (falls back to the first enabled item) when it names a disabled item or an unknown key."},{name:"onValueChange",type:"(value: string) => void",description:"Active-tab change handler."},{name:"variant",type:'"default" | "line" | "card"',defaultValue:'"default"',description:'Trigger-strip appearance. `default`/`card` keep the pill/card chrome (a selected trigger gets a soft `ring-primary/25` ring). `line` is UNDERLINE-ONLY: the selected trigger gets no ring or card border at all \u2014 only the token-owned 2px primary bar (--tabs-indicator-{background,size,offset}) \u2014 so the `:focus-visible` keyboard ring stays visible and clearly distinct from selection. With `items`, the variant is forwarded to the list; when composing manually, pass the same value to `<TabsList variant="line">`.'}],usage:["DO pass `items` when all tab content is known up front \u2014 each item needs a unique `value`, trigger `label`, and panel `content`.",'When not using `items`, compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger.',"DO: use `defaultValue` (uncontrolled) for simple local state; use `value` + `onValueChange` together (controlled) when the active tab is driven by URL query params, router state, or parent state. NEVER set both simultaneously.","DO use `variant` on Tabs when using `items`; when composing manually, set `variant` on `TabsList`.",'DO: pass `orientation="vertical"` to `<Tabs>` (not to `TabsList`) for a side-rail layout \u2014 the CSS group classes on root and triggers respond automatically, so no extra className gymnastics are needed.',"DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles, including the token-owned indicator bar for the `line` variant. Adding your own `border-b-2 border-primary` (or a page-local `ring-0` override to remove one) breaks the design; retune --tabs-indicator-{background,size,offset} in the service theme instead.","DO trust the horizontal `TabsList` to scroll its own overflow (hidden scrollbar, swipeable) instead of clipping when tab labels \u2014 especially long localized ones (Japanese, German) \u2014 don't fit a narrow container. Don't wrap it in your own `overflow-x-auto` div or truncate labels to work around clipping; that
|
|
1004
|
+
toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,storyPath:"feedback/Toaster.stories.tsx",rules:[]},{name:"Tabs",group:"navigation",tagline:"Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",props:[{name:"items",type:"{ value: string; label: React.ReactNode; content: React.ReactNode; disabled?: boolean }[]",description:"Optional data-driven tab list. When provided, Tabs renders all triggers and content panels. When Tabs owns the initial selection (no `value`, and no `defaultValue` naming an existing ENABLED item), it falls back to the first item that is NOT `disabled` \u2014 never a disabled one \u2014 and selects nothing if every item is disabled."},{name:"value",type:"string",description:"Controlled active tab key."},{name:"defaultValue",type:"string",description:"Uncontrolled initial tab key. Ignored (falls back to the first enabled item) when it names a disabled item or an unknown key."},{name:"onValueChange",type:"(value: string) => void",description:"Active-tab change handler."},{name:"variant",type:'"default" | "line" | "card"',defaultValue:'"default"',description:'Trigger-strip appearance. `default`/`card` keep the pill/card chrome (a selected trigger gets a soft `ring-primary/25` ring). `line` is UNDERLINE-ONLY: the selected trigger gets no ring or card border at all \u2014 only the token-owned 2px primary bar (--tabs-indicator-{background,size,offset}) \u2014 so the `:focus-visible` keyboard ring stays visible and clearly distinct from selection. With `items`, the variant is forwarded to the list; when composing manually, pass the same value to `<TabsList variant="line">`.'}],usage:["DO pass `items` when all tab content is known up front \u2014 each item needs a unique `value`, trigger `label`, and panel `content`.",'When not using `items`, compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger.',"DO: use `defaultValue` (uncontrolled) for simple local state; use `value` + `onValueChange` together (controlled) when the active tab is driven by URL query params, router state, or parent state. NEVER set both simultaneously.","DO use `variant` on Tabs when using `items`; when composing manually, set `variant` on `TabsList`.",'DO: pass `orientation="vertical"` to `<Tabs>` (not to `TabsList`) for a side-rail layout \u2014 the CSS group classes on root and triggers respond automatically, so no extra className gymnastics are needed.',"DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles, including the token-owned indicator bar for the `line` variant. Adding your own `border-b-2 border-primary` (or a page-local `ring-0` override to remove one) breaks the design; retune --tabs-indicator-{background,size,offset} in the service theme instead.","DO trust the horizontal `TabsList` to scroll its own overflow (hidden scrollbar, swipeable) instead of clipping when tab labels \u2014 especially long localized ones (Japanese, German) \u2014 don't fit a narrow container. Don't wrap it in your own `overflow-x-auto` div or truncate labels to work around clipping; that is now the framework's job.","DON'T assume the first item is ever auto-selected when it is `disabled` \u2014 Tabs always resolves the fallback to the first ENABLED item (or none, if all are disabled). A `disabled: true` first item is safe to author without also setting `defaultValue`.",'DON\'T write your own resize/scroll-into-view effect to keep the selected tab on screen \u2014 `TabsList` observes its own size and its triggers\' `data-state` and re-pins the active (or focused, under `activationMode="manual"`) trigger with `scrollIntoView({ block: "nearest", inline: "nearest" })`, honoring `prefers-reduced-motion` and leaving a deliberate manual scroll alone. Before that, a 1440 \u2192 1024 \u2192 390 resize could strand the ACTIVE FIRST tab entirely outside the strip while it still reported `aria-selected="true"`.',"DON'T re-centre the strip with a `justify-center` utility. `TabsList` aligns with `safe center` on purpose: plain centring splits the overflow across BOTH edges while `scrollLeft` only ever covers the trailing one, so the leading tab ends up permanently outside the scrollport and no gesture reaches it. `safe` keeps the centred look while the tabs fit and falls back to start alignment the moment they don't."],useCases:["Detail drawers or pages that need full per-panel control \u2014 e.g. an accounting journal-entry sheet where one panel has `forceMount` to keep a live chart mounted, requiring custom `TabsContent` props that `Tabs` cannot pass.","Controlled tabs driven by URL search params (e.g. `?tab=history`) where the parent reads/writes the active key and passes it to `value` / `onValueChange`.",'Vertical side-rail navigation inside a `SplitPane` or settings layout where `orientation="vertical"` on the root and `variant="line"` on `TabsList` combine to produce a sidebar-style tab strip.',"Lightweight widget tabs on a dashboard card \u2014 e.g. switching a `DataTable` between 'Pending' and 'Paid' invoice views \u2014 where an uncontrolled `defaultValue` is sufficient and no URL state is needed.","Admin entity profile pages (company, partner, employee) where each `TabsContent` wraps an Inertia deferred prop panel, lazy-loading expensive data only when the tab is first activated."],related:["Steps (@godxjp/ui/navigation) \u2014 sequential wizard/progress indicator. Use Steps when order and completion state matter (multi-step forms, onboarding flows); use Tabs when panels are non-sequential and any tab can be visited freely.","Toolbar / ToolbarGroup (@godxjp/ui/navigation) \u2014 horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.","DropdownMenu (@godxjp/ui/navigation) \u2014 use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a DropdownSidebar is a lighter alternative to a full tab strip."],example:`import { Tabs } from "@godxjp/ui/navigation";
|
|
940
1005
|
|
|
941
1006
|
<Tabs
|
|
942
1007
|
defaultValue="overview"
|
|
@@ -1516,7 +1581,7 @@ function CustomRadioGroup() {
|
|
|
1516
1581
|
{/* wrap each item in Field for label + description */}
|
|
1517
1582
|
</Radio.Group>
|
|
1518
1583
|
);
|
|
1519
|
-
}\`}`,storyPath:"data-entry/Radio.stories.tsx",rules:[3,6,13,23]},{name:"Popover",group:"data-display",tagline:"Radix-backed floating panel anchored to a trigger \u2014 always compose with PopoverTrigger + PopoverContent; never use a raw div overlay.",props:[{name:"open",type:"boolean",description:"Controls open state in controlled mode. Pair with onOpenChange."},{name:"defaultOpen",type:"boolean",defaultValue:"false",description:"Initial open state for uncontrolled usage."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Callback fired when the popover open state changes. Required when using controlled mode (open prop)."},{name:"modal",type:"boolean",defaultValue:"false",description:"When true, interaction outside the popover is blocked and focus is trapped inside (Radix Root prop)."},{name:"align",type:"'start' | 'center' | 'end'",defaultValue:'"center"',description:"PopoverContent prop. Horizontal alignment of the popover relative to the trigger."},{name:"sideOffset",type:"number",defaultValue:"4",description:"PopoverContent prop. Distance in pixels between the popover panel and its anchor."},{name:"side",type:"'top' | 'right' | 'bottom' | 'left'",defaultValue:'"bottom"',description:"PopoverContent prop. Which side of the trigger the panel prefers to open on (auto-flips on overflow)."},{name:"asChild",type:"boolean",defaultValue:"false",description:"PopoverTrigger prop. Merges trigger props onto the immediate child element (e.g. a Button) instead of rendering an extra DOM node. Strongly recommended to avoid a wrapping <button>."},{name:"className",type:"string",description:"PopoverContent prop. Extra Tailwind classes merged onto the panel (default: w-72 p-4 rounded-md border shadow-md z-50)."}],usage:["DO compose: <Popover> \u2192 <PopoverTrigger asChild> \u2192 <Button/> and <PopoverContent>. All four parts are required for any popover to function; omitting PopoverTrigger or PopoverContent produces nothing.","DO use asChild on PopoverTrigger when the trigger is already a Button or link \u2014 this avoids a nested <button><button> violation and extra DOM nesting.","DO use controlled mode (open + onOpenChange) when external code must open/close the popover programmatically (e.g., form validation reveal, keyboard shortcut). For toggle-only interactions, uncontrolled (defaultOpen) is simpler.","DO structure panel content with PopoverHeader > PopoverTitle + PopoverDescription for labelled panels. This is purely presentational but establishes the correct font-weight and muted-foreground on the description.","DON'T hand-roll a floating div or use a CSS show/hide toggle \u2014 Popover provides portal rendering, focus trap, Escape-to-close, and ARIA automatically.","DON'T place a Popover inside a Dialog without setting modal={false} on the Popover \u2014 nested modals conflict with Radix's focus management and produce stuck focus."],useCases:["Advanced filter panel: a 'Filters' Button triggers a Popover containing filter inputs (date range, status selects); panel width overridden via className='w-96'.","Row action menu overflow: when a DataTable row has too many actions for inline display, a Popover holds the secondary actions (Edit, Archive, Delete) without navigating away.","Contextual help / tooltip-rich: a small '?' icon button opens a Popover with PopoverTitle + PopoverDescription explaining a form field \u2014 richer than a Tooltip but less intrusive than a Dialog.","Inline record preview: clicking a reference number in an invoice list opens a Popover showing a summary card of the linked document before the user decides to navigate.","Column visibility picker: a 'Columns' button above a DataTable opens a Popover containing checkboxes to show/hide columns, with controlled state managed in parent.","Quick-edit cell: for an admin table, clicking a status badge opens a Popover with a RadioGroup to change status in-place without a full Dialog."],related:["Tooltip \u2014 use Tooltip for brief, non-interactive label-like hints (hover-only, no inputs). Use Popover when the floating content is interactive (buttons, inputs, forms).","Dialog/Sheet \u2014 use Dialog or Sheet for full modal actions that require user confirmation or significant input. Use Popover for lightweight, anchor-relative panels that dismiss on outside click.","DropdownMenu \u2014 use DropdownMenu for a flat list of clickable actions or links. Use Popover when the floating panel needs arbitrary layout (forms, grids, rich content) rather than a menu list."],example:`import {
|
|
1584
|
+
}\`}`,storyPath:"data-entry/Radio.stories.tsx",rules:[3,6,13,23]},{name:"Popover",group:"data-display",tagline:"Radix-backed floating panel anchored to a trigger \u2014 always compose with PopoverTrigger + PopoverContent; never use a raw div overlay.",props:[{name:"open",type:"boolean",description:"Controls open state in controlled mode. Pair with onOpenChange."},{name:"defaultOpen",type:"boolean",defaultValue:"false",description:"Initial open state for uncontrolled usage."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Callback fired when the popover open state changes. Required when using controlled mode (open prop)."},{name:"modal",type:"boolean",defaultValue:"false",description:"When true, interaction outside the popover is blocked and focus is trapped inside (Radix Root prop)."},{name:"align",type:"'start' | 'center' | 'end'",defaultValue:'"center"',description:"PopoverContent prop. Horizontal alignment of the popover relative to the trigger."},{name:"sideOffset",type:"number",defaultValue:"4",description:"PopoverContent prop. Distance in pixels between the popover panel and its anchor."},{name:"side",type:"'top' | 'right' | 'bottom' | 'left'",defaultValue:'"bottom"',description:"PopoverContent prop. Which side of the trigger the panel prefers to open on (auto-flips on overflow)."},{name:"asChild",type:"boolean",defaultValue:"false",description:"PopoverTrigger prop. Merges trigger props onto the immediate child element (e.g. a Button) instead of rendering an extra DOM node. Strongly recommended to avoid a wrapping <button>."},{name:"flush",type:"boolean",defaultValue:"false",description:"PopoverContent prop. The panel's CONTENT owns its inset: the popover zeroes its own --popover-space-inset so a Command list, a menu or a table runs edge to edge and draws its separators across the full width. Reach for it whenever the child already paints its own rows; leave it off for prose panels, which want the panel padding."},{name:"className",type:"string",description:"PopoverContent prop. Extra Tailwind classes merged onto the panel (default: w-72 p-4 rounded-md border shadow-md z-50)."}],usage:["DO compose: <Popover> \u2192 <PopoverTrigger asChild> \u2192 <Button/> and <PopoverContent>. All four parts are required for any popover to function; omitting PopoverTrigger or PopoverContent produces nothing.","DO set `<PopoverContent flush>` when the panel holds a Command list, a menu or a table \u2014 the child owns its own inset, so its rows and separators reach the panel edges. Never zero the padding with a utility on className: that is a per-call-site constant no service theme can retune, while `flush` keeps the inset on --popover-space-inset.","DO use asChild on PopoverTrigger when the trigger is already a Button or link \u2014 this avoids a nested <button><button> violation and extra DOM nesting.","DO use controlled mode (open + onOpenChange) when external code must open/close the popover programmatically (e.g., form validation reveal, keyboard shortcut). For toggle-only interactions, uncontrolled (defaultOpen) is simpler.","DO structure panel content with PopoverHeader > PopoverTitle + PopoverDescription for labelled panels. This is purely presentational but establishes the correct font-weight and muted-foreground on the description.","DON'T hand-roll a floating div or use a CSS show/hide toggle \u2014 Popover provides portal rendering, focus trap, Escape-to-close, and ARIA automatically.","DON'T place a Popover inside a Dialog without setting modal={false} on the Popover \u2014 nested modals conflict with Radix's focus management and produce stuck focus."],useCases:["Advanced filter panel: a 'Filters' Button triggers a Popover containing filter inputs (date range, status selects); panel width overridden via className='w-96'.","Row action menu overflow: when a DataTable row has too many actions for inline display, a Popover holds the secondary actions (Edit, Archive, Delete) without navigating away.","Contextual help / tooltip-rich: a small '?' icon button opens a Popover with PopoverTitle + PopoverDescription explaining a form field \u2014 richer than a Tooltip but less intrusive than a Dialog.","Inline record preview: clicking a reference number in an invoice list opens a Popover showing a summary card of the linked document before the user decides to navigate.","Column visibility picker: a 'Columns' button above a DataTable opens a Popover containing checkboxes to show/hide columns, with controlled state managed in parent.","Quick-edit cell: for an admin table, clicking a status badge opens a Popover with a RadioGroup to change status in-place without a full Dialog."],related:["Tooltip \u2014 use Tooltip for brief, non-interactive label-like hints (hover-only, no inputs). Use Popover when the floating content is interactive (buttons, inputs, forms).","Dialog/Sheet \u2014 use Dialog or Sheet for full modal actions that require user confirmation or significant input. Use Popover for lightweight, anchor-relative panels that dismiss on outside click.","DropdownMenu \u2014 use DropdownMenu for a flat list of clickable actions or links. Use Popover when the floating panel needs arbitrary layout (forms, grids, rich content) rather than a menu list."],example:`import {
|
|
1520
1585
|
Popover,
|
|
1521
1586
|
PopoverTrigger,
|
|
1522
1587
|
PopoverContent,
|
|
@@ -1964,7 +2029,7 @@ export function NotifyRow() {
|
|
|
1964
2029
|
recoveryCodes={recoveryCodes}
|
|
1965
2030
|
onAcknowledge={finish}
|
|
1966
2031
|
labels={labels}
|
|
1967
|
-
/>`,storyPath:"feedback/TwoFactorSetup.stories.tsx",rules:[]},{name:"AuthDivider",group:"layout",tagline:"The auth-scoped PRESET over `Separator label` \u2014 a localized conjunction between two equal rules.",props:[{name:"label",type:"string",required:!0,description:"Localized conjunction such as \u201Cor\u201D."},{name:"className",type:"string",description:"Optional structural class override."}],usage:["DO use AuthDivider only INSIDE an auth form.
|
|
2032
|
+
/>`,storyPath:"feedback/TwoFactorSetup.stories.tsx",rules:[]},{name:"AuthDivider",group:"layout",tagline:"The auth-scoped PRESET over `Separator label` \u2014 a localized conjunction between two equal rules.",props:[{name:"label",type:"string",required:!0,description:"Localized conjunction such as \u201Cor\u201D."},{name:"className",type:"string",description:"Optional structural class override."}],usage:["DO use AuthDivider only INSIDE an auth form. It is a thin preset over `<Separator label>`: it re-points the --separator-* knobs at the --auth-shell-divider-* layer (the 11px auth micro-scale, the auth rule/label colours), so using it elsewhere drags auth geometry into an unrelated screen.","DON'T use it for a message stream's day divider or a \"new messages\" watermark \u2014 that is `<Separator label>` with a `labelAlign` and a `tone`. Misusing AuthDivider recreates the exact defect the preset removed: a service retuning its login divider silently retuned every divider in its chat."],useCases:["An \u201Cor\u201D conjunction between a credential form and a provider action row"],related:["Separator \u2014 the primitive this presets; use it directly for any labelled rule outside auth."],example:`import { AuthDivider } from "@godxjp/ui/layout";
|
|
1968
2033
|
|
|
1969
2034
|
<AuthDivider label="or" />`,storyPath:"layout/AuthDivider.stories.tsx",rules:[44,45]},{name:"AuthFooter",group:"layout",tagline:"Compact hosted-auth footer for consumer-owned product, legal, privacy, and locale content.",props:[{name:"product",type:"ReactNode",required:!0,description:"Product identity content."},{name:"terms",type:"ReactNode",required:!0,description:"Terms link or localized text."},{name:"privacy",type:"ReactNode",required:!0,description:"Privacy link or localized text."},{name:"locale",type:"ReactNode",description:"Optional consumer-owned locale control."},{name:"className",type:"string",description:"Optional structural class override merged onto the footer root."}],usage:["Pass real links and locale controls from the consumer; AuthFooter never invents navigation.","DO drop it into AuthShell's `footer` slot \u2014 that slot supplies the contentinfo landmark, so AuthFooter itself renders a plain div and can also sit inside an existing footer without nesting landmarks.","AuthFooter owns ONLY the geometry: mono ramp, wrap, and a `\xB7` separator between the slots that are actually PRESENT (omit `locale` and its separator disappears). Don't hand-write separators into the slot content.","Public type: `AuthFooterProp` (alias `AuthFooterProps`) from `@godxjp/ui/layout` \u2014 registered in the prop registry, not a local interface.","Retune the line through `--auth-footer-content-gap` / `--auth-footer-text-font-size`, never page CSS (rule #45)."],example:`import { AuthFooter } from "@godxjp/ui/layout";
|
|
1970
2035
|
|
|
@@ -2011,7 +2076,7 @@ import { Badge } from "@godxjp/ui/data-display";
|
|
|
2011
2076
|
hasActiveFilters={hasFilters}
|
|
2012
2077
|
resultCount={rows.length}
|
|
2013
2078
|
actions={<Button onClick={openCreate}>Add member</Button>}
|
|
2014
|
-
/>`,storyPath:"navigation/FilterBar.stories.tsx",rules:[]},{name:"PermissionMatrix",group:"data-display",tagline:"Domain data is 100% consumer-supplied.",props:[{name:"roles",type:"{ id: string; name: string; description?: string; locked?: boolean }[]",required:!0,description:"Role COLUMNS in render order. `locked` keeps that role's cells read-only (with a localized lock badge) even in an editable matrix."},{name:"permissions",type:"{ id: string; name: string; description?: string; group?: string }[]",required:!0,description:"Permission ROWS in render order, with an optional category caption."},{name:"grants",type:"ReadonlySet<string> | { roleId: string; permissionId: string }[]",required:!0,description:"The grant relation: the grantKey(roleId, permissionId) Set from @godxjp/ui/lib/permission-grid (canonical, O(1)), or a plain pair array normalized through the same encoding."},{name:"onGrantChange",type:"(roleId: string, permissionId: string, granted: boolean) => void",description:"Its PRESENCE makes the matrix editable (real Checkbox cells, Space toggles). Omitted, the matrix is the canonical read-only \u2713/\u2014 grid."},{name:"readOnly",type:"boolean",defaultValue:"false",description:"Force the read-only grid even when onGrantChange is present (viewer permission)."},{name:"compare",type:"[string, string] | null",description:"Two role ids compared side by side: their columns tint, and rows where they disagree carry a localized difference badge."},{name:"diffOnly",type:"boolean",defaultValue:"false",description:"With `compare`, keep only the rows the two roles disagree on (\u5DEE\u5206\u306E\u307F)."},{name:"loading / denied / error / empty / onRetry",type:"boolean | ReactNode / handler",description:"`true` renders the built-in localized surface; a node replaces it; onRetry adds Retry to the built-in error only."},{name:"label",type:"string",description:"Accessible table name (localized default)."}],usage:["DO import it \u2014 it is a real export from @godxjp/ui/data-display (the
|
|
2079
|
+
/>`,storyPath:"navigation/FilterBar.stories.tsx",rules:[]},{name:"PermissionMatrix",group:"data-display",tagline:"Domain data is 100% consumer-supplied.",props:[{name:"roles",type:"{ id: string; name: string; description?: string; locked?: boolean }[]",required:!0,description:"Role COLUMNS in render order. `locked` keeps that role's cells read-only (with a localized lock badge) even in an editable matrix."},{name:"permissions",type:"{ id: string; name: string; description?: string; group?: string }[]",required:!0,description:"Permission ROWS in render order, with an optional category caption."},{name:"grants",type:"ReadonlySet<string> | { roleId: string; permissionId: string }[]",required:!0,description:"The grant relation: the grantKey(roleId, permissionId) Set from @godxjp/ui/lib/permission-grid (canonical, O(1)), or a plain pair array normalized through the same encoding."},{name:"onGrantChange",type:"(roleId: string, permissionId: string, granted: boolean) => void",description:"Its PRESENCE makes the matrix editable (real Checkbox cells, Space toggles). Omitted, the matrix is the canonical read-only \u2713/\u2014 grid."},{name:"readOnly",type:"boolean",defaultValue:"false",description:"Force the read-only grid even when onGrantChange is present (viewer permission)."},{name:"compare",type:"[string, string] | null",description:"Two role ids compared side by side: their columns tint, and rows where they disagree carry a localized difference badge."},{name:"diffOnly",type:"boolean",defaultValue:"false",description:"With `compare`, keep only the rows the two roles disagree on (\u5DEE\u5206\u306E\u307F)."},{name:"loading / denied / error / empty / onRetry",type:"boolean | ReactNode / handler",description:"`true` renders the built-in localized surface; a node replaces it; onRetry adds Retry to the built-in error only."},{name:"label",type:"string",description:"Accessible table name (localized default)."}],usage:["DO import it \u2014 it is a real export from @godxjp/ui/data-display (the lesson: a docs page is not importable). Never hand-compose the sticky-column grid per app.","DO keep grants in the lib/permission-grid grantKey Set form when you already hold role:permission tuples \u2014 the pair-array form exists for convenience and is normalized through the same encoding.","DO put it in a Card with CardContent flush: <Card><CardContent flush><PermissionMatrix \u2026/></CardContent></Card>. Below its natural measure the grid scrolls horizontally INSIDE its own container (390px keeps the sticky permission column).","DO NOT encode platform roles/permissions in the library \u2014 roles, permissions and grants are consumer data by contract.","DO NOT pass compare pickers/toggles into the matrix \u2014 compose Select + Switch beside it and drive `compare`/`diffOnly` (see the showcase)."],useCases:["RBAC role tab on a service detail screen: read-only matrix + role compare.","Org role editor: editable matrix (onGrantChange) with the system role locked.","Permission-denied / failed read states without hand-rolling: denied / error / onRetry."],related:["lib/permission-grid \u2014 the pure grant/diff data helpers the matrix (and any custom RBAC UI) shares.","DataTable \u2014 general tabular data with sorting/selection/pagination; PermissionMatrix is the fixed role-grid specialization with a sticky FIRST column (which DataTable cannot pin).","ServiceRolePanel \u2014 the master-detail roles surface a matrix typically renders inside."],example:`import { Card, CardContent, PermissionMatrix } from "@godxjp/ui/data-display";
|
|
2015
2080
|
import { grantKey } from "@godxjp/ui/lib/permission-grid";
|
|
2016
2081
|
|
|
2017
2082
|
const grants = new Set(rolePermissions.map((rp) => grantKey(rp.roleId, rp.permissionId)));
|
|
@@ -2046,7 +2111,7 @@ import { ServiceRolePanel } from "@godxjp/ui/layout";
|
|
|
2046
2111
|
{(role) =>
|
|
2047
2112
|
role && <PermissionMatrix roles={[role]} permissions={permissions} grants={grants} readOnly={role.locked} />
|
|
2048
2113
|
}
|
|
2049
|
-
</ServiceRolePanel>`,docPath:"layout/service-role-panel.tsx",storyPath:"layout/ServiceRolePanel.stories.tsx",rules:[24,40]}];function I(t){let a=t.trim().toLowerCase();return v.find(e=>e.name.toLowerCase()===a)}function F(t){return v.filter(a=>a.group===t)}var k=[{name:"ValueProp<T = string>",concept:"Abstract controlled value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs","SearchSelect"]},{name:"DefaultValueProp<T = string>",concept:"Abstract uncontrolled initial value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs"]},{name:"OnValueChangeProp<T = string>",concept:"Callback for abstract value changes. DOM events continue to use onChange.",values:["(value: T) => void"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Transfer","settings pickers"]},{name:"OpenProp / DefaultOpenProp / OnOpenChangeProp",concept:"Disclosure state.",values:["boolean","(open: boolean) => void"],usedBy:["Dialog","Sheet","Popover"]},{name:"SizeProp",concept:"Shared public size names.",values:["xs","sm","md","lg"],usedBy:["Button","Steps","Switch"],notes:"Component-specific subsets must be documented. Old alias small is sm."},{name:"ToneProp",concept:"Semantic status/color intent.",values:["default","success","warning","destructive","info","muted","neutral"],usedBy:["Badge","Alert"],notes:"Status values belong in tone, not variant."},{name:"GapProp",concept:"Shared layout gap scale.",values:["xs","sm","md","lg","xl"],usedBy:["Flex"],notes:'The single shared gap scale on Flex \u2014 the one layout primitive (default direction="row"; use direction="col" for vertical rhythm; these replaced the removed Stack/Inline).'},{name:"TitleProp",concept:"Primary heading text.",values:["React.ReactNode"],usedBy:["PageContainer","PageHeader","EmptyState","Dialog"]},{name:"DensityProp",concept:"Page/subtree density.",values:["compact","default","comfortable"],usedBy:["PageContainer"]},{name:"CenteredShellPresetProp",concept:"Named whole-page SHELL contract for CenteredShell \u2014 the page measure, section rhythm, chrome flatness and hero heading tier, all owned by component tokens.",values:["default","public-landing"],usedBy:["CenteredShell"],notes:'public-landing is the PUBLIC marketing/product landing geometry: one 67.5rem measure shared by the header bar, the centred column and the footer, the section rhythm, flat elevation-free cards and the hero h1 tier. "default" emits NO data-preset at all, so the existing shell box is untouched. Pair with `Flex hideBelow`/`hideFrom` for header region visibility \u2014 a landing must never own a page-local media query or a max-width wrapper.'},{name:"TablePresetProp",concept:"Named collection contract for a table \u2014 how a dense collection behaves when its container gets narrow.",values:["default","action-collection"],usedBy:["Table","DataTable"],notes:"action-collection swaps the desktop intrinsic column widths for table-layout: fixed + the token-owned column PRIORITY measures below `collapseBelow`, so a five-column approval queue keeps every column \u2014 including row actions \u2014 inside a 390px frame with no page-local CSS, no hidden column and no horizontal scroll. It changes the SIZING model only: no display change, no role rewriting, no card transformation, so table semantics and screen-reader navigation are identical at every width. ONE contract, two entry points: the `Table` primitive (mark `priority` on each TableHead/TableCell yourself) and the TanStack `DataTable` (mark `priority` on the ColumnDef; it stamps both cells for you, and additionally releases the surface's `--table-surface-min-inline-size` floor). Both read the same `--table-action-collection-*` tokens \u2014 there is no parallel DataTable family."},{name:"TableColumnPriorityProp",concept:"Relative importance of a table column, used by the action-collection preset to allocate the narrow-frame measure.",values:["primary","secondary","meta","actions"],usedBy:["Table","DataTable"],notes:"On the raw `Table`, set it on BOTH the TableHead and the TableCell of a column; on `DataTable` set it once on the `ColumnDef` and it is stamped onto the <th> and every <td>. `actions` reserves its measure first so the row-action affordance can never be pushed outside the viewport; a column with no priority takes the remaining space (the free-text column)."},{name:"AuthShellPresetProp",concept:"Named hosted-identity FLOW MEASURE for AuthShell \u2014 the card max-width plus the desktop and mobile page gutters, all owned by component tokens.",values:["default","device-authorization","context-selection"],usedBy:["AuthShell"],notes:'Orthogonal to AuthShell\'s `variant` (which owns control density + heading size) and applied after it, so `variant="canonical" preset="device-authorization"` composes. device-authorization = 380px card / 5px inline gutter at 390; context-selection = 25rem card, edge-to-edge on mobile. Selecting a preset REPLACES any consumer-side --auth-shell-card-max-width override \u2014 a page-local width is the anti-pattern these presets exist to remove.'},{name:"AvatarShapeProp",concept:"Avatar geometry \u2014 WHAT the identity mark represents: a person or an entity. Deliberately NOT the control ShapeProp (default|pill|sharp), whose `sharp` is --radius-sharp: 0 and cannot express the rounded rect an entity mark needs.",values:["circle","square"],usedBy:["Avatar"],notes:'circle (default, inert) = the round --radius-pill person avatar on the muted surface; square = the entity-header organization/service mark \u2014 a compact rounded square on the brand surface driven by --avatar-square-{radius,size,background,foreground}. Choose by meaning, never by look: a className="rounded-md bg-primary" override on the call site is the anti-pattern this prop removes.'}];function B(t){let a=t.trim().toLowerCase().replace(/prop(?:<.*>)?$/i,"");return k.find(e=>e.name.toLowerCase().replace(/prop(?:<.*>)?$/i,"")===a)}var C=[{name:"--wa-*",category:"primitive",tier:"primitive",role:"Neutral decorative Japanese accent primitives for charts/tags/decoration only."},{name:"--chart-1..6",category:"primitive",tier:"primitive",role:"Neutral decorative chart series palette. The @godxjp/ui/charts components (LineChart/BarChart/AreaChart/PieChart) read these by series index automatically \u2014 a service rethemes every chart at once by overriding --chart-1..6; per-series/per-slice overrides go through the component's series.color / colors props."},{name:"--space-0..12",category:"primitive",tier:"primitive",role:"Raw spacing scale."},{name:"--icon-size-{2xs,xs,sm,md,lg,xl,2xl,3xl,4xl}",category:"primitive",tier:"primitive",role:'Raw ICON scale \u2014 10 / 12 / 14 / 16 (md, the default) / 20 / 24 / 36 / 40 / 48 px. Icon was the last geometric axis with no scale: 28 component tokens declared raw numbers and between them used only these nine values, so this NAMES an existing vocabulary rather than inventing one. It is a FIXED LIST, not a `base * ratio^n` scale like --font-size-*, and deliberately so: 14/16 = 0.875 but 20/16 = 1.25, no single ratio generates the steps. Type can sit between pixels because hinting and antialiasing carry it; a 1px-stroke glyph cannot, so every step lands on a whole pixel at a 16px root. NOT --scaling-multiplied here: density is opted into by the tokens that want it (`calc(var(--icon-size-md) * var(--scaling))`, as --control-icon-size does), which keeps the scale itself the crisp whole-pixel list. TIER 1 vs TIER 2 \u2014 a value used in MORE THAN ONE place belongs on the scale and a service retunes it once; a value used in exactly ONE place (a 6px status dot) does NOT, and is set at the call site on that component\'s own --*-icon-size / --*-glyph-size token (`style={{ "--menu-icon-size": "6px" }}` or a `[data-\u2026]`-scoped theme rule). An inline custom property wins by inheritance proximity, so tier 2 never needs !important, a :root override, or a fork.'},{name:"--stroke-{hairline,sm,md,lg,xl,2xl}",category:"primitive",tier:"primitive",role:"Raw STROKE scale \u2014 the thickness of a painted LINE: 1 (hairline) / 1.5 / 2 (md, the emphasis stroke) / 3 / 4 / 6 px. The gh#324 census called `width` the worst axis in the system at 91% raw; the census was right and the diagnosis was not, because `-width` is THREE concerns wearing one suffix \u2014 the thickness of a drawn line, the measure of a container (a dialog, a reading column, an auth card) and the content width of a field (how wide a picker must be to hold its longest label). Only the first is a vocabulary, and it repeats hard: six values across ~20 tokens spanning borders, focus rings, accent rails, selected-state markers and presence strokes. The other two are tier 2 by nature \u2014 their values appear in exactly one place \u2014 and are deliberately NOT on a scale. PX, NEVER REM: a stroke is a device line whose job is to read as one crisp rule at any type size, so it must not grow with the root font-size (`--slider-track-height: 0.375rem` is a different decision and stays put). NOT --scaling-multiplied: --scaling is a DENSITY knob, and a 1px divider at 0.92px would blur rather than tighten. --focus-ring-width is a member of this scale (`var(--stroke-md)`), so retuning line weight retunes the rings with it. --stroke-hairline sits BELOW --stroke-sm rather than being called xs because 1px is not really a step: it is the device hairline, the one thickness a service retunes the EXISTENCE of rather than the value (cardinal rule #44)."},{name:"--band-height-{xs,sm,md,lg,xl,2xl,3xl}",category:"primitive",tier:"primitive",role:"Raw BAND-HEIGHT scale \u2014 the vertical extent of a horizontal strip that content is centred in: 24 / 28 / 32 (md, the default control and row band) / 36 / 44 (the WCAG 2.2 AA touch floor, rule #24) / 48 / 56 px. `height` split the same way `width` did: a control, a table row, a menu item, a sidebar nav row and the app-shell top bar are ONE decision sharing seven values, each declared by two to five tokens, while a chart plot's height, a transfer pane's min-height and a popover's max-height are container measures chosen per surface with almost no repeated value and stay literal. gh#324's own case for this is that `PageContainer`'s chrome band had no height token while `AppShell`'s bar did, so nothing could be aligned to it \u2014 the axis had no name. NOT A SUBSTITUTE FOR --control-height-*: the control tier is a RUNTIME LADDER (it multiplies by --scaling, steps \xB1--space-1 for sm/lg/xs, and `@media (pointer: coarse)` lifts the whole thing to the 44px tap floor); this scale is the STATIC vocabulary the ladder is ANCHORED on (`--control-height-default: var(--band-height-md)`), exactly as --font-size-base anchors the type scale. Point a band token at --control-height-* to \"reuse a step\" and you silently enrol it in density AND in the coarse-pointer growth \u2014 that is a geometry change, not a rename, and it is why --table-row-height-default reads var(--band-height-md) instead. NOT --scaling-multiplied here: whether a band breathes with density is a per-token decision (--control-height opts in, --app-shell-bar-height deliberately does not)."},{name:"--overlay-z-index",category:"semantic",tier:"semantic",role:"The ONE stacking layer every portaled overlay sits on \u2014 Tooltip, Popover, Select, DropdownMenu, ContextMenu/Menubar, Sheet (overlay AND panel), and since #319 also Dialog, AlertDialog and the CommandPalette, scrim and content alike. Each of those used to hard-code `z-50` independently, so an app that mounts the library under its own stacking context (a sticky masthead, a third-party chat widget) had to fight a separate literal per primitive \u2014 and whichever one it missed rendered underneath. Stacking is a SYSTEM decision, not a per-primitive one: raise every overlay together by overriding this once. Default 50, byte-identical to the literals it replaced. The DataTable sticky header deliberately stays BELOW it, so a menu opened from a sticky column header still wins. NOTE: overlays render into a portal on document.body, so a scoped `[data-tenant]` / `.dark` override must also sit on the portal container to reach them."},{name:"--font-size-*",category:"primitive",tier:"primitive",role:"Raw typography scale."},{name:"--font-sans-base",category:"semantic",tier:"semantic",role:`The default sans face. FONT-AGNOSTIC by default (pure system stack) \u2014 the library ships no hardcoded brand font. Override this for one face everywhere. --font-family-sans defaults to it; --font-family-display / --font-family-body default to --font-family-sans (override those for a dual display+body brand). Consumers supply the actual @font-face (next/font, @fontsource, self-host); the opt-in @godxjp/ui/styles/fonts OVERWRITES this (and --font-sans-vi) with the bundled DXS stack \u2014 "Noto Sans JP" primary (incl. the Vietnamese coverage) with "M PLUS 2" as the fallback. BUNDLE CHANGED IN v18.12.20 (product override, direct instruction): v16 bundled Noto Sans JP + Montserrat, v18.12.0\u201318.12.19 bundled M PLUS 2 + Noto Sans JP, v18.12.20+ bundles Noto Sans JP + M PLUS 2 \u2014 if a design spec names an earlier bundle's faces, set the tokens explicitly instead of relying on the bundle. IMPORT ORDER IS LOAD-BEARING: styles/fonts must be imported AFTER styles/base, because foundation.css declares this system-stack default on :root at the same specificity (0,1,0) and the later declaration wins (the all-in-one @godxjp/ui/styles entry already orders it correctly \u2014 issue #210).`},{name:"--font-sans-{ja,ko,vi,zh-hans,zh-hant}",category:"semantic",tier:"semantic",role:'Per-language font SLOT tokens. styles/base.css wires each [lang] to read its slot with --font-sans-base as fallback, so a consumer switches a locale\'s face by setting e.g. `--font-sans-ja: "Noto Sans JP", var(--font-sans-base)` \u2014 NO [lang] selectors to write. Empty by default. zh-hans matches lang zh|zh-Hans|zh-CN; zh-hant matches zh-Hant|zh-TW. Deciding which @font-face ships on which locale route is the consumer\'s font pipeline, not the library. The opt-in @godxjp/ui/styles/fonts only fills --font-sans-vi (with the bundled "Noto Sans JP", "M PLUS 2" stack); the other slots stay empty and fall back to --font-sans-base.'},{name:"--duration-{fast,base,slow}",category:"primitive",tier:"primitive",role:"Motion durations (150 / 250 / 500ms). Read these instead of a literal `0.5s` for enter/transition timing (rule #2). the reference design keeps motion short; honour `prefers-reduced-motion` at the call site."},{name:"--ease-{standard,emphasized,decelerate,accelerate}",category:"primitive",tier:"primitive",role:"Motion easing curves. `standard` for most transitions, `emphasized` for entrances/overlays (the vaul drawer curve), `decelerate` for settling in, `accelerate` for exits. Read instead of a literal `cubic-bezier(\u2026)`."},{name:"--reveal-distance",category:"primitive",tier:"primitive",role:"Distance (10px) a revealed element travels on enter (translateY/-X). Read instead of a literal `translateY(10px)` for staggered reveals."},{name:"--reveal-stagger-step",category:"primitive",tier:"primitive",role:"One step (60ms) of the Reveal stagger ladder \u2014 `<Reveal delay={n}>` waits n \xD7 this before entering, so a column of reveals cascades. Read instead of a literal `.04s`/`.09s` per-item delay."},{name:"--duration-loop",category:"primitive",tier:"primitive",role:"The AMBIENT/LOOPING interval (1400ms) \u2014 the cycle member of the motion tier, and the one gh#313 added. `--duration-{fast,base,slow}` time a TRANSITION (A to B, then stop); a loop needs an interval, so every ambient affordance in the product breathes at one rate. `Activity` reads it through `--activity-interval`."},{name:"--activity-interval",category:"component",tier:"component",role:"Per-component override of the ambient loop interval (defaults to `var(--duration-loop)`). A service tunes its typing/sync rhythm without moving every loop in the product (cardinal rule #45). Declared in the MOTION tier (tokens/foundation.css) beside `--reveal-stagger-step`, not in tokens/components/activity.css, because component-token names must carry a geometry/colour property word and there is none for a duration."},{name:"--activity-stagger-step",category:"component",tier:"component",role:"Offset (160ms) between the three `Activity variant='dots'` marks \u2014 the LOOP counterpart to `--reveal-stagger-step`, named to match. In RTL the dots reverse with the flex row, so the cascade follows the reading direction with no extra rule. Declared in the motion tier for the same reason as `--activity-interval`."},{name:"--activity-mark-size",category:"component",tier:"component",role:"Diameter (0.25em) of one `dots` mark. `em`-based so the whole mark tracks `--activity-font-size-*` (and therefore `--font-size-base` and the density axis) instead of pinning a px per size step."},{name:"--activity-mark-offset",category:"component",tier:"component",role:"Travel (0.25em) of one `dots` step \u2014 the LOOP counterpart to `--reveal-distance`, named to match. It is ALSO the height the mark row reserves, so a dot at the top of its arc is never clipped and the row never grows mid-cycle."},{name:"--activity-mark-rest-alpha",category:"component",tier:"component",role:"Opacity (0.35) of a mark at the trough of its cycle. Deliberately never 0: a mark that vanishes reads as a rendering bug, and it is what makes the `prefers-reduced-motion` frame (animation dropped) land on a legible resting state."},{name:"--activity-pulse-mark-size",category:"component",tier:"component",role:"Diameter (0.5em) of the single `variant='pulse'` mark \u2014 bigger than one `dots` dot because it carries the whole affordance alone."},{name:"--activity-gap",category:"component",tier:"component",role:"Mark-to-mark and mark-to-label gap (defaults to `var(--space-1)`)."},{name:"--activity-font-size-{xs,sm,md,lg}",category:"component",tier:"component",role:"Root size ladder for `Activity`. The mark is em-based, so this ONE value scales the whole indicator; the steps mirror the `Text` size the label renders at, so mark and label stay optically paired."},{name:"--activity-bar-{width,height,radius,segment-width,track-alpha}",category:"component",tier:"component",role:"The indeterminate `variant='bar'` sweep: track measure (4rem \u2014 an inline measure beside a label, not a 100% fill), thickness, corner, the moving segment's share of the track (40%), and the track's tint alpha (0.2)."},{name:"--activity-color",category:"component",tier:"component",role:"Mark ink for `Activity`. ROLE-MIRROR KNOB \u2014 declared `initial` at :root with the role default at the CALL SITE (`hsl(var(--activity-color, var(--muted-foreground)))`), so a scoped `[data-tenant]` / `.dark` override of `--muted-foreground` still reaches it (docs/TOKENS.md \xB7 'Role-mirror knobs MUST be initial'). It retints the default `tone='muted'`; an explicit non-muted `tone` sets its own semantic role."},{name:"--shadow-color",category:"primitive",tier:"primitive",role:"RGB channels (space-separated, e.g. `12 26 49`) that tint the WHOLE elevation ramp (--shadow-xs..2xl) at once. Default `0 0 0`. Single-brand :root only \u2014 a scoped override does not re-resolve the ramp (the steps compute at :root); for a scoped/multi-tenant card lift set --card-shadow to a literal value."},{name:"--shadow-glow",category:"primitive",tier:"primitive",role:"Opt-in brand GLOW halo layered on the primary CTA's resting shadow. Default invisible (`0 0 0 0 transparent`, valid inside a comma shadow list). A service sets the whole value, e.g. `--shadow-glow: 0 8px 20px hsl(var(--primary) / .32)` \u2014 works scoped under [data-tenant] because the service declares it inside the scope."},{name:"--focus-ring-color",category:"primitive",tier:"primitive",role:"Themeable hue of EVERY keyboard-focus ring (HSL components, default var(--ring)). A leaf token, so it re-resolves at the element that paints the ring \u2014 override it once, even scoped under [data-tenant], to retint all focus rings. Pair with --focus-ring-width."},{name:"--focus-ring-width",category:"primitive",tier:"primitive",role:"Thickness (2px) of the solid keyboard-focus ring. Leaf token \u2014 :focus-visible rules read it directly as `0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color))`, never via an intermediate composite (which would freeze at :root). Propagates scoped."},{name:"--gradient-{brand,hero,glow}",category:"primitive",tier:"primitive",role:"Opt-in decorative gradient fills, default `none`. --gradient-hero paints the PageContainer header (hero banner); --gradient-glow paints the AppShell .app-main (ambient brand wash); --gradient-brand is a spare. A service sets the full gradient, e.g. `--gradient-glow: radial-gradient(60% 80% at 50% 0%, hsl(var(--primary) / .25), transparent)`."},{name:"--primary",category:"semantic",tier:"semantic",role:"Action color role."},{name:"--brand / --brand-foreground",category:"semantic",tier:"semantic",role:'The IDENTITY role \u2014 the canonical GoDX emerald (light #009766 = oklch(0.595 0.137 162.94); dark lifts lightness only, #00b87c). It is what `<Logo mark="godx">` / `tone="success">` / the godx wordmark / the `@godxjp/ui/email` brand capsule paint, and it is distinct from THREE lookalikes: --primary (the ACTION colour \u2014 re-theme it freely, the mark never moves), --success (\u82E5\u7AF9 #68be8d, the STATUS green for badges/progress/"saved" text \u2014 the mark borrowed this before gh#250 and shipped \u0394E76 \u2248 17.5 off canonical), and --text-brand (the blue brand-NUMERAL text slot behind the Tailwind `text-brand` utility). There is no `bg-brand` utility: `--color-brand` is already taken by --text-brand, so identity surfaces read `hsl(var(--brand))` directly in component CSS. Re-brand a service by overriding --brand once (scoped `[data-tenant]` works \u2014 every logo knob resolves it at the call site).'},{name:"--success",category:"semantic",tier:"semantic",role:'Success STATUS role (\u82E5\u7AF9 wakatake #68be8d) \u2014 badges, alerts, progress fills, timeline done dots, `tone="success"` text. NOT the product identity: the GoDX mark reads --brand.'},{name:"--warning",category:"semantic",tier:"semantic",role:"Warning status role."},{name:"--destructive",category:"semantic",tier:"semantic",role:"Destructive/error status role."},{name:"--info",category:"semantic",tier:"semantic",role:"Information status role."},{name:"--attention",category:"semantic",tier:"semantic",role:"Attention status role."},{name:"--border / --input",category:"semantic",tier:"semantic",role:"TWO roles, NOT synonyms \u2014 never set one to the other. --border is DECORATIVE chrome: table rules, card edges, section dividers, Separator. WCAG 2.2 SC 1.4.11 does not reach it (a divider is not \"required to identify a component or its state\") and this system's dense JP grid depends on it staying quiet \u2014 light `30 7% 83%`, dark `45 6% 22%`. --input is the CONTROL BOUNDARY: the 1px edge of Input/Textarea/Select/outline Button/TagInput/the composite date field/the topbar search (the Tailwind `border-input` utility reads it), plus the Switch's unchecked track fill. A field has no fill of its own and no shadow, so that edge is the whole visual claim that you may type there \u2014 it owes SC 1.4.11's 3:1 against EVERY surface a control sits on, not just the plain page: light `30 7% 53%` (3.47:1 on --background/--card/--popover, 3.18:1 on --muted/--secondary, 3.35:1 on a zebra row), dark `45 6% 47%` (4.22:1 on --background, 3.88:1 on --card/--popover, 3.17:1 on --muted). They shipped sharing one value and put every text field at 1.46:1. A service re-theming neutrals MUST move the two independently; `src/tokens/__tests__/input-boundary-contrast.test.ts` recomputes the ratios and fails below 3:1 or if the two roles are re-coupled. To quieten the Switch off-track without dragging --input back down, override --switch-unchecked-background instead."},{name:"--page-header-divider",category:"semantic",tier:"semantic",role:"PageContainer header bottom divider. Default none; a service theme opts in with `1px solid hsl(var(--border))`."},{name:"--page-footer-divider",category:"semantic",tier:"semantic",role:"PageContainer footer top divider \u2014 the third page-chrome band, on the same contract as `--page-header-divider` and `--page-toolbar-divider` (its rule used to be a hard literal in layout.css, so a page could not turn it off at all). Declared `initial` and resolved at the CALL SITE as `var(--page-footer-divider, 1px solid hsl(var(--border)))`, so a scoped [data-tenant]/.dark override still reaches it. It is the ONE chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where the line separating the actions from the page content is the behaviour every existing page already draws \u2014 the old literal IS the fallback, so unset is byte-identical. Set `--page-footer-divider: none` when the footer content already carries its own frame (a chat composer is a bordered Card, and the shell's full-width rule otherwise stacks a second line right above it)."},{name:"--overlay-background",category:"semantic",tier:"semantic",role:"Modal scrim \u2014 the single backdrop colour shared by every overlay (Dialog, AlertDialog, Sheet, Drawer, AppShell mobile nav). Default `rgb(0 0 0 / 0.5)`. A service tints it once, e.g. a navy `rgb(12 26 49 / .55)`. HOW IT REACHES AN OVERLAY: each surface declares its own `--*-overlay-background` knob `initial` and resolves a SHARE of this colour at the call site, `color-mix(in srgb, var(--overlay-background) var(--*-overlay-alpha), transparent)`, so the calibrated per-surface depth survives (Sheet/drawer 40% = 0.2, Dialog 60% = 0.3) while ONE override here retints them all. NOTE: portaled overlays render outside a [data-tenant] subtree, so for multi-tenant scoping put the tenant attribute on the portal container too."},{name:"--page-toolbar-{background,pad-block,divider}",category:"semantic",tier:"semantic",role:"PageContainer `toolbar` band chrome \u2014 the FIXED strip between the page header and the (scrolling) body. All three knobs are quiet by default (rule #44). `--page-toolbar-background` (default `transparent`) is the band's GROUND: set it ONCE in a service theme \u2014 `--page-toolbar-background: hsl(var(--card));` \u2014 to lift the band off the page ground the way a chat channel's workflow rail or a collection's filter strip usually wants. It is bound at `:root` rather than `initial` because its default is a plain CSS keyword, not another role token, so there is nothing for a scoped override to re-resolve; it is the `background` shorthand, so a gradient works too. NEVER write `className=\"bg-card\"` on the strip at the call site \u2014 that is hand-laid page chrome, it is invisible to per-tenant theming, and it paints the strip rather than the band (so it misses the page gutters and the `measure` cap). `--page-toolbar-pad-block` (default 0) is the band's own block inset. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (the container's band gap is cancelled on the band itself \u2014 chrome is attached, not a third section floating between two voids), so there is no outside space to tune. It stays 0 even now that the band can be painted: a TRANSPARENT band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height is taken from the scroll viewport the slot exists to protect. The corollary is that a theme which PAINTS the band must also set the inset here (`--page-toolbar-pad-block: var(--space-2)`) \u2014 the two go together, and neither belongs in a `py-*` utility at the call site. `--page-toolbar-divider` is declared `initial` and resolved at the CALL SITE as `var(--page-toolbar-divider, var(--page-header-divider))` \u2014 so ONE `--page-header-divider` opt-in rules the whole page chrome consistently, a scoped [data-tenant]/.dark override of it still reaches the band (a `:root` binding would freeze it), and `--page-toolbar-divider: none` silences just the band. `variant=\"ghost\"` keeps the divider quiet regardless (it does NOT clear the background \u2014 ghost is about rules and pads, not grounds). The band's inline gutters and its `measure` cap are shared with the header and body \u2014 they are not separate knobs, which is what keeps the three bands aligned."},{name:"--page-title-font-size-chrome",category:"semantic",tier:"semantic",role:'PageContainer title step under `headerScale="chrome"` \u2014 a page whose top row IS chrome (a chat channel name, a mail subject, an IDE tab) rather than a document title. Default `var(--heading-h3)` = `--font-size-base` (14px), the body step, so the row reads as a label ON the surface instead of the page\'s headline; a consumer chat header measured 61px with a 20px h1 against a design that wanted ~40px at the `sm` step. Deliberately a THIRD knob beside `--page-title-font-size` / `--page-title-font-size-compact`: those two are one document title at two viewport sizes (a responsive step), this is a different KIND of page and holds at every width \u2014 the compact rule must not pull a chrome header back up to h2. Read ONLY when the prop is passed, so a document page never touches it (rule #44). A service retunes the chrome step here once; never override `--page-title-font-size` at a call site to fake it.'},{name:"--page-subtitle-font-size-chrome",category:"semantic",tier:"semantic",role:'PageContainer SUBTITLE step under `headerScale="chrome"` \u2014 the caption under a chrome top row (a channel purpose line, a mail preview), not a document\'s standfirst. Default `var(--font-size-2xs)` (ratio\u207B\xB2, ~11px), two steps below the document default `--font-size-base`. It is a fourth knob beside `--page-subtitle-font-size` / `-compact` for the same reason the title step is: those two are ONE document subtitle at two viewport sizes, this is a different KIND of page and holds at every width \u2014 and it has to out-rank the 720px compact rule, which is already a compound selector, or a chrome caption would step back UP on a phone. Two things it fixes at once. Hierarchy: at the document step the chrome subtitle rendered at `--font-size-base`, the IDENTICAL step the chrome TITLE takes, so a channel name and its purpose line read as one undifferentiated block. Height: the step drives the LINE BOX at the inherited `--line-height-body` (14px x 1.7 = 23.8px vs 11px x 1.7 = 18.9px), so a band whose whole point is to leave room for the transcript stops spending ~5px of it on a caption. Type only \u2014 line-height, colour and weight stay with the base rule, so a wrapped JA/VI purpose line keeps its rhythm. Read ONLY when the prop is passed (rule #44); a service retunes the caption step here once, never with a `text-*` utility at the call site.'},{name:"--page-pad-block-start-chrome",category:"semantic",tier:"semantic",role:"PageContainer TOP inset under `headerScale=\"chrome\"` \u2014 the second half of the same fact the title step answers: chrome sits ON the frame's edge. Default `0px`, so a chrome page opens flush; a document page keeps `--space-page-active-y` (24px, 16px below the 720px step) because a title needs air above it. Measured on a consumer chat screen: an otherwise correctly-sized channel head started at y=24 against a design that starts it at y=0, and those 24px came straight off the transcript viewport (617px in the design, 587px in the app). It is a knob rather than a literal (rule #44) so a service whose grid wants its chrome inset writes `--page-pad-block-start-chrome: var(--space-2)` once instead of forking the page shell, and it is read ONLY when the prop is passed, so no document page resolves it. Block-start only \u2014 the page's bottom edge belongs to `stickyFooter`, which zeroes it for its own reason."},{name:"--page-header-min-block-size-chrome",category:"semantic",tier:"semantic",role:"PageContainer BAND HEIGHT under `headerScale=\"chrome\"` \u2014 the third half of the same fact the title step and the flush top edge answer. A document header is content-height, correctly: a title is as tall as the title is. Chrome is furniture, and furniture needs a band that things centre INTO. Without one, the band's vertical centre is a function of its own copy \u2014 measured in Chromium on the catalogue page, 42.02px with an `extra` control and 40.38px without \u2014 so nothing in the page can be aligned to it and no service can aim at a number. Default `auto` (rule #44, the quiet state): no floor, content-height, byte-identical to every page shipped before the token existed, chrome pages included. The BAND-HEIGHT AXIS HAS ONE OWNER and it is `--app-shell-bar-height` (`--centered-shell-bar-height` already reads it), so the one line a service writes to put its page chrome on the shell bar's band is `--page-header-min-block-size-chrome: var(--app-shell-bar-height)` \u2014 the band becomes 48px and the title column and `extra` both centre at y=24 regardless of copy length. It is NOT that value by default because a chrome `PageContainer` is not necessarily inside an `AppShell` (the canonical chat composition renders one inside a bordered region, a SplitPane pane, a Card), and a band that is not trying to line up with a bar has no business inheriting that bar's height. A MIN, never a height: a taller `extra` (an `lg` control, a wrapped JA/VI channel name) still fits instead of overflowing. The header's own `--page-header-pad-bottom` lives INSIDE the band (border-box); under the canonical chrome composition (`variant=\"ghost\"`) that pad is 0 and the centring is exact."},{name:"--page-header-pad-bottom",category:"semantic",tier:"semantic",role:"PageContainer header bottom inset. Defaults to page top padding minus the section gap so the title band is vertically balanced."},{name:"--master-detail-rail-{compact,standard}",category:"semantic",tier:"semantic",role:"MasterDetail fixed rail track \u2014 18.75rem (300px) / 20rem (320px). The `railWidth` prop picks the preset; a service theme retunes the presets themselves. Consumers never author a grid track."},{name:"--master-detail-gap",category:"semantic",tier:"semantic",role:"Gap between the MasterDetail master and detail regions (default --space-stack-md), side by side and stacked."},{name:"--master-detail-collapse-below",category:"semantic",tier:"semantic",role:"MasterDetail stacking threshold (default 40rem / the `sm` step), measured against the composition's OWN inline size, not the viewport: wider keeps the split, narrower stacks master above detail. A real knob \u2014 the layout resolves it inside a flex-basis calc(), because a media/container-query CONDITION cannot read a var(). The `collapseBelow` prop overrides it per instance (rule #45)."},{name:"--master-detail-master-viewport-{compact,standard}",category:"semantic",tier:"semantic",role:'Bounded MasterDetail master viewport \u2014 20rem (320px) / 28rem (448px), selected by the semantic `masterViewport` prop. `masterViewport="auto"` (the default) applies NO bound, so existing compositions are unchanged; the presets cap the collection\'s block size and scroll it INSIDE the region, which is what stops a long real collection from pushing the detail below the fold once the layout stacks. Retune the presets in a service theme exactly like --master-detail-rail-*; never author a pixel max-height in consumer CSS.'},{name:"--master-detail-master-viewport-inset",category:"semantic",tier:"semantic",role:"Focus-ring room inside a bounded MasterDetail master region (default --space-1 / 4px), and its scroll-padding. An overflow container clips BOTH axes, so without this inset the focus ring of a row at the region's edge would be cut off; it also keeps a row scrolled into view off the clip edge. Only applies when `masterViewport` is compact/standard."},{name:"--page-header-extra-measure",category:"semantic",tier:"semantic",role:'Inline measure of the PageContainer header `extra` region while `headerLayout="responsive-inline"` and the page is below the 640px step (default 11rem / 176px). The default `headerLayout="stack"` never reads it \u2014 `extra` keeps wrapping onto its own full-width line under the subtitle. At >=640px both arrangements are identical, so this token only governs the compact range.'},{name:"--page-measure-{narrow,medium}",category:"semantic",tier:"semantic",role:'Bounded PageContainer MEASURE presets \u2014 42rem (672px) / 48rem (768px), selected by the semantic `measure` prop. They are OUTER measures: the package-owned page gutters (--space-page-active-x, 24px each side) sit INSIDE the cap, so the VISIBLE surface is 624px / 720px, and at 390px nothing binds (358px surface at the 16px compact gutter). Unlike variant="narrow" \u2014 which caps only .ui-page-body \u2014 the measure caps the page HEADER and the BODY together, so a header `extra` action ends flush with the body surface. `measure="default"` (the default) reads NEITHER token and matches no rule, so existing pages are unchanged. Retune the presets in a service theme; never author a page-local max-width.'},{name:"--badge-space-*",category:"component",tier:"component",role:"Badge spacing."},{name:"--button-count-{min-width,space-inline,font-size} / --button-count-{background,color} / --button-count-{default,destructive,secondary}-{background,color}",category:"component",tier:"component",role:"Button's COUNTER PILL (`count` / `overflowCount` / `showZero`). The COLOUR knobs exist because the pill used to tint itself translucently over whatever surface the button happened to have (`bg-primary-foreground/15` on filled variants, `bg-foreground/8` on the outline family), which means its contrast was a FUNCTION of that surface rather than a property of the pill \u2014 and five variant x theme x hover combinations measured below the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: default 3.88 light, destructive 4.29 dark, and the outline family 4.32 light at rest, 3.64 light on hover, 3.68 dark on hover. The two worst were HOVER states, which is exactly why a screenshot sweep never found them: `--accent` only exists under the cursor. The fix is the treatment gh#312 validated on Toggle: OPAQUE role fills, so the ratio no longer depends on the variant or on hover. Each filled variant wears its OWN label pair SWAPPED (default --primary on --primary-foreground = 5.04 light / 7.07 dark; destructive 6.10 / 5.53; secondary 14.25 / 12.40), which makes the pill exactly as legible as the label beside it and impossible to make worse without making the button itself unreadable first \u2014 a promise a fixed colour could not make across re-themes. The outline family shares one pill, --foreground on --muted (14.25 / 12.40), whose --muted fill sits 1.09:1 against the button's own ground, so at rest the counter still reads as quiet text rather than a badge: it is the legibility of the DIGITS that rose, not the loudness of the pill. All eight colour knobs are ROLE-MIRROR knobs -- `initial` at :root with the role default at the CALL SITE -- so a scoped `.dark`/`[data-tenant]` override of the role reaches the pill. The geometry knobs are byte-identical to Toggle's, asserted by a token-parity test."},{name:"--toggle-count-{min-width,space-inline,font-size,radius,gap} / --toggle-count-{background,color} / --toggle-pressed-count-{background,color} / --toggle-pressed-border-color / --toggle-count-forced-outline-width",category:"component",tier:"component",role:"Toggle's COUNTER PILL (`count` / `overflowCount` / `showZero` / `countLabel`, gh#312) \u2014 the counted, PRESSED chip Button could not express. SHARE vs SPLIT is the whole point of this group. GEOMETRY is shared: the five geometry knobs carry Button's exact counter values off the same primitive scale (var(--space-4) / var(--space-1) / var(--font-size-xs) / var(--radius-pill) / 0), so a Toggle count set beside a Button count measures identically in a browser (12.47px, 16px min-inline, 4px inline padding, pill radius, tabular-nums) and a token-parity test fails if either side moves alone. `--toggle-count-gap` is ADDITIVE to the toggle's own flex gap and defaults to the quiet 0 (rule #44). COLOUR is deliberately NOT shared: Button's pill sits on a static variant surface and tints itself translucently, but Toggle's pill sits on a surface that INVERTS when pressed, where that treatment cannot clear WCAG 1.4.3 AA at the xs step (measured 3.82:1, and 4.39:1 even at the lowest usable alpha \u2014 the ceiling is the chip label's own 5.04:1 and any tint only lowers it). So Toggle uses OPAQUE role fills that make its contrast independent of the chip's variant and hover surface, and inverts them with the state: unpressed --foreground on --muted (14.18:1 light / 12.44:1 dark), pressed --primary on --primary-foreground \u2014 the chip label's own pair, swapped (5.04:1 light / 7.05:1 dark), so the pill is exactly as legible as the label beside it. All five colour knobs are ROLE-MIRROR knobs: `initial` at :root with the role default at the CALL SITE, so a scoped `.dark`/`[data-tenant]` override of the role actually reaches the pill. `--toggle-pressed-border-color` defaults to the pressed fill itself (quiet, #44) and is the knob a service overrides when its pressed fill is a PALE tint that weakens the inversions. `--toggle-count-forced-outline-width` (1px) draws the pressed pill's outline under `forced-colors: active`, where every fill is flattened and neither inversion encodes anything \u2014 together with the chip border following the system Highlight, that keeps the pressed state off colour alone (WCAG 1.4.1)."},{name:"--logo-godx-{size,size-xs|sm|md|lg,color} / --logo-success-{background,foreground} / --logo-identity-foreground",category:"component",tier:"component",role:'The GoDX IDENTITY mark. COLOUR: its default is the --brand IDENTITY role (canonical emerald) \u2014 deliberately independent of --primary, so a re-themed action colour never recolours the brand, AND of --success, the \u82E5\u7AF9 STATUS green it wrongly defaulted to before gh#250 (`tone="success"` names the semantic SLOT, not the status role). Role-mirror knobs: declared `initial` at :root with the role default at the CALL SITE (`hsl(var(--logo-godx-color, var(--brand)))`), so a scoped `.dark`/`[data-tenant]` override of --brand actually reaches the mark. BOX: `--logo-godx-size-{xs,sm,md,lg}` (1.5 / 1.75 / 2 / 2.5rem) is the identity mark\'s own size ramp \u2014 the `size` prop drives it exactly like --logo-size-* drives the boxed glyph, one tier above its glyph sibling because the artwork is a capsule inside a square viewBox. `--logo-godx-size` is the PIN, not the base: `initial` by default (so the tiers apply) and, set once by a service theme, it freezes the mark at that box on EVERY tier. INK: `--logo-identity-foreground` (48 9% 9%, #191815) is the colour the boxed `mark="glyph"` sets its TEXT in on the identity fill \u2014 deliberately NOT --brand-foreground. --brand-foreground is the artwork KNOCKOUT colour (it tracks --background in both themes, because `mark="godx"` punches its inner bar as an evenodd HOLE and the email mark paints that hole solid to match) and as negative space only owes WCAG 2.2 SC 1.4.11\'s 3:1. The boxed glyph renders real TEXT, so SC 1.4.3 applies at 4.5:1 \u2014 14px bold is NOT "large text" (that needs 18.66px bold / 24px) \u2014 and knockout white on the emerald measured only 3.67:1, a straight AA failure. The ink is theme-INVARIANT (it is the same near-black spine the dark theme already resolved to), so dark is unchanged at 6.89:1 and light rises to 4.74:1. It is NOT a role-mirror knob \u2014 its default is a real value, not a role, so there is no role to freeze and it is declared at :root, not `initial`. Re-theming --brand to a DARK fill? Override the public knob --logo-success-foreground to re-invert the ink. Applies to `<Logo mark="godx">` / `tone="success"`.'},{name:"--logo-wordmark-{gap,font-size-xs|sm|md|lg,font-weight,letter-spacing,font-family,color}",category:"component",tier:"component",role:'Wordmark/lockup knobs for `<Logo wordmark="\u2026" />` \u2014 the mark\u2194wordmark gap, the per-size-tier wordmark type ramp, and its face/weight/tracking/colour. The package ships NO wordmark artwork: `--logo-wordmark-font-family` defaults (at the call site) to --font-family-display, so the wordmark is typeset in the design-system face; pass an inline <svg> as `wordmark` when a real logotype exists. `--logo-wordmark-color` is a role-mirror knob (`initial`) whose call-site default is hsl(var(--foreground)), or the --brand IDENTITY role on the godx / tone="success" lockup \u2014 never --primary and never the --success status green. Setting it once in a service theme re-colours every lockup with no page CSS.'},{name:"--card-*",category:"component",tier:"component",role:"Card surface, border, spacing, and typography."},{name:"--control-*",category:"component",tier:"component",role:"Shared form control heights, padding, icons, and focus chrome."},{name:"--control-affix-{inset-inline-end,action-size,action-radius,icon-size,rest-alpha} / --control-trigger-space-inline-end",category:"component",tier:"component",role:"OVERLAY trailing affix \u2014 the clear \u2715 / chevron a select-family trigger parks ON TOP of its inline end. One set for Select, SearchSelect, Cascader and TreeSelect (`.ui-control-affix-action` / `-icon` / `-indicator`); the Dialog and Sheet close \u2715 borrow `--control-affix-icon-size` so every overlay \u2715 in the system is one size. Before #319 each of those hard-coded the same `end-2 size-6 rounded-sm opacity-50` stack, so retuning affix weight meant chasing the same literal through four components and missing one. `--control-affix-rest-alpha` (0.5) is the resting weight and is deliberately QUIETER than the inside-field set's 0.7 (`--control-inline-affix-rest-alpha`): an overlay affix floats over the selected value, so at equal weight the \u2715 starts reading as content. `--control-trigger-space-inline-end` (2.25rem, a raw rem because the `pe-9` it replaced is a flat Tailwind step, not a density-scaled one) is the inline room the trigger reserves so a long selected label never runs UNDER the affix \u2014 grow the affix without growing this and the label collides with it; shrink it without shrinking this and you ship dead space at every trigger's end."},{name:"--control-inline-affix-{size,icon-size,space-gap,inset-inline,rest-alpha,space-inline-end,pair-space-inline-end}",category:"component",tier:"component",role:"INSIDE-FIELD affix \u2014 the leading/trailing controls Input, Textarea, TimePicker and DatePicker render WITHIN the field box, as opposed to the overlay set (`--control-affix-*`) that a select-family trigger parks on top of. Kept a SEPARATE set on purpose, not an alias: these sit on the field's own surface rather than over it, so they rest heavier \u2014 `--control-inline-affix-rest-alpha` is 0.7 against the overlay's 0.5. Collapse the two into one knob and one side always loses: either the inline affixes go too faint to find, or the overlay \u2715 starts fighting the value it sits on. `--control-inline-affix-space-inline-end` (2.25rem) reserves room for ONE affix; a field parking TWO (TimePicker's clear+clock, DatePicker's clear+calendar) reserves `--control-inline-affix-pair-space-inline-end` (3.5rem) instead \u2014 retune only the single knob and the two-affix fields run their own text under the icons. Textarea has no single line to centre on, so it pins its clear to the top-end corner via `--textarea-clear-inset-block-start` rather than centring like Input."},{name:"--textarea-autogrow-{line-height,min-height-rows,max-height-rows,box-inset} / --control-multiline-padding-block",category:"component",tier:"component",role:"Textarea `autoGrow` geometry \u2014 the box that grows with its content. The floor and ceiling are counted in TEXT ROWS (`--textarea-autogrow-min-height-rows` 1, `--textarea-autogrow-max-height-rows` 8), not pixels, because a row count survives a density change and a `--font-size-base` retheme while a px ceiling silently stops matching the visible line count. A service sets its composer's resting and maximum height once in theme; the `minRows` / `maxRows` props override per instance \u2014 the same theme-global / prop-local priority as `--form-label-width` vs `labelWidth`. `--textarea-autogrow-line-height` is `initial` at :root and read as `var(--textarea-autogrow-line-height, var(--line-height-normal))` at the call site, so a service that retunes the multiline line-height for CJK legibility retunes the row unit with it instead of getting a ceiling that no longer means eight visible lines. `--textarea-autogrow-box-inset` is the non-text part of the box the row maths adds back (block padding + border, both edges) and `--control-multiline-padding-block` is that block padding, shared with `.ui-control-multiline` itself so the two cannot drift. The floor is additionally clamped up to `--control-height`, so a resting one-row composer still lines up with the Input / Button on its row rather than undercutting the control tier. Every default reproduces today's geometry when `autoGrow` is absent."},{name:"--control-composite-field-*",category:"component",tier:"component",role:"The bordered two-input shell (`.ui-control-composite-field`) shared by DateRangePicker, MonthPicker and MonthRangePicker: one field box wrapping two inputs plus a separator so the pair reads as a SINGLE control instead of two adjacent ones. `--control-composite-field-space-gap` (`--space-2`) is the rhythm between the halves and the separator. Shared deliberately \u2014 before #319 each picker carried its own literal and the range fields drifted into slightly different boxes, the tell that a design system is not actually one system. The box's height, border, disabled state and focus chrome still come from `--control-*` / `.ui-control`, so retune this only for the internal gap; anything else belongs on the control family."},{name:"--menu-item-{height,radius,space-inline,space-gap,font-size,inset-space-inline-start} / --menu-content-{space-inset,min-width}",category:"component",tier:"component",role:"ONE row rhythm for every popup list surface: ContextMenu, Menubar, DropdownMenu AND Select's listbox all lay their rows out against these (`.ui-select-item` sits in the same rule as `.ui-dropdown-menu-item`), so a service retunes menu density once instead of four times. Before #319 the height was a literal `2rem` in the CSS and DropdownMenu had not been converted at all \u2014 it carried its whole box as Tailwind literals on the component. Set `--menu-item-height` and all four surfaces resize together; patch one component's padding instead and you get the four-menus-that-almost-match tell. `--menu-item-inset-space-inline-start` is the indicator column an inset row (a checkbox/radio row with no indicator of its own) reserves so its label stays aligned with its checked siblings \u2014 move it whenever you change `--menu-indicator-size`, or the two columns desynchronise. `--menu-content-min-width` (10rem) and `--menu-content-space-inset` are the panel, not the row; DropdownMenu re-points the min-width at `--dropdown-content-min-width` (8rem) because it anchors to a small trigger, so override that one to widen dropdowns alone."},{name:"--table-*",category:"component",tier:"component",role:"Table row/cell sizing."},{name:"--table-pagination-padding-{y,x}",category:"component",tier:"component",role:"DataTable.Pagination footer inset. The footer used to declare `padding-top` ONLY, so in the documented flush container (`<Card><CardContent flush><DataTable/>`) the 'rows per page' label and the page-size Select sat flush against the container edge and its closing border. It now owns `padding-block` + `padding-inline`. Both knobs are declared `initial` with the default at the CALL SITE \u2014 block `var(--space-stack-sm)` (the old padding-top value, now on both block edges), inline `var(--table-cell-space-x)` so the label lands on the same optical axis as the first column's text. `initial` is load-bearing: both defaults are density-scaled and re-declared inside a `.ui-density-*` subtree, and a `:root` binding would freeze the footer at the `:root` density. Consumers no longer need a local `.ui-data-table-pagination` override; a service on another grid retunes either axis."},{name:"--table-surface-min-inline-size",category:"component",tier:"component",role:'DataTable surface narrow-viewport width FLOOR. Below the `sm` viewport step a multi-column admin grid whose cells are `white-space: nowrap` would be crushed, so the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. This shipped as a hard-coded `min-w-[640px] sm:min-w-0` utility pair ON the surface element \u2014 the literal that forced the horizontal scroll at 390 \u2014 leaving a service that wants a narrower (or no) floor with no route but forking the component (rule #45). Default `640px`, byte-identical to the old utility, and deliberately px so the floor releases at exactly the same width as the px-based `sm` media query that clears it (a rem value drifts under a non-16px root and re-introduces the scroll between the two thresholds). Set `0` to opt out; `DataTable preset="action-collection"` already does, because there the column-priority measures own the width.'},{name:"--form-label-width",category:"component",tier:"component",role:"Label column width in horizontal Form layout. Default max-content; a service theme sets it once (e.g. 110px) \u2014 the labelWidth prop overrides per form/field."},{name:"--form-label-gap",category:"component",tier:"component",role:"Label\u2194control column gap in horizontal Form layout. Default 16px (--space-4)."},{name:"--dialog-* / --alert-* / --skeleton-*",category:"component",tier:"component",role:"Feedback component sizing and spacing."},{name:"--banner-{radius,border-width,space-inset-block,space-inset-inline}",category:"component",tier:"component",role:'Banner strip geometry \u2014 the full-bleed presentation of the Alert primitive (`<Banner>` / Alert variant="banner"). radius 0 (square strip corners), a single hairline block-end rule (tone-coloured by the alert rules), block inset denser than the inline card (--space-3), inline inset defaulting to the page gutter (--space-page-active-x) so banner text aligns with page content. Colour stays entirely with `tone`; a service retunes the strip once here \u2014 a consumer never writes CSS to place a banner.'},{name:"--page-header-status-gap",category:"semantic",tier:"semantic",role:"Gap between the page title and its status/meta band (PageContainer `status`, gh#255), and between the band's own items. Default --space-inline-sm. The band shares the title line and wraps under it on compact viewports; a page that never passes `status` reads neither the token nor the wrapping row."},{name:"--sheet-responsive-breakpoint-width / --sheet-bottom-max-height",category:"component",tier:"component",role:`The responsive drawer / detail-panel contract (SheetContent responsive="auto"). --sheet-responsive-breakpoint-width (default 48rem = 768px, the library's canonical mobile line) is the viewport width at and below which the desktop side panel becomes a mobile bottom sheet; --sheet-bottom-max-height (default 85dvh) caps THAT bottom presentation only (a plain side="bottom" sheet stays content-sized). Because a CSS @media cannot resolve a custom property, the breakpoint is read off :root at runtime by the exported useSheetResponsiveMode() hook \u2014 which is also what OrgSwitcher responsive="auto" uses, so ONE knob moves the popover\u2192sheet and side\u2192bottom line for every overlay at once. Accepts px/rem/em.`},{name:"--scroll-area-anchor-offset",category:"component",tier:"component",role:'ScrollArea `anchor="bottom"` stickiness band: how close to the bottom edge the reader still counts as "following the stream". Inside it, arriving content keeps the viewport pinned to the newest item; one pixel beyond it the reader is reading history and anchoring never moves them again until they come back (WCAG 3.2.5). Sized to the row height a service renders \u2014 a dense audit line is ~20px, a chat bubble with an avatar ~64px, and "one row from the bottom" is what the reader means. In rem so the band is still one row at 200% zoom. A CSS comparison cannot express it, so the component reads the token off the element at mount (px/rem/em accepted); the `anchorOffset` prop overrides it per instance. The rail geometry knobs (--scroll-area-bar-size / -bar-padding / --scroll-area-thumb-radius) live with the data-display tier.'},{name:"--qr-code-*",category:"component",tier:"component",role:"QrCode scanner-safe foreground/background and xs/sm/md/lg natural-size tiers. Keep foreground/background at strong contrast; the defaults remain dark-on-light in dark application themes."},{name:"--chart-trend-plot-height{,-xs,-sm,-md,-lg}",category:"component",tier:"component",role:"CompactBarTrend plot-height tiers (xs 3.5rem = dashboard summary-card density, sm 5rem, md 7.5rem, lg 10rem). The `size` prop picks a step via data-size; a service retunes the whole scale here. This is why a consumer NEVER writes an inline height for a compact trend."},{name:"--chart-trend-bar-{gap,radius,max-width,min-height}",category:"component",tier:"component",role:"CompactBarTrend mark geometry: gap between bars (default --space-2), rounded data-end radius (--radius-sm), the per-mark width ceiling (1.5rem, so a 3-point trend does not render slabs) and the min height floor (2px, so a zero value still reads as a plotted category). All service-tunable (rule #45)."},{name:"--chart-trend-bar-background{,-alpha} / --chart-trend-bar-emphasis-background",category:"component",tier:"component",role:"CompactBarTrend fills \u2014 role-mirror knobs declared `initial` with the role default at the call site. Muted marks default to hsl(var(--muted-foreground) / --chart-trend-bar-background-alpha) (0.75, which clears the 3:1 non-text contrast floor); the emphasized 'current' mark defaults to hsl(var(--primary)). Point them at --chart-N to tint a trend to a series colour."},{name:"--chart-trend-baseline-border",category:"component",tier:"component",role:"CompactBarTrend baseline rule. QUIET by default (rule #44 \u2014 resolves to `none`); a service opts in with `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-{tick-gap,tick-font-size,footer-gap}",category:"component",tier:"component",role:"CompactBarTrend category-tick and activity-footer rhythm \u2014 the plot\u2194tick gap, tick type size (--font-size-xs) and the plot\u2194footer gap."},{name:"--email-shell-{width,padding,page-padding,border-width} / --email-card-{radius,reference-height} / --email-stack-gap{,-sm}",category:"component",tier:"component",role:"Transactional-email shell geometry: the 480px card width, its 32px inset, the 24px viewport gutter, the 1px edge, the 10px radius (mirrors --card-radius) and the block rhythm. The canonical invitation reference measures 480\xD7407 (--email-card-reference-height is a visual-regression target, NEVER a fixed height on the card). Values are LITERAL px \u2014 email clients resolve neither var() nor rem \u2014 and are consumed through the `@godxjp/ui/email` export (EMAIL_SHELL), never by a stylesheet."},{name:"--email-{body,heading}-{font-size,line-height,font-weight} / --email-font-family-{sans,mono}",category:"component",tier:"component",role:`Email type ramp, reconciled with the SCR-302 canonical reference. Body 14px/1.9, title 17px/1.7 at weight 500 (a transactional title is CALM, not bold) \u2014 deliberately looser and quieter than the web ramp, because a mail client gives the reader no density control, so do NOT 'fix' it back to --heading-h1/--line-height-body. --email-font-family-sans names the canonical Noto Sans JP face first and degrades Hiragino \u2192 Yu Gothic \u2192 M PLUS 2 \u2192 Meiryo \u2192 system UI \u2192 Arial \u2192 sans-serif; no email client honours @font-face, so the stack is the whole fallback contract. --email-font-family-mono (mirrors --font-family-mono) sets invoice ids, masked card numbers, ISO dates and amounts. Family names export SINGLE-quoted so the value drops into a double-quoted style="\u2026" attribute unescaped (emailInlineStyle throws on a double quote). Exposed as EMAIL_TYPOGRAPHY.`},{name:"--email-mark-{width,height,gap} / --email-wordmark-{font-size,font-weight}",category:"component",tier:"component",role:'Email brand-mark LOCKUP. The ARTWORK is byte-identical to <Logo mark="godx" /> (the 32\xD732 capsule + internal glyph, asserted by test); only the rendered box is email-specific \u2014 the canonical header sets a 22px mark, an 8px gap (mirrors --logo-wordmark-gap) and a 13px/700 wordmark (mirrors --logo-wordmark-font-weight). In that pairing the mark is DECORATIVE (render it with label: "") and the wordmark carries the accessible name, so the brand is announced once. Exposed as EMAIL_BRAND_MARK; the svg / dataUri / tableHtml deliveries all derive from this one geometry source.'},{name:"--email-cta-{height,line-height,padding-x,radius,font-size,font-weight} / --email-focus-border-width",category:"component",tier:"component",role:"The single primary email CTA, reconciled with the SCR-302 canonical reference: 44px tall with line-height EQUAL to the height so the label centres without flexbox in Outlook, 16px inline padding (--space-4), the 6px --radius base, 14px/500 label. The height is a TOUCH TARGET and is deliberately DECOUPLED from --control-height-lg (36px), which it mirrored until 18.8.0 \u2014 do not restore the mirror. Email is touch-only: no hover, no precise pointer, no dependable zoom, so the web AA floor (SC 2.5.8, 24\xD724, which 36px clears) is the wrong bar. 44px is SC 2.5.5 Target Size (Enhanced, AAA, 44\xD744) plus the Apple HIG 44pt / Material 48dp convention, and the mobile reflow still takes the CTA full-bleed. Fill/label are EMAIL_COLORS.primary / primaryForeground, derived from the --primary role \u2014 never paste a canonical hex, or the email stops tracking a re-theme. --email-focus-border-width mirrors --focus-ring-width for the webmail panes that honour :focus-visible. Exposed as EMAIL_CTA / EMAIL_FOCUS."},{name:"--email-footer-{font-size,line-height,link-gap,padding-top,border-width} / --email-mobile-*",category:"component",tier:"component",role:"Legal-footer typography (11px/1.8 quiet type per the SCR-302 canonical, 12px between adjacent links, 20px above the hairline) and the narrow-viewport reflow (520px breakpoint, fluid card, 20px inset, 12px gutter, 16px heading \u2014 one step under the 17px desktop title, full-bleed CTA). The canonical 390px reference raster is unusable (435px wide and already clipping the card, dxs-platform/platform#496), so the mobile ramp is the library's documented contract rather than a measured value. Exposed as EMAIL_FOOTER / EMAIL_MOBILE; every reflow rule needs an inline fallback because most clients strip <style>."},{name:"--auth-shell-device-{card-max-width,main-padding,main-padding-mobile,otp-slot-inline-size,otp-slot-block-size}",category:"component",tier:"component",role:'AuthShell `preset="device-authorization"` measure \u2014 the canonical OAuth device-grant artboard: a 23.75rem/380px card at 1440/1024 and a 15px-block \xB7 5px-inline page gutter at 390 (so the card renders x=5px, width=380px), PLUS the code field the flow is actually about. The preset now owns its OTP slot box and hands it to --otp-slot-{inline,block}-size: 27.5x52 per slot, i.e. a 4-slot `appearance="grouped"` box of 112x54 with its 1px group border. Without it the field fell back to the square --control-height tier and rendered 146x38 against that artboard \u2014 the preset owned the page but not its subject. LITERAL lengths, not --space-*, because the artboard specifies exact device pixels that must not drift with --scaling. Selecting the preset replaces a consumer-side --auth-shell-card-max-width override or a forked `.auth-shell--wide` class; the canonical 360px/15px default is untouched.'},{name:"--auth-shell-context-{card-max-width,main-padding,main-padding-mobile,card-stack-gap}",category:"component",tier:"component",role:'AuthShell `preset="context-selection"` measure \u2014 the organisation/context picker: a 25rem card on desktop/tablet, edge-to-edge on mobile (0 inline gutter), and a 1rem rhythm between the three direct sections of the auth column (intro \xB7 choice card \xB7 "remember" row).'},{name:"--auth-shell-recovery-{card-max-width,main-padding,main-padding-mobile}",category:"component",tier:"component",role:'AuthShell `preset="account-recovery"` measure \u2014 the SCR-008 27rem/432px panel, shared by the password-recovery panel (request \xB7 sent \xB7 new-password \xB7 expired) AND the sign-in MFA challenge panel (OTP \xB7 recovery-code \xB7 passkey-failure), because both canonical desktop panels measure w=432 at 1440. Mobile gutter = 15px inline at <=30rem, so the panel is x=15, width=360 \u2014 the SAME rhythm as the canonical Login flow, chosen deliberately: the supplied 390 reference is a desktop 2x2 composite that crops horizontally and is NOT a valid source. The canonical 360px Login measure and the 24rem un-preset shell are untouched.'},{name:"--auth-shell-registration-{card-max-width,main-padding-block-start,main-padding-block-start-mobile,main-padding-inline,main-padding-inline-mobile,main-padding-block-end,main-padding-block-end-mobile,card-stack-gap,identity-slot-block-size}",category:"component",tier:"component",role:'AuthShell `preset="registration"` measure \u2014 the canonical SCR-002 sign-up flow: a 22.5rem/360px form measure with a 15px inline gutter at 390 (card x=15, width=360 \u2014 the same page rhythm as the Login flow, so sign-in \u2192 sign-up never jumps on a phone). START-aligned (a tall sign-up card must scroll, not clip its own top above the scroll origin), with the block-start offset DERIVED from the canonical artboard (card y=284 at 1440x900, y=274 at 390x844: padding-block-start + 112px identity slot + 20px stack gap), and a footer-clearance knob of its own (block-end 3rem / 2rem mobile) so the legal/consent footer never sits flush against the submit button after a long scroll. The fixed 112px identity track absorbs absent / one-line / wrapped two-line identity copy without moving the card anchor.'},{name:"--otp-slot-size",category:"component",tier:"component",role:"InputOTP slot box. Default `var(--control-height)` \u2014 byte-identical to before and still density-aware \u2014 but its OWN knob, so an auth panel can widen the 6-slot challenge row to fill a wide surface WITHOUT re-scoping --control-height (which would resize the primary button and every other input in the same card). Set it to a NAMED tier (`var(--control-height-lg)`), never an ad-hoc calc offset."},{name:"--otp-slot-{inline-size,block-size}",category:"component",tier:"component",role:'Per-AXIS InputOTP slot box. --otp-slot-size stays the SQUARE shorthand; these two win over it and fall back to it, so a code field that is taller than it is wide \u2014 the canonical device-grant slot, 27.5x52 \u2014 is finally expressible from a token. Declared `initial` so the whole chain (axis \u2192 square \u2192 --control-height) resolves at the CALL SITE and a shell that re-scopes --control-height still reaches a field that sets neither axis. `AuthShell preset="device-authorization"` sets both for you.'},{name:"--otp-container-align",category:"component",tier:"component",role:'Main-axis alignment of the whole InputOTP row. Default `flex-start` = the historical layout, so nothing moves. It exists because a centred code challenge is the common auth case and the container element belongs to `input-otp` \u2014 the only thing a consumer could reach was a wrapper div, and every one of them wrote it. Per instance use `align="center"` on InputOTP, which outranks this token.'},{name:"--tabs-indicator-{background,size,offset}",category:"component",tier:"component",role:"Tabs `line` ACTIVE indicator \u2014 the only selected-state decoration the line variant paints; it never gets a surrounding ring/card border, so the `:focus-visible` keyboard ring stays visible and distinct (WCAG 2.4.7). --tabs-indicator-background is declared `initial` so its hsl(var(--primary)) default re-resolves at the CALL SITE under a scoped [data-tenant]/.dark theme. Defaults = primary \xB7 2px \xB7 offset 0 (flush with the trigger edge); raise the offset to park the bar on a thicker strip hairline. The selected ring on the default/card variants is unchanged."},{name:"--avatar-square-{radius,size,background,foreground}",category:"component",tier:"component",role:'Avatar `shape="square"` entity-header mark \u2014 the compact rounded square an organization/service header uses instead of the round person avatar. --avatar-square-background / --avatar-square-foreground are declared `initial` so the hsl(var(--primary)) / hsl(var(--primary-foreground)) defaults re-resolve at the CALL SITE under a scoped theme. Defaults = --radius-lg corners \xB7 --control-height box (swapping shape never reflows the header) \xB7 primary fill at 4.65:1 contrast. `shape="circle"` (the default) is inert and reads --avatar-background / --avatar-tint as before.'},{name:"--avatar-tinted-{background,foreground,glyph-size}",category:"component",tier:"component",role:'Avatar `appearance="tinted"` \u2014 the CAPABILITY MEDALLION: the tinted plate a feature/capability glyph sits on, as opposed to the solid entity mark above. Orthogonal to `shape`, so `shape="square" appearance="tinted"` is the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are declared `initial` so the hsl(var(--primary) / 0.1) / hsl(var(--primary)) defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme \u2014 which is the whole point: consumers were writing that 0.1 alpha literal into page CSS, or giving up and rendering a bare glyph, because the medallion is a composition (Avatar + a Lucide glyph) whose tint had no token. Defaults = 10% primary wash \xB7 primary glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-presence-{size,min-size,inset,ring-width,ring-color,stroke-width,bar-inline-size,bar-block-size,online-color,away-color,busy-color,offline-color}",category:"component",tier:"component",role:'Avatar `presence` \u2014 the realtime reachability dot on the mark. Every constant is a knob because the workaround it replaces baked four of them plus a raw palette colour into consumer page CSS (`-end-0.5 -bottom-0.5 size-2.5 ring-2 bg-green-500`). --avatar-presence-size is a PROPORTION of the mark (default 30%, floored by --avatar-presence-min-size = --space-2), NOT a px step, so one value tracks every avatar the system paints \u2014 the --control-height box and its xs/sm/lg steps, --avatar-square-size, --org-switcher-avatar-size, --upload-avatar-size, a call site\'s own `size-12`, the 36px ListRow density="compact" mark \u2014 instead of freezing a diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs declared `initial`, with the role default at the CALL SITE (`var(--knob, var(--role))`), so a scoped [data-tenant]/.dark override of --background / --success / --warning / --destructive / --muted-foreground reaches them \u2014 bound at :root the ring would freeze light-mode-white on a dark avatar. --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the ACCESSIBLE encoding, not decoration: they draw the per-state silhouette (filled / half-filled / barred / hollow) that keeps presence off colour-alone (WCAG 1.4.1) in greyscale, for a deuteranope and under forced colors. Defaults = 30% diameter (floor 0.5rem) \xB7 flush with the corner (inset 0) \xB7 a 2px --background separator ring \xB7 a 1.5px state stroke \xB7 a 56%-wide 1.5px dnd bar.'},{name:"--separator-{rule-size,rule-color,label-gap,label-inset,label-font-size,label-line-height,label-font-weight,label-color,tone-<tone>-rule-color,tone-<tone>-label-color}",category:"component",tier:"component",role:"Separator, including the LABELLED rule a `label` interrupts \u2014 a message stream's day divider, a \"new messages\" unread watermark, an auth conjunction. Before #308 the only labelled rule in the library was the auth-scoped AuthDivider, which baked `height: 1px` into shell-layout.css and read the `--auth-shell-divider-*` micro-scale, so a service retuning its login divider silently retuned every day divider in its chat. Defaults are the QUIETEST state: a 1px hairline, a muted --font-size-xs label at --font-weight-medium, no surrounding padding. --separator-label-gap and --separator-label-inset (the SHORT rule half under labelAlign start/end \u2014 a grid track on the inline axis, so it flips under RTL) are declared `initial` because --space-* is density-scaled and re-declared inside a `.ui-density-*` subtree; a :root binding would freeze them at the :root density. Every colour knob is a role-mirror and is likewise `initial`, with the role default at the CALL SITE \u2014 --separator-rule-color \u2192 --border, --separator-label-color \u2192 --muted-foreground, and --separator-tone-{muted,primary,success,warning,destructive,info}-{rule,label}-color \u2192 the matching semantic role. `tone` re-points the rule AND the label, never the rule alone, so an attention rule is not colour-only (WCAG 1.4.1) and survives forced-colors. AuthDivider now only re-points these knobs at the --auth-shell-divider-* layer, so #263's canonical login geometry is unchanged."},{name:"--steps-inline-{gap,item-gap,font-size,separator-size,index-font-weight,index-color,separator-color}",category:"component",tier:"component",role:"Steps type=inline compact auth/device progress rhythm, typography and EMPHASIS. Consumers select the semantic appearance; services retune the row once without page CSS. The step number's emphasis is two knobs \u2014 --steps-inline-index-font-weight (default bold) and --steps-inline-index-color (role-mirror `initial`, default the inherited step colour) \u2014 because the canonical hosted-identity row marks the step with an accent TINT at normal weight while the library default marks it with bold. --steps-inline-separator-color (role-mirror `initial`, default --muted-foreground) tints the glyph the `separator` prop picks."},{name:"--auth-account-summary-{min-height,gap,padding,identity-min-width,avatar-size,avatar-glyph-size,email-font-size}",category:"component",tier:"component",role:"AuthAccountSummary compact signed-in identity row: wrapping threshold, avatar, email and action rhythm. The default touch-safe row is package-owned and remains themeable without consumer selectors."},{name:"--auth-shell-{main-align,card-stack-gap}",category:"component",tier:"component",role:"AuthShell column knobs shared by every preset. --auth-shell-main-align is the block alignment of the auth column (default `center`, the vertically-centred card; set `start` for a tall intro+card+footnote stack). --auth-shell-card-stack-gap is the gap between the card slot's direct sections \u2014 default `0px` (quiet, rule #44) so an existing single-card consumer is byte-for-byte unchanged; a preset opts in."},{name:"--card-{accent-rail-width,accent-perimeter-width,accent-perimeter-ring-width,featured-border-color,featured-ring-width}",category:"component",tier:"component",role:'Card ACCENT geometry, one set per placement. `accentPlacement="edge"` (the default) draws the leading rail at --card-accent-rail-width (6px). `accentPlacement="perimeter"` draws the FULL attention border, measured by --card-accent-perimeter-width + --card-accent-perimeter-ring-width (1px + 1px, the optical weight of `variant="featured"`) in the card\'s own semantic accent tone. `variant="featured"` is the brand-toned member of the same family and no longer hard-codes --primary: its edge is the --card-featured-border-color role-mirror knob (`initial`, so the --primary default resolves at the CALL SITE and a scoped [data-tenant]/.dark override reaches it) at --card-featured-ring-width. The accent COLOUR itself is not a knob \u2014 it is resolved from `data-accent` on the card, so retint the ROLE (--attention, --success \u2026).'},{name:"--card-space-shell-y",category:"component",tier:"component",role:"Card BLOCK-axis shell padding \u2014 a plain header's top, a `solo` body's top and the terminal slot's bottom. Split off --card-space-inset, which is now inline-only, so a theme can make a card SHORTER without narrowing its column. Declared `initial`: the default resolves at the CALL SITE to --card-space-inset, so a card that overrides neither (and `density=\"tight|cozy\"`, which re-declares it `initial` to re-arm the per-instance inset) renders exactly as before."},{name:"--auth-shell-{compact-card-inset,card-padding-block-compact,card-body-gap-compact,card-gap-compact}",category:"component",tier:"component",role:"The compact AuthShell card's four independent knobs \u2014 one per axis, so the canonical Login card is tunable WITHOUT a consumer selector on the card-content slot: `compact-card-inset` = the inline column (\u2192 --card-space-inset); `card-padding-block-compact` = the card's block/top-bottom padding (\u2192 --card-space-shell-y, `initial` so its default mirrors the live inline inset and canonical output is unchanged); `card-body-gap-compact` = the header\u2194body gap (\u2192 --card-space-body-y, 12px, the role the block knob used to be mis-wired to); `card-gap-compact` = the in-slot title\u2195description stack gap (\u2192 --card-space-gap)."},{name:"--app-setting-picker-compact-{control-height,padding-x,gap,font-size}",category:"component",tier:"component",role:'AppSettingPicker `compact` trigger \u2014 the small, content-hugging LABELLED switcher for an auth/legal footer (`kind="locale" appearance="labeled" compact`). The box height reads the official --control-height-sm tier (never a literal or an ad-hoc calc), and padding/gap/type are separate knobs so a service theme retunes the footer switcher without forking CSS.'},{name:"--centered-shell-column-offset-block",category:"component",tier:"component",role:'CenteredShell column block offset \u2014 default `0` (the quiet state, rule #44: the top-aligned flowing/scrolling page). `<CenteredShell align="center">` flips it to `auto`, which centres the column in the 100dvh shell: the package-owned geometry for a SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice) at 1440/1024/390, so a consumer never writes `min-h-dvh` + flex-centring CSS or a className. Auto block offsets collapse to 0 when the content is taller than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--error-surface-*",category:"component",tier:"component",role:'ErrorSurface knobs \u2014 the whole 403/404/500/503 exception surface is themeable without a single consumer class or media query, in BOTH `mode="application"` and `mode="system"`. Geometry: `--error-surface-max-width` (32rem measure of the surface block, so a long JA/VI headline wraps readably even inside a wide PageContainer), `--error-surface-gap` (rhythm between status code \xB7 body \xB7 metadata \xB7 progress), `--error-surface-padding-block` (--space-10, the 1440/1024 steps) and `--error-surface-padding-block-compact` (--space-6, below the narrow step, i.e. the 390 case), `--error-surface-brand-gap` (system-mode Logo slot) and `--error-surface-progress-max-width` (18rem, so the maintenance meter reads as metadata, not as the page\'s primary content). Metadata list: `--error-surface-meta-gap` (row rhythm) \xB7 `--error-surface-meta-row-gap` (label\u2194value gap inside one row) \xB7 `--error-surface-meta-padding-block`. Chrome is QUIET by default (rule #44): `--error-surface-meta-border` is `none`; a service opts in with `1px solid hsl(var(--border))`. The narrow step is a CONTAINER query at 30rem on the SURFACE\'s own width, not a viewport media query, so it compacts correctly whether the squeeze came from a 390px phone or from an application shell with an expanded sidebar; below it, metadata rows stack label-over-value. System-mode viewport centring is delegated to `--centered-shell-column-offset-block` (via CenteredShell align="center"), and the description measure to `--empty-state-description-max-width`.'},{name:"--centered-shell-landing-*",category:"component",tier:"component",role:'CenteredShell public-landing knobs \u2014 the SHELL geometry of a public marketing/product landing, so the landing itself stays a COMPOSITION (Hero/Navbar/Footer fail the Framework-Component Test) with ZERO page-local CSS. Read only under `preset="public-landing"`; the default shell emits no `data-preset`, so none of them can match. Measure: `--centered-shell-landing-max-width` (67.5rem, shared by the header bar, the centred column AND the footer \u2014 the bar/footer inline padding is `max(gutter, (100% - measure) / 2)`, so header content starts on exactly the column edge and no consumer max-width wrapper exists), `--centered-shell-landing-inset-inline` (+ `-compact`, the 390 gutter), `--centered-shell-landing-main-padding-block` (+ `-compact`), `--centered-shell-landing-section-gap` (+ `-compact`, the rhythm BETWEEN plain `<section>` elements \u2014 the consumer never sets a gap) and `--centered-shell-landing-footer-padding-block`. Chrome is QUIET by default (rule #44): `--centered-shell-landing-card-shadow` is `none`, which flattens every Card in the subtree from ONE knob instead of a consumer `.landing .card { box-shadow: none }`. Type: `--centered-shell-landing-heading-size` (+ `-compact`) re-points `--heading-h1`, so a hero title is a real `Heading level={1}` and never a page font-size. `--centered-shell-landing-background` is a ROLE-MIRROR declared `initial` with the role default at the call site (`var(--centered-shell-landing-background, var(--background))`), so a scoped [data-tenant]/.dark override of `--background` still reaches the landing canvas. The compact step is a 40rem media query owned by the package; region visibility (anchor nav below the tablet step, wordmark and the secondary action at mobile) is `Flex hideBelow` / `hideFrom`, never a consumer @media.'},{name:"--table-action-collection-*",category:"component",tier:"component",role:'Table action-collection knobs \u2014 the canonical dense approval/action queue at 1440 \xB7 1024 \xB7 390. Read only under `preset="action-collection"`. Column PRIORITY measures replace the desktop intrinsic widths (a `white-space: nowrap` free-text column is what makes a five-column queue wider than its card and forces the horizontal scroll): `--table-action-collection-primary-width` (18%), `-secondary-width` (22%), `-meta-width` (12%) and `-actions-width` (3.5rem \u2014 an absolute measure because the row-action affordance must never be squeezed below its touch target); an unmarked column takes the remaining space. Below the collapse step the same four re-point to their `-compact` tier (24% / 22% / 20% / 2.75rem) together with `--table-action-collection-font-size-compact`, `-row-height-compact`, `-cell-space-x-compact` and `-cell-padding-y-compact`. The step is a CONTAINER query on the table\'s own width (sm 40rem \xB7 md 48rem \xB7 lg 64rem \xB7 xl 80rem via `collapseBelow`), not a viewport media query, so a table inside a master rail collapses before the page does. Percentages so the ratio holds at any card width; a service retunes the ratio once instead of authoring column widths per screen (rule #45). ONE family serves BOTH entry points \u2014 the `Table` primitive (`priority` on `TableHead`/`TableCell`) and the TanStack `DataTable` (`priority` on the `ColumnDef`, stamped onto both cells for you). There is deliberately no parallel `--data-table-action-collection-*` family: retune these once and every queue follows.'},{name:"--legal-document-*",category:"component",tier:"component",role:"LegalDocumentShell knobs \u2014 the whole legal/policy document surface is themeable without a single consumer `.legal-*` class. Geometry: `--legal-document-measure-max-width` (46rem readable measure, caps header/body/footer), `--legal-document-toc-width` \xB7 `--legal-document-column-gap` \xB7 `--legal-document-toc-inset-block-start` (sticky offset) \xB7 `--legal-document-toc-max-height`, `--legal-document-section-gap` \xB7 `--legal-document-section-title-gap` \xB7 `--legal-document-header-gap` \xB7 `--legal-document-meta-gap` \xB7 `--legal-document-nav-gap` \xB7 `--legal-document-footer-gap`, `--legal-document-meta-font-size`, `--legal-document-body-line-height` (1.8 long-form leading) and `--legal-document-scroll-offset` (the `scroll-margin-block-start` a hash jump leaves above a heading \u2014 this is why no app needs JS offset arithmetic). Contents entries: `--legal-document-toc-font-size` \xB7 `--legal-document-toc-title-font-size` \xB7 `--legal-document-toc-item-padding` \xB7 `--legal-document-toc-item-radius` \xB7 `--legal-document-toc-marker-width`. Chrome is QUIET by default (rule #44): `--legal-document-toc-border` / `--legal-document-header-border` / `--legal-document-footer-border` are all `none`; a service opts in with `1px solid hsl(var(--border))`. The six colour knobs are ROLE-MIRRORS declared `initial` with the role default at the call site \u2014 `--legal-document-toc-foreground` (\u2192 --muted-foreground, also paints the contents caption), `--legal-document-toc-active-foreground` (\u2192 --foreground), `--legal-document-toc-active-background` (\u2192 --accent), `--legal-document-toc-marker-color` (\u2192 --primary), `--legal-document-meta-foreground` (\u2192 --muted-foreground) and `--legal-document-summary-foreground` (\u2192 --muted-foreground) \u2014 so a scoped [data-tenant]/.dark override of the role still reaches them. The one-column \u21C4 two-column split is a CONTAINER query at 56rem on the shell's own width, not a viewport media query."},{name:"--empty-state-description-max-width",category:"component",tier:"component",role:"EmptyState description MEASURE \u2014 default 28rem (~65 Latin characters per line). A service or locale that needs a shorter/longer measure for its own copy (JA/VI error-page descriptions, a narrow system surface) retunes this one knob instead of forking `.ui-empty-state-description` (rule #45)."},{name:"--sidebar-badge-{background,foreground} / --sidebar-badge-destructive-{background,foreground}",category:"component",tier:"component",role:'Sidebar nav COUNT PILL colour, split into a resting pair and an emphasis pair. Before these knobs `.sb-badge` had a font-size token and no colour token at all, so a rail that had to tell "unread" from "mentions you" could only nest a `<Badge>` INSIDE `SidebarItemProp.badge` \u2014 which renders a pill inside a pill (measured: a 37.11x19.14 `.sb-badge` wrapping a 25.11x19.14 `<Badge>` with its own border). The resting pair is what every badge has always looked like (hsl(var(--secondary)) fill, hsl(var(--muted-foreground)) text); the `-destructive-` pair is read ONLY by rows that pass `badgeTone="destructive"`, which is the only thing that emits `data-tone` on the pill (rule #44), so a rail that never sets the prop renders exactly the node it always did. All four are role-mirror knobs declared `initial` with the role default resolved at the call site, so a scoped [data-tenant]/.dark override reaches them. Colour ONLY \u2014 the pill\'s min-width, radius, inline pad and font size are shared by both tones, which is what keeps a mention row and an unread row aligned in the same column.'},{name:"--sidebar-nav-item-{foreground,hover-foreground,disabled-foreground} / --sidebar-nav-icon-{foreground,hover-foreground,active-foreground,disabled-foreground}",category:"component",tier:"component",role:"Sidebar navigation row + icon COLOUR, split so they can differ. Previously `.sb-nav-item` set one `color` for the whole row, so the Lucide SVG inherited `--muted-foreground` along with the label and the icons read as 'missing' against a canonical shell that wants darker 16px icons. `--sidebar-nav-item-*` drives the row/label (incl. sub rows), `--sidebar-nav-icon-*` drives `.sb-icon`. All are role-mirror knobs declared `initial` at `:root` with the role default at the call site, so a scoped `[data-tenant]`/`.dark` override reaches them; icon defaults resolve to `currentColor`, i.e. rendering is unchanged until a service opts in (`--sidebar-nav-icon-foreground: hsl(var(--foreground))` is the canonical darker-icon setting). Colour ONLY \u2014 the 16px icon, 32px row height and 10px gap are untouched. The active row keeps `--sidebar-item-active-{background,foreground}`."},{name:"--list-row-compact-{padding-y,padding-x,gap,body-min-width}",category:"component",tier:"component",role:'ListRow `density="compact"` inline-actions geometry \u2014 the compact invitation/history row: a 36px leading Avatar, a shrinkable title+description and one or two small trailing Buttons on ONE line inside the canonical 358px card (326px content). `--list-row-compact-body-min-width` (6rem) is the compact body threshold \u2014 the default 12rem `--list-row-body-min-width` is what forced the actions (and a history Badge + date) onto a second line at 390px; it is consumed as `min(<token>, 100%)` exactly like the default knob, so the #224 wrap contract is intact and a cluster that genuinely cannot fit still wraps instead of widening the page root. `--list-row-compact-{padding-y,padding-x,gap}` are declared `initial` with the density-scaled default at the CALL SITE (`var(--space-2)` block, `var(--list-row-padding-x)` inline, `var(--space-2)` gap) \u2014 a `:root` binding would freeze compact rows at the `:root` density. Measured at 390px: invitation row 62px (was 126px, actions wrapped), history row 41px (was 114px), documentElement scrollWidth === clientWidth.'},{name:"--list-row-{body-min-width,trailing-gap,indicator-size,read-background,unread-background,indicator-color}",category:"component",tier:"component",role:"ListRow shrink + unread state. `--list-row-body-min-width` (12rem) is consumed as `min(<token>, 100%)` \u2014 the `min()` is load-bearing, a bare length re-inflates the row's intrinsic width and reintroduces page-root overflow. `--list-row-trailing-gap` spaces wrapped trailing actions. The unread trio are role-mirror knobs declared `initial` with the role at the call site: `--list-row-unread-background` defaults to `hsl(var(--muted))` NOT `--accent` \u2014 accent measured 4.23:1 against the xs muted-foreground description line, below WCAG AA; muted is 4.63:1 light / 5.47:1 dark. `--list-row-indicator-color` (\u2192 `--primary`) and `--list-row-indicator-size` drive the unread dot, whose meaning is carried by localized sr-only text, never by colour alone."},{name:"--filter-bar-scroll-padding-y",category:"component",tier:"component",role:'Scrollbar gutter for `<FilterBar overflow="scroll">`, default `var(--space-1)`. Only applies at \u2265640px, where a wide filter set becomes a single nowrap strip with a sticky inline-end clear-all; below that the bar still stacks, so the gutter would be dead space. Pair with the existing `--filter-bar-sticky-background` (already an `initial` role-mirror knob) when theming the strip.'},{name:"--filter-bar-{search-width,filter-width,chip-gap,section-gap}",category:"component",tier:"component",role:"FilterBar typed-model geometry \u2014 every list page gets the SAME search width, filter width, chip rhythm and section stacking without page-local CSS (rule #45: theme sets once, className overrides per instance). `--filter-bar-search-width` (20rem) sizes the model search slot; `--filter-bar-filter-width` (\u2192 `--filter-picker-width-md`) is the min inline size of each typed filter group; both apply from 640px up, consumed as `min(100%, <token>)` so a 390px viewport never overflows with long JA/EN/VI labels (below 640px everything stacks full-width). `--filter-bar-chip-gap` (`--space-2`) spaces the applied-chips row; `--filter-bar-section-gap` (`--space-2`) stacks strip \u2192 chips \u2192 result-count/error line."},{name:"--app-shell-{sidebar-width,rail-width}",category:"component",tier:"component",role:"AppShell docked navigation rail widths \u2014 `--app-shell-sidebar-width` (default 16rem) is the expanded rail, `--app-shell-rail-width` (default 4rem) the icon-only rail used at `<AppShell sidebarCollapsed>`. These were hard-coded `grid-template-columns` literals; a service on a different design grid (e.g. a 255px rail) now sets ONE token in its theme instead of forking `.app-root`. Below the 900px shell breakpoint both collapse to a single full-width column and the sidebar moves into AppShell's drawer."},{name:"--app-shell-bar-{inset,inset-compact,gap}",category:"component",tier:"component",role:"AppShell top-bar chrome \u2014 `--app-shell-bar-inset` is the bar's inline padding, `--app-shell-bar-inset-compact` the tighter value below the compact step, `--app-shell-bar-gap` the gap between the bar's direct children (default --space-3). ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS: the bar sits in the SAME grid track as `.app-main`, directly above the page, so its first control and the page title are on one vertical edge. The two insets used to name their own values (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and nothing reconciled them \u2014 measured in Chromium, the bar's content started at x=80 and the page header's at x=88 (1512px), and because the two sides stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even constant (8px at 1512, 12px between 720 and 900, 4px below 720), which a consumer cannot correct by hand. The page gutter now OWNS the axis and these knobs read it: `--app-shell-bar-inset: var(--space-page-x)` (24px), `--app-shell-bar-inset-compact: var(--space-page-compact-x)` (16px), stepping at the PAGE's `(max-width: 720px)`, not the shell's 900px restructure. GEOMETRY MOVED \u2014 the bar's inline padding went 16px \u2192 24px (12px \u2192 16px compact, and 12px \u2192 24px between 720 and 900); the page did not move. The NAMES are unchanged, so a theme that already sets either knob keeps working, and a bar that genuinely wants to sit tighter than its page still has its own. Height stays `--app-shell-bar-height` (3rem) at EVERY width, and it is also the owner of the chrome BAND-HEIGHT axis \u2014 see `--page-header-min-block-size-chrome`."},{name:"--topbar-{height,inset,gap}",category:"component",tier:"component",role:"Standalone `<Topbar>` box knobs. Topbar is a pure slot bar, so the defaults are the QUIET ones and rendering is unchanged from before they existed: `--topbar-height: auto` and `--topbar-inset: 0px` (inside AppShell the `.app-topbar` grid row owns height and inset), `--topbar-gap: var(--space-2)`. A service that mounts Topbar DIRECTLY on a page sizes it from the theme (`--topbar-height: 3.5rem; --topbar-inset: var(--space-4)`) instead of an app-local class. `--topbar-gap` is both the gap BETWEEN the start/center/end clusters and the gap INSIDE each, so one knob re-rhythms the whole bar. None of them touch the gh#226 shrink contract (start clips, center yields first, end stays anchored inline-end)."},{name:"--{dialog,sheet}-overlay-background / --app-shell-mobile-nav-background (+ -alpha)",category:"component",tier:"component",role:"Overlay scrims. All three are ROLE-MIRROR knobs declared `initial` at `:root`, with the default resolved at the CALL SITE as a SHARE of the one semantic `--overlay-background` \u2014 `color-mix(in srgb, var(--overlay-background) var(--<x>-overlay-alpha), transparent)`. Previously each carried a private literal, so setting `--overlay-background` had NO effect anywhere. The share knobs (`--dialog-overlay-alpha` 60%, `--sheet-overlay-alpha` 40%, `--app-shell-mobile-nav-alpha` 40%) keep every default byte-identical (0.3 / 0.2 / 0.2 black) while letting one `--overlay-background` override retint the whole system, including scoped `[data-tenant]` / `.dark` themes. Setting a `*-overlay-background` knob directly still wins outright. NOTE: overlays render in a portal, so a multi-tenant scope must also sit on the portal container."}];function N(t){return C.filter(a=>a.category===t)}var q=[{name:"--activity-mark-size",value:"0.25em",description:"Mark geometry. `em`-based so the whole mark tracks `--activity-font-size-*` (and therefore * `--font-size-base` and the density axis) instead of pinning a px diameter per size step."},{name:"--activity-mark-offset",value:"0.25em",description:"Travel of one `dots` step \u2014 the loop counterpart to `--reveal-distance`, named to match. * It is ALSO the height the mark row reserves, so the indicator appearing under a composer * never shifts the row."},{name:"--activity-mark-rest-alpha",value:"0.35",description:"Resting opacity of a dot at the bottom of its cycle. Never 0: a dot that vanishes reads as a * rendering bug at the trough, and the reduced-motion resting state must stay fully legible."},{name:"--activity-pulse-mark-size",value:"0.5em",description:"The single `pulse` mark \u2014 a breathing dot (live / recording), bigger than one `dots` dot * because it carries the whole affordance alone."},{name:"--activity-gap",value:"var(--space-1)",description:"Mark-to-mark and mark-to-label gap."},{name:"--activity-font-size-xs",value:"var(--font-size-2xs)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-font-size-sm",value:"var(--font-size-xs)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-font-size-md",value:"var(--font-size-sm)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-font-size-lg",value:"var(--font-size-lg)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-bar-width",value:"4rem",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-radius",value:"var(--radius-pill)",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-segment-width",value:"40%",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-track-alpha",value:"0.2",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-height",value:"0.25em",description:"scale-exempt: an em thickness that must track the label beside it, not a fixed band step"},{name:"--activity-color",value:"initial",description:'Mark colour. ROLE-MIRROR KNOB \u21D2 declared `initial` here with the role default at the CALL SITE * (`hsl(var(--activity-color, var(--muted-foreground)))` in styles/motion.css). Written as * `--activity-color: var(--muted-foreground)` it would freeze at the `:root` role value and a * scoped `[data-tenant]` / `.dark` override of `--muted-foreground` would never reach it. * @see docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`". * Documented default: --activity-color = var(--muted-foreground) (tone="muted").'},{name:"--badge-space-gap",value:"var(--space-inline-xs)",description:"Badge component tokens."},{name:"--badge-space-x",value:"var(--space-2)",description:"Badge component tokens."},{name:"--badge-space-y",value:"var(--space-1)",description:"Badge component tokens."},{name:"--badge-font-size",value:"var(--font-size-xs)",description:"Small-by-design (badge/pill/counter). A knob (rule #45) so a service can * re-tune badge text without touching the global --font-size-xs step."},{name:"--badge-line-height",value:"calc(1 / 0.75)",description:"Companion to --badge-font-size (gh#260). The cva's old `text-xs` utility also set * line-height via Tailwind's default `--text-xs--line-height: calc(1 / 0.75)` (the theme * remaps --text-xs but never that companion). Same unitless ratio here keeps the default * badge pixel-identical now that badge-layout.css owns the type metrics."},{name:"--badge-icon-size",value:"var(--icon-size-xs)",description:"Leading/trailing glyph inside the pill. It was a bare `0.75rem` in badge-layout.css with no * token at all, so a service could only resize it with `!important` or a forked stylesheet \u2014 * the two routes the icon axis's tier 2 exists to abolish (gh#326). Same step, so nothing * moves. Matches --badge-font-size's step by design: the glyph reads as a character in the * label's run, so the two retune together. Deliberately NOT --scaling-multiplied \u2014 the literal * it replaces did not track density."},{name:"--banner-radius",value:"0",description:"Square corners \u2014 a strip spans its container edge-to-edge, so it carries no radius."},{name:"--banner-border-width",value:"1px",description:"The strip keeps a single hairline block-end rule (tone-coloured via the alert * border rules); inline/block-start edges are open so it reads as part of the frame."},{name:"--banner-space-inset-block",value:"var(--space-3)",description:"Strip inset. Block is denser than the inline Alert card; inline defaults to the * page gutter so banner text aligns with page content under AppShell/PageContainer."},{name:"--banner-space-inset-inline",value:"var(--space-page-active-x, var(--space-6))",description:"Strip inset. Block is denser than the inline Alert card; inline defaults to the * page gutter so banner text aligns with page content under AppShell/PageContainer."},{name:"--card-space-inset",value:"var(--space-section-active)",description:"Horizontal inset of every slot (header / content / footer) + the resting top/bottom * shell padding. This is the column the title, body and footer all align to."},{name:"--card-space-shell-y",value:"initial",description:'BLOCK-axis (top/bottom) shell padding of the slotted card rhythm \u2014 a plain header\'s top, a * `solo` body\'s top, and the terminal slot\'s bottom (gh#232). Split off --card-space-inset so a * shell (AuthShell compact) can retune the card\'s height WITHOUT moving the inline column. * Declared `initial` \u2014 NOT `var(--card-space-inset)` \u2014 so the default resolves at the CALL SITE: * a :root binding would freeze at the :root inset and `[data-density="tight"|"cozy"]` (which * override --card-space-inset on the card element) would stop reaching it. See docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`" \u2014 the same call-site rule applies to derived knobs.'},{name:"--card-space-header-y",value:"var(--space-stack-sm)",description:"Vertical padding of a BANDED header band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-body-y",value:"var(--space-section-active)",description:"Gap between the header and the body, and the body's own top padding \u2014 the breathing * room under a title before content begins."},{name:"--card-space-solo-y",value:"initial",description:'Symmetric block padding for a SOLO content slot, so a composed shell can tune block and * inline spacing independently without targeting CardContent. * Declared `initial` for the SAME reason as --card-space-shell-y above: written as * `var(--card-space-inset)` here it would freeze at the :root inset, and a card carrying * [data-density="tight"|"cozy"] (which override --card-space-inset ON THE CARD) would keep the * :root value. The default now resolves at the call site through the chain * solo-y \u2192 shell-y \u2192 inset, so a solo body follows the general block knob when only that is * set, and an explicit solo-y still wins.'},{name:"--card-space-footer-y",value:"var(--space-stack-sm)",description:"Vertical padding of a SEPARATED footer band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-divided-y",value:"var(--card-space-header-y)",description:"DIVIDED-section vertical padding (rule #44/#45). A header/footer that carries a divider * border (banded header, separated footer) reads as its own band, so it pads SYMMETRICALLY * top+bottom \u2014 distinct from a plain header that flows into the body (top inset, no bottom). * One themeable knob keeps the header- and footer-band rhythm in sync; a service theme tunes * the band density here instead of forking per-slot CSS."},{name:"--card-space-gap",value:"var(--space-stack-xs)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-size",value:"var(--font-size-base)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-line-height",value:"var(--line-height-tight)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-weight",value:"var(--font-weight-semibold)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-font-size",value:"var(--font-size-sm)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-line-height",value:"var(--line-height-normal)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-background",value:"initial",description:'Card fill + edge \u2014 opt-in knobs that DEFAULT to the live --card / --border roles. Declared * `initial` (not `var(--card)`) so the default re-resolves at the call site under a scoped theme: * a :root binding to a role var freezes at the :root value and a scoped `[data-tenant]` override of * the role never reaches it (see docs/STANDARDS-vocabulary-tokens.md \xB7 "role-mirror knobs"). A * service still overrides the knob directly (--card-background: \u2026) to win over the role default.'},{name:"--card-border",value:"initial",description:"default = hsl(var(--card))"},{name:"--card-header-background",value:"initial",description:"Banded-header fill \u2014 role-tintable (rule #45): a service points this at any role, * e.g. --card-header-background: var(--primary), and tunes --card-header-background-alpha for * the wash strength. Default = the live --muted role (resolved at the call site)."},{name:"--card-header-background-alpha",value:"0.55",description:"default = hsl(var(--muted))"},{name:"--card-header-border-bottom",value:"initial",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-radius",value:"var(--radius-xl)",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-shadow",value:"var(--shadow-sm)",description:"The DXS hi-fi baseline uses a quiet 10px data surface with one shadow-sm * elevation layer. Consumers can still flatten or lift cards through this knob."},{name:"--card-glow",value:"0 0 0 0 transparent",description:"Brand glow layer \u2014 invisible no-op at rest (rule #44). Paired AFTER --card-shadow in the * surface box-shadow so a service can wash every card with the global glow, e.g. * --card-glow: var(--shadow-glow), with no markup change."},{name:"--card-tint",value:"transparent",description:"Fill tint \u2014 subtle role wash over the card background (default transparent = invisible). * Painted as an overlay so a service sets --card-tint: hsl(var(--primary) / 0.04) once."},{name:"--card-accent-rail-width",value:"var(--stroke-2xl)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--card-accent-perimeter-width",value:"var(--stroke-hairline)",description:'Accent placement `perimeter` (gh#12) \u2014 the FULL attention border. Two knobs so a service can * tune the edge weight and the outer ring independently; the defaults reproduce the optical * weight of `variant="featured"` (1px border + 1px ring) in the card\'s own semantic accent tone * instead of --primary. Structural 1px literals, like the base card hairline.'},{name:"--card-accent-perimeter-ring-width",value:"var(--stroke-hairline)",description:'Accent placement `perimeter` (gh#12) \u2014 the FULL attention border. Two knobs so a service can * tune the edge weight and the outer ring independently; the defaults reproduce the optical * weight of `variant="featured"` (1px border + 1px ring) in the card\'s own semantic accent tone * instead of --primary. Structural 1px literals, like the base card hairline.'},{name:"--card-featured-border-color",value:"initial",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--card-featured-ring-width",value:"var(--stroke-hairline)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-label-font-size",value:"var(--font-size-xs)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-label-font-weight",value:"var(--font-weight-medium)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-label-letter-spacing",value:"0.04em",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-value-font-size",value:"var(--font-size-2xl)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-value-line-height",value:"1.1",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-value-font-weight",value:"var(--font-weight-semibold)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-hint-font-size",value:"var(--font-size-xs)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-gap",value:"var(--space-stack-xs)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-size",value:"var(--icon-size-2xl)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-glyph-size",value:"var(--icon-size-lg)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-radius",value:"var(--radius-md)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-background",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-icon-foreground",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-delta-font-size",value:"var(--font-size-xs)",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-space-gap",value:"var(--space-stack-sm)",description:"\u2500\u2500 ServiceLauncherCard (gh#219) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Internal rhythm of a launcher tile: one gap knob drives the vertical stack AND the * icon\u2194title\u2194status row, so a service theme retunes the whole tile density once."},{name:"--card-service-launcher-icon-size",value:"calc(var(--icon-size-2xl) * var(--scaling))",description:"Semantic icon surface \u2014 36px, the `--icon-size-2xl` step, multiplied by --scaling so the * medallion still breathes with its sibling controls (the gh#328 rule: an icon that wants * density says so itself). It read `var(--control-height-lg)` until gh#324: a CONTROL tier is * the wrong axis for an icon box, and the bug was visible \u2014 `@media (pointer: coarse)` lifts * the control ladder to the 44px tap floor, so on every touch device the medallion silently * inflated to 48px while the glyph inside it stayed 20px."},{name:"--card-service-launcher-icon-glyph-size",value:"calc(var(--icon-size-lg) * var(--scaling))",description:"Semantic icon surface \u2014 36px, the `--icon-size-2xl` step, multiplied by --scaling so the * medallion still breathes with its sibling controls (the gh#328 rule: an icon that wants * density says so itself). It read `var(--control-height-lg)` until gh#324: a CONTROL tier is * the wrong axis for an icon box, and the bug was visible \u2014 `@media (pointer: coarse)` lifts * the control ladder to the 44px tap floor, so on every touch device the medallion silently * inflated to 48px while the glyph inside it stayed 20px."},{name:"--card-service-launcher-icon-radius",value:"var(--radius-md)",description:"Semantic icon surface \u2014 36px, the `--icon-size-2xl` step, multiplied by --scaling so the * medallion still breathes with its sibling controls (the gh#328 rule: an icon that wants * density says so itself). It read `var(--control-height-lg)` until gh#324: a CONTROL tier is * the wrong axis for an icon box, and the bug was visible \u2014 `@media (pointer: coarse)` lifts * the control ladder to the 44px tap floor, so on every touch device the medallion silently * inflated to 48px while the glyph inside it stayed 20px."},{name:"--card-service-launcher-icon-background",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-icon-foreground",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-unavailable-icon-background",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-unavailable-icon-foreground",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-description-font-size",value:"var(--font-size-xs)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-description-line-height",value:"var(--line-height-body)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-metadata-font-size",value:"var(--font-size-xs)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-metadata-line-height",value:"var(--line-height-normal)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-cta-min-height",value:"calc(var(--control-height-lg) * 4)",description:"Dashed catalog/add companion tile: kept tall enough to sit level with a populated launcher * tile in the same grid row. Four control tiers \u2248 medallion + title + description + action."},{name:"--card-service-launcher-skeleton-title-width",value:"var(--space-12)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--card-service-launcher-skeleton-status-width",value:"var(--space-10)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--chart-trend-plot-height-xs",value:"3.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-sm",value:"5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-md",value:"7.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-lg",value:"10rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height",value:"var(--chart-trend-plot-height-xs)",description:"Active tier \u2014 chart-layout.css re-points this per `data-size`; overriding it directly * pins one height for every trend regardless of `size`."},{name:"--chart-trend-bar-gap",value:"var(--space-2)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-radius",value:"var(--radius-sm)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-max-width",value:"1.5rem",description:"Ceiling on a single mark so a 3-point trend does not render slabs."},{name:"--chart-trend-bar-min-height",value:"var(--stroke-md)",description:"Floor so a zero/absent value still reads as a plotted category rather than a hole."},{name:"--chart-trend-bar-background",value:"initial",description:'Muted mark fill \u2014 role-mirror knob, declared `initial` so the role default re-resolves * at the CALL SITE under `.dark` / `[data-tenant]` (docs/TOKENS.md \xB7 "Role-mirror knobs * MUST be `initial`"). Default = hsl(var(--muted-foreground) / 0.75), which clears the * 3:1 non-text contrast floor (WCAG 1.4.11) against the card surface.'},{name:"--chart-trend-bar-background-alpha",value:"0.75",description:"Opacity applied to the muted-fill role default."},{name:"--chart-trend-bar-emphasis-background",value:"initial",description:'Emphasized ("current") mark fill \u2014 role-mirror knob. Default = hsl(var(--primary)).'},{name:"--chart-trend-baseline-border",value:"initial",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-gap",value:"var(--space-1)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-font-size",value:"var(--font-size-xs)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-footer-gap",value:"var(--space-2)",description:"Gap between the plot block and the optional activity footer slot."},{name:"--control-height-compact",value:"var(--band-height-sm)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-default",value:"var(--band-height-md)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-comfortable",value:"var(--band-height-xl)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-compact",value:"var(--space-2)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-default",value:"var(--space-3)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-comfortable",value:"var(--space-4)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height",value:"calc(var(--control-height-default) * var(--scaling))",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-sm",value:"calc(var(--control-height) - calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-lg",value:"calc(var(--control-height) + calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-xs",value:"calc(var(--control-height) - calc(0.5rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-padding-x",value:"var(--control-padding-x-default)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap",value:"var(--space-inline-sm)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap-sm",value:"var(--space-inline-xs)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-radius",value:"var(--radius)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--button-radius",value:"var(--radius-md)",description:"Button corner radius \u2014 defaults to the button's historical `rounded-md` so nothing * changes by default, but is its OWN knob so a service theme can retune the button * radius INDEPENDENTLY of input/control radius (issue #124)."},{name:"--control-font-size",value:"var(--font-size-base)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-border-width",value:"var(--stroke-hairline)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-shadow",value:"var(--shadow-xs)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size",value:"calc(var(--icon-size-md) * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size-sm",value:"calc(var(--icon-size-sm) * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--toggle-focus-ring-width",value:"var(--stroke-lg)",description:"Ring knobs for the two controls that need a softer, heavier mark than the * global default \u2014 both are filled surfaces where a hard 2px ring reads as a * second border. Values preserve the look these controls always had; they are * knobs now instead of hand-written box-shadows (styles/focus-ring.css)."},{name:"--toggle-focus-ring-alpha",value:"0.35",description:"Ring knobs for the two controls that need a softer, heavier mark than the * global default \u2014 both are filled surfaces where a hard 2px ring reads as a * second border. Values preserve the look these controls always had; they are * knobs now instead of hand-written box-shadows (styles/focus-ring.css)."},{name:"--rating-focus-ring-offset",value:"2px",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--checkbox-size",value:"calc(1rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--checkbox-size-compact",value:"0.875rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--checkbox-size-comfortable",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-gap",value:"var(--space-inline-sm)",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-group-gap-x",value:"var(--space-6)",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-group-gap-y",value:"var(--space-3)",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-description-gap",value:"0.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-control-offset",value:"0.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-width",value:"calc(2.25rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-width-compact",value:"2rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-width-comfortable",value:"2.5rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-height",value:"calc(1.25rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-height-compact",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-height-comfortable",value:"1.375rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-size",value:"calc(1rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-size-compact",value:"0.875rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-size-comfortable",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-translate",value:"calc(1rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-translate-compact",value:"0.875rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-translate-comfortable",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--slider-track-height",value:"0.375rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--slider-thumb-size",value:"1rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--otp-slot-size",value:"initial",description:'InputOTP slot box (gh#233). Its own knob so an auth surface can widen the 6-slot challenge row * to fill a wide panel WITHOUT re-scoping --control-height (which would resize every other * control in the same card). Declared `initial` \u2014 the tier-mirror form of the role-mirror rule in * docs/TOKENS.md: the default must resolve at the CALL SITE (`var(--otp-slot-size, * var(--control-height))`), because `--otp-slot-size: var(--control-height)` here would FREEZE at * the :root tier (32px) and an OTP row inside `.ui-auth-shell[data-variant="canonical"]`, which * re-scopes --control-height to 36px, would silently shrink. Verified in Chromium: 36px before * and after. A service opts in with a NAMED tier (`var(--control-height-lg)`), never a calc.'},{name:"--otp-slot-inline-size",value:"initial",description:'Per-AXIS overrides of the slot box (gh#12). --otp-slot-size stays the square shorthand; these * two win over it when set, so a code field can be WIDER-than-tall or TALLER-than-wide without * abandoning the token. A canonical device-grant code field is 27.5x52 per slot (a 4-slot * `appearance="grouped"` box of 112x54 with its 1px group border) \u2014 not expressible with one * square knob, which is why grouped OTP measured 146x38 against that artboard. `initial` for the * same tier-mirror reason as --otp-slot-size: the whole fallback chain * (axis \u2192 square \u2192 --control-height) must resolve at the CALL SITE so a shell that re-scopes * --control-height still reaches an OTP row that sets neither axis.'},{name:"--otp-slot-block-size",value:"initial",description:"default = var(--otp-slot-size, var(--control-height))"},{name:"--otp-container-align",value:"flex-start",description:'Main-axis alignment of the whole OTP row (rule #44/#45 \u2014 chrome is a token, default quiet). * `flex-start` is the historical behaviour, so an existing field is byte-identical; a centred * code field is `align="center"` on InputOTP (which wins over this knob) or this token set once * in a service theme. It exists because EVERY consumer was wrapping .ui-otp-container in a * flex-centring div to get a centred challenge.'},{name:"--checkbox-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-on-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-track-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-range-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-unchecked-background",value:"initial",description:'Switch OFF track fill (gh#315) \u2014 `initial`, default hsl(var(--input)). Its own knob so a * service can quieten the off-track without dragging the --input control-boundary role back * below the WCAG SC 1.4.11 floor (rule #45); whatever you set still owes 3:1 against the page * and against the thumb (--background), or "off" stops being a visible state.'},{name:"--color-picker-input-width",value:"6.5rem",description:"ColorPicker \u2014 width of the hex text field beside the swatch."},{name:"--command-list-max-height",value:"min(300px, 50vh)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-input-padding-x",value:"var(--space-3)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-group-padding",value:"var(--space-1)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-item-padding-y",value:"var(--space-2)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-item-padding-x",value:"var(--space-2)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-width",value:"35rem",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-inset-top",value:"calc(var(--space-6) * 4)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-viewport-inset",value:"var(--space-3)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-hint-padding-y",value:"var(--space-2)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-hint-padding-x",value:"var(--space-4)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-hint-gap",value:"var(--space-4)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--search-input-edge-inset",value:"var(--space-3)",description:"SearchInput \u2014 the inset the leading/trailing icons sit at, and the field padding derived * from it so the text never runs under an icon."},{name:"--search-input-start-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:"SearchInput \u2014 the inset the leading/trailing icons sit at, and the field padding derived * from it so the text never runs under an icon."},{name:"--search-input-end-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:"SearchInput \u2014 the inset the leading/trailing icons sit at, and the field padding derived * from it so the text never runs under an icon."},{name:"--choice-description-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--color-picker-hex-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--command-group-heading-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--search-input-label-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--tag-input-chip-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--toggle-sm-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--button-sm-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--control-affix-inset-inline-end",value:"var(--space-2)",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-action-size",value:"1.5rem",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-action-radius",value:"var(--radius-sm)",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-icon-size",value:"var(--icon-size-md)",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-rest-alpha",value:"0.5",description:"Resting weight of an affix glyph \u2014 it must read as secondary until hovered/focused."},{name:"--control-trigger-space-inline-end",value:"2.25rem",description:"2.25rem \u2014 the room a single trailing affix needs. Written as a raw rem because the * literal it replaced (`pe-9`) is a flat Tailwind step, not a density-scaled one."},{name:"--control-inline-affix-size",value:"1.25rem",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-icon-size",value:"var(--control-affix-icon-size)",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-space-gap",value:"var(--space-1)",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-inset-inline",value:"var(--space-2)",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-rest-alpha",value:"0.7",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-space-inline-end",value:"2.25rem",description:"Inline room the field reserves so its text never runs under a single affix."},{name:"--textarea-clear-inset-block-start",value:"var(--space-2)",description:"Input centres its affix on the field's single line; a textarea has no single line to centre * on, so its clear control parks at the top-end corner instead."},{name:"--control-multiline-padding-block",value:"var(--space-2)",description:"MULTILINE BOX \u2014 the block padding of `.ui-control-multiline` (Textarea). It was a bare * `--space-2` inside the padding shorthand, which made it unreachable for a service that runs a * denser composer, AND it is the constant the auto-grow row maths has to add back, so the two * had to read the same knob or drift (#45)."},{name:"--textarea-autogrow-line-height",value:"initial",description:'The unit a "row" is measured in. `initial` on purpose: the call site reads * `var(--textarea-autogrow-line-height, var(--line-height-normal))`, so a service that retunes * the multiline line-height for CJK legibility retunes the ceiling with it (#46). Unitless \u2014 * it is multiplied by `--control-font-size`.'},{name:"--textarea-autogrow-min-height-rows",value:"1",description:"Floor of the grown box, in text rows. The `minRows` prop overrides per instance \u2014 the same * theme-global / prop-local priority as `--form-label-width` vs `labelWidth`. The floor is * additionally clamped up to `--control-height` so a resting one-row composer still lines up * with the Input / Button beside it instead of undercutting the control tier."},{name:"--textarea-autogrow-max-height-rows",value:"8",description:"Ceiling of the grown box, in text rows; past it the control scrolls internally instead of * pushing the page. `maxRows={0}` sets this to `infinity` for an unbounded box."},{name:"--textarea-autogrow-box-inset",value:"calc( (var(--control-multiline-padding-block) + var(--control-border-width)) * 2 )",description:"The non-text part of the box the row maths must add back: block padding + border, both * edges. `.ui-textarea-autogrow--ghost` re-declares it without the border, because the ghost * variant drops its own chrome."},{name:"--control-inline-affix-pair-space-inline-end",value:"3.5rem",description:"A field parking TWO inline affixes (TimePicker's clear+clock, DatePicker's clear+calendar) * reserves a wider end than the single-affix case."},{name:"--control-composite-field-space-gap",value:"var(--space-2)",description:"COMPOSITE FIELD \u2014 the bordered box that wraps two inputs plus a separator (date/month * range pickers) or one input plus affixes. Shared so the four range/picker fields cannot * drift into four slightly different boxes."},{name:"--input-file-button-height",value:"var(--band-height-sm)",description:"The <input type=file> button is a browser-owned box we restyle; it sits one tier below the * field so it reads as a control INSIDE the control."},{name:"--input-file-button-font-size",value:"var(--font-size-sm)",description:"The <input type=file> button is a browser-owned box we restyle; it sits one tier below the * field so it reads as a control INSIDE the control."},{name:"--search-select-panel-max-width",value:"32rem",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-panel-viewport-inset",value:"var(--space-6)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-list-space-inset",value:"var(--space-1)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-footer-space-inset",value:"var(--space-1)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-option-space-gap",value:"var(--space-2)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-option-font-size",value:"var(--font-size-sm)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-option-sublabel-font-size",value:"var(--font-size-xs)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-status-space-inline",value:"var(--space-2)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-status-space-block",value:"var(--space-3)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-placeholder-space-block",value:"var(--space-6)",description:"Empty/error states sit alone in the panel, so they get a taller, centred block."},{name:"--search-select-status-font-size",value:"var(--font-size-sm)",description:"Empty/error states sit alone in the panel, so they get a taller, centred block."},{name:"--search-select-spinner-size",value:"1rem",description:"Empty/error states sit alone in the panel, so they get a taller, centred block."},{name:"--cascader-column-min-width",value:"9rem",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-columns-max-height",value:"min(280px, 50vh)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-list-max-height",value:"min(300px, 50vh)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-panel-space-inset",value:"var(--space-1)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-space-inline",value:"var(--space-3)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-space-block",value:"var(--space-2)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-space-gap",value:"var(--space-1)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-font-size",value:"var(--font-size-sm)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-icon-size",value:"var(--icon-size-md)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-result-space-inline",value:"var(--space-2)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-result-space-block",value:"0.375rem",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-result-radius",value:"var(--radius-sm)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-result-icon-space-inline-end",value:"var(--space-2)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-empty-space-block",value:"var(--space-6)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--select-content-max-height",value:"24rem",description:"SELECT \u2014 the Radix listbox surface. Its popup shares the menu row rhythm (--menu-item-*), * so only what is genuinely its own lives here (#319)."},{name:"--select-scroll-button-space-block",value:"var(--space-1)",description:"SELECT \u2014 the Radix listbox surface. Its popup shares the menu row rhythm (--menu-item-*), * so only what is genuinely its own lives here (#319)."},{name:"--select-item-space-inline",value:"0.625rem",description:"Select rows sit slightly wider than a menu row: a listbox has no leading icon column to * absorb the optical inset, so 2.5 reads level with a menu's 2 + icon."},{name:"--time-picker-column-height",value:"13rem",description:"TIME PICKER \u2014 scroll columns of hour/minute/second plus an inline affix pair. All of its * geometry was literal on the component (#319): a service could not shorten the column, widen * the panel for a 12-hour layout, or retune the row rhythm without forking."},{name:"--time-picker-panel-width",value:"9rem",description:"TIME PICKER \u2014 scroll columns of hour/minute/second plus an inline affix pair. All of its * geometry was literal on the component (#319): a service could not shorten the column, widen * the panel for a 12-hour layout, or retune the row rhythm without forking."},{name:"--time-picker-panel-width-12h",value:"13rem",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-heading-space-inline",value:"var(--space-1)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-heading-space-block",value:"0.375rem",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-heading-font-size",value:"var(--font-size-xs)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-column-space-inset",value:"var(--space-1)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-option-space-block",value:"0.375rem",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-option-radius",value:"var(--radius-md)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-option-font-size",value:"var(--font-size-sm)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-footer-space-inset",value:"var(--space-2)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-affix-space-gap",value:"var(--control-inline-affix-space-gap)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--tree-select-list-max-height",value:"min(300px, 50vh)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-space-block",value:"0.375rem",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-space-inline-end",value:"var(--space-2)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-radius",value:"var(--radius-sm)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-font-size",value:"var(--font-size-sm)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-depth-space-base",value:"var(--space-2)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-depth-space-step",value:"1.25rem",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-toggle-size",value:"1.25rem",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-toggle-space-inline-end",value:"var(--space-1)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-label-space-gap",value:"var(--space-2)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-empty-space-block",value:"var(--space-6)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--calendar-space-inset",value:"var(--space-3)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-month-space-gap",value:"var(--space-4)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-caption-space-inline",value:"var(--space-8)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-caption-font-size",value:"var(--font-size-sm)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-nav-space-block-start",value:"var(--space-3)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-nav-space-inline",value:"var(--space-1)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-nav-rest-alpha",value:"0.7",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-grid-space-block-start",value:"var(--space-4)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-week-space-block-start",value:"var(--space-2)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-weekday-font-size",value:"var(--font-size-xs)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-weekday-radius",value:"var(--radius-md)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-day-font-size",value:"var(--font-size-sm)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-day-radius",value:"var(--radius-md)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-chevron-size",value:"var(--icon-size-md)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--transfer-pane-min-height",value:"14rem",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-pane-radius",value:"var(--radius-md)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-panes-space-gap",value:"var(--space-3)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-space-inline",value:"var(--space-3)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-space-block",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-font-size",value:"var(--font-size-sm)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-space-gap",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-search-space-inset",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-list-space-inset",value:"var(--space-1)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-space-inline",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-space-block",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-space-gap",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-radius",value:"var(--radius-sm)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-font-size",value:"var(--font-size-sm)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-check-space-block-start",value:"0.125rem",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-row-description-font-size",value:"var(--font-size-xs)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-empty-space-block",value:"var(--space-8)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-actions-space-gap",value:"var(--space-2)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-action-icon-size",value:"var(--icon-size-md)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--month-picker-panel-space-inset",value:"var(--space-3)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-grid-space-block-start",value:"var(--space-3)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-grid-space-gap",value:"var(--space-1)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-cell-space-inline",value:"var(--space-4)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-nav-font-size",value:"var(--font-size-sm)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-nav-rest-alpha",value:"var(--calendar-nav-rest-alpha)",description:"Year-nav chevrons read as secondary until hovered \u2014 same treatment as the Calendar nav, so * the two panels stay visually consistent when either is retuned."},{name:"--month-picker-icon-size",value:"var(--icon-size-md)",description:"Year-nav chevrons read as secondary until hovered \u2014 same treatment as the Calendar nav, so * the two panels stay visually consistent when either is retuned."},{name:"--month-picker-separator-icon-size",value:"var(--icon-size-sm)",description:"Year-nav chevrons read as secondary until hovered \u2014 same treatment as the Calendar nav, so * the two panels stay visually consistent when either is retuned."},{name:"--button-xs-height",value:"var(--control-height-xs)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-space-inline",value:"var(--space-2)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-space-gap",value:"var(--space-1)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-font-size",value:"var(--font-size-xs)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-icon-size",value:"var(--icon-size-xs)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-icon-space-inline-md",value:"var(--space-3)",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-icon-space-inline-sm",value:"0.625rem",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-icon-space-inline-lg",value:"var(--space-4)",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-icon-space-inline-xs",value:"0.375rem",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-space-block",value:"var(--space-2)",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--otp-caret-block-size",value:"1rem",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--otp-caret-inline-size",value:"var(--stroke-hairline)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--otp-separator-icon-size",value:"var(--icon-size-md)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-min-width",value:"var(--space-4)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-space-inline",value:"var(--space-1)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-font-size",value:"var(--font-size-xs)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-default-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-default-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-destructive-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-destructive-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-secondary-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-secondary-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--control-label-font-size",value:"var(--font-size-sm)",description:"LABEL \u2014 the Label primitive's own box and type (#319). Every one of these was a Tailwind * literal baked into label.tsx (`text-sm leading-none flex items-center gap-2`), so a service * whose form grid wanted a smaller/denser label had to hand-write font-size on every call site * (which is exactly why --form-label-font-size had to be invented as a per-instance escape). * They live under the `control` prefix because Label is a control-family primitive and * src/tokens/components/ has no `label` file of its own. * * Defaults reproduce today's rendering exactly: * font-size \u2190 `text-sm` \u2192 --text-sm \u2192 var(--font-size-sm) * line-height \u2190 `leading-none` \u2192 1 (leading-none beats text-sm's companion line-height)"},{name:"--control-label-line-height",value:"1",description:"LABEL \u2014 the Label primitive's own box and type (#319). Every one of these was a Tailwind * literal baked into label.tsx (`text-sm leading-none flex items-center gap-2`), so a service * whose form grid wanted a smaller/denser label had to hand-write font-size on every call site * (which is exactly why --form-label-font-size had to be invented as a per-instance escape). * They live under the `control` prefix because Label is a control-family primitive and * src/tokens/components/ has no `label` file of its own. * * Defaults reproduce today's rendering exactly: * font-size \u2190 `text-sm` \u2192 --text-sm \u2192 var(--font-size-sm) * line-height \u2190 `leading-none` \u2192 1 (leading-none beats text-sm's companion line-height)"},{name:"--control-label-space-gap",value:"0.5rem",description:"Label \u2192 addon/required-marker gap. A RAW rem, deliberately NOT var(--space-2): the `gap-2` it * replaces reads Tailwind's own --spacing grid, which this repo does NOT density-scale, so a * --space-* default would silently resize every label row under a compact/comfortable density. * A service that WANTS it to follow density points the knob at var(--space-2) itself."},{name:"--control-label-disabled-alpha",value:"0.7",description:"Weight of a label whose `peer` control is disabled. Deliberately its OWN knob rather than the * global --disabled-opacity (0.5): a label must stay READABLE next to a disabled control \u2014 it is * still the field's accessible name \u2014 so it rests one step heavier than the control it names. * Default = the historical `peer-disabled:opacity-70`."},{name:"--control-height-compact",value:"var(--band-height-xl)",description:"Weight of a label whose `peer` control is disabled. Deliberately its OWN knob rather than the * global --disabled-opacity (0.5): a label must stay READABLE next to a disabled control \u2014 it is * still the field's accessible name \u2014 so it rests one step heavier than the control it names. * Default = the historical `peer-disabled:opacity-70`."},{name:"--control-height-default",value:"var(--band-height-xl)",description:"Weight of a label whose `peer` control is disabled. Deliberately its OWN knob rather than the * global --disabled-opacity (0.5): a label must stay READABLE next to a disabled control \u2014 it is * still the field's accessible name \u2014 so it rests one step heavier than the control it names. * Default = the historical `peer-disabled:opacity-70`."},{name:"--accordion-chevron-size",value:"var(--icon-size-md)",description:'Accordion chevron and Carousel arrow. Both were bare 1rem literals in * data-display-layout.css \u2014 glyphs with no tier-2 route, invisible to the icon ratchet because * neither selector contains the word "icon".'},{name:"--carousel-arrow-icon-size",value:"var(--icon-size-md)",description:'Accordion chevron and Carousel arrow. Both were bare 1rem literals in * data-display-layout.css \u2014 glyphs with no tier-2 route, invisible to the icon ratchet because * neither selector contains the word "icon".'},{name:"--accordion-focus-ring-offset",value:"2px",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--carousel-dot-focus-ring-offset",value:"2px",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--progress-label-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--tree-item-title-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--tree-item-description-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--timeline-note-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--avatar-background",value:"initial",description:"Avatar surface \u2014 `initial` so the --muted default re-resolves at the call site under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override never reaches it). A service re-tints the placeholder fill once (e.g. --avatar-background: hsl(var(--accent))). Default = hsl(var(--muted))."},{name:"--avatar-tint",value:"transparent",description:"Optional role wash over the avatar (default transparent = invisible, rule #44). Painted as an overlay so a service sets --avatar-tint: hsl(var(--primary) / 0.08)."},{name:"--avatar-square-radius",value:"var(--radius-lg)",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-size",value:"var(--control-height)",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-background",value:"initial",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-foreground",value:"initial",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-tinted-background",value:"initial",description:'Capability medallion \u2014 `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability / feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square" for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override of --primary reaches them \u2014 the exact reason this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page CSS. Defaults = hsl(var(--primary) / 0.1) wash \xB7 hsl(var(--primary)) glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-tinted-foreground",value:"initial",description:'Capability medallion \u2014 `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability / feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square" for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override of --primary reaches them \u2014 the exact reason this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page CSS. Defaults = hsl(var(--primary) / 0.1) wash \xB7 hsl(var(--primary)) glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-tinted-glyph-size",value:"var(--control-icon-size)",description:'Capability medallion \u2014 `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability / feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square" for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override of --primary reaches them \u2014 the exact reason this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page CSS. Defaults = hsl(var(--primary) / 0.1) wash \xB7 hsl(var(--primary)) glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-presence-size",value:"30%",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-min-size",value:"var(--space-2)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-inset",value:"0",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-ring-width",value:"var(--stroke-md)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-stroke-width",value:"var(--stroke-sm)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-bar-inline-size",value:"56%",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-bar-block-size",value:"var(--stroke-sm)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-ring-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-online-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-away-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-busy-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-offline-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--progress-track-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--progress-fill-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--timeline-dot-done-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-dot-current-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-line-completed-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--tree-item-active-border",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--tree-item-active-background",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--qr-code-foreground",value:"0 0% 0%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-background",value:"0 0% 100%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-xs",value:"6rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-sm",value:"8rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-md",value:"10rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-lg",value:"12.5rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--scroll-area-bar-size",value:"0.625rem",description:"SCROLL AREA \u2014 the custom scrollbar rail. `.ui-scroll-area-*` did not exist at all: the whole * rail shipped as Tailwind literals on the component (`w-2.5 p-px` / `h-2.5 p-px`, * `rounded-full`), so a service could not widen the bar for a touch/coarse-pointer console or * square the thumb to match a sharp-cornered theme without forking ScrollArea (rule #45). * --scroll-area-bar-size is a RAW rem on purpose: it replaces a FLAT Tailwind step (2.5 = 10px), * not a --space-* step, so binding it to the density-scaled scale would silently change today's * rendering under a non-default --scaling. Defaults reproduce the previous rail exactly."},{name:"--scroll-area-bar-padding",value:"1px",description:"Hairline inset that keeps the thumb off the rail edge \u2014 a service running a thicker bar * usually wants a proportionally larger gutter here."},{name:"--scroll-area-thumb-radius",value:"var(--radius-pill)",description:"Hairline inset that keeps the thumb off the rail edge \u2014 a service running a thicker bar * usually wants a proportionally larger gutter here."},{name:"--permission-matrix-label-width",value:"16rem",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-role-space-gap",value:"var(--space-1)",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-name-space-gap",value:"0.375rem",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-cell-icon-size",value:"var(--control-icon-size)",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-min-width",value:"48rem",description:"Floor for the scrolling body \u2014 below this the columns collapse into unreadable slivers, so * the table scrolls instead. Mirrors --table-surface-min-inline-size on DataTable."},{name:"--password-strength-score-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--password-strength-checklist-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--branch-scope-picker-gap",value:"0.75rem",description:"Stack rhythm between the mode radios, the subset box and the error line (was `gap-3`)."},{name:"--branch-scope-picker-badges-gap",value:"0.375rem",description:"Read-only summary: the wrapped run of branch Badges (was `gap-1.5`)."},{name:"--branch-scope-picker-subset-gap",value:"0.5rem",description:'The `mode="selected"` subset box \u2014 its inner stack rhythm and the inset that clears its * indent rule (was `gap-2` / `ps-4`).'},{name:"--branch-scope-picker-subset-padding-inline",value:"1rem",description:'The `mode="selected"` subset box \u2014 its inner stack rhythm and the inset that clears its * indent rule (was `gap-2` / `ps-4`).'},{name:"--branch-scope-picker-subset-border-width",value:"var(--stroke-md)",description:"The indent rule itself is CHROME (#44), so it is a knob: a service that wants the flat * treatment sets it to 0 rather than forking the component. Default = today's `border-s-2`."},{name:"--branch-scope-picker-list-max-height",value:"16rem",description:"Bounded height of the scrollable branch list (was `max-h-64`). Flat by design: the box is a * keyboard-reachable scroll region whose job is to cap the control's measure, so it must not * grow with density and push the surrounding form off a 390px frame."},{name:"--descriptions-label-width",value:"8rem",description:'Width of the label column when <Descriptions layout="horizontal">. Labels align to this * shared column so the values line up (the horizontal-detail look, mirroring <Form layout>). * A rem value gives a fixed aligned column; set `max-content` to size each label to its text. * (rule #44/#45 \u2014 a service theme tunes it here instead of forking CSS.)'},{name:"--descriptions-row-gap",value:"var(--space-3)",description:"Row-to-row gap (gh#294). Default = the historical hardcoded `gap-y-3`, so nothing changes * visually by default. A consumer composing Descriptions beside a Form/FormField (a read-only * block above an editable field on the same card) retunes this to `var(--space-4)` to match * Form's own field-to-field rhythm instead of the two blocks reading as visually unrelated."},{name:"--descriptions-column-gap",value:"1.5rem",description:"Column-to-column gap of the <dl> grid (#319). Was a hardcoded `gap-x-6`, the one axis of the * grid rhythm a service could NOT reach while --descriptions-row-gap was already a knob \u2014 so a * 2/3-column detail block could be retuned vertically but never horizontally. * A RAW rem, deliberately NOT var(--space-6): `gap-x-6` reads Tailwind's own --spacing grid, * which this repo does NOT density-scale, so a --space-* default would silently rescale the * column gap under a compact/comfortable density. Point it at var(--space-6) to opt in."},{name:"--descriptions-label-gap",value:"0.75rem",description:'Label \u2192 value gap inside ONE item when layout="horizontal" (was `gap-x-3`). Pairs with * --descriptions-label-width: a service that narrows the label column usually wants to close * this gap in the same step. Raw rem for the same --spacing reason as above.'},{name:"--descriptions-value-font-size",value:"var(--font-size-sm)",description:"VALUE TYPOGRAPHY (gh#294 + #319). `FormField staticText` renders its read-only value with the * SAME typography as a <dd> so a static Form row and a Descriptions value are indistinguishable * when mixed on one card. That contract used to be two copies of the literal `text-sm` plus a * comment asking future editors to keep them in sync; both call sites now read these tokens, so * the mirror is mechanical and a service retunes BOTH from one place."},{name:"--descriptions-value-line-height",value:"calc(1.25 / 0.875)",description:"Companion to --descriptions-value-font-size (the gh#260 bug). The `text-sm` utility this * replaces ALSO set a line-height, via Tailwind's default `--text-sm--line-height` * (= calc(1.25 / 0.875)); the theme remaps --text-sm but never that companion. Without this the * value would silently inherit ambient leading instead of its own 20px line box."},{name:"--email-shell-width",value:"480px",description:"\u2500\u2500 Shell \u2014 the 480px transactional-email geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-shell-padding",value:"32px",description:"canonical transactional card width"},{name:"--email-shell-page-padding",value:"24px",description:"card inset \u2014 mirrors --space-8 (2rem)"},{name:"--email-shell-border-width",value:"1px",description:"gutter between the client viewport and the card (--space-6)"},{name:"--email-card-radius",value:"10px",description:"card edge \u2014 mirrors the 1px --border hairline"},{name:"--email-card-reference-height",value:"407px",description:"mirrors --card-radius (--radius-xl = 6px \xD7 \u03C6 \u2248 10px)"},{name:"--email-stack-gap",value:"24px",description:"canonical invitation reference measures 480\xD7407"},{name:"--email-stack-gap-sm",value:"12px",description:"block rhythm \u2014 mirrors --space-stack-lg (1.5rem)"},{name:"--email-mark-width",value:"22px",description:'The mark ARTWORK is the same 32\xD732 capsule + glyph `<Logo mark="godx" />` paints; only the * RENDERED BOX differs. The canonical SCR-302 header sets it at 22px next to a 13px/700 "GoDX" * wordmark, which is smaller than the web --logo-godx-size (2rem) \u2014 an email header carries the * identity, not the navigation, so the lockup sits quieter (gh#250).'},{name:"--email-mark-height",value:"22px",description:"canonical SCR-302 header lockup mark box"},{name:"--email-mark-gap",value:"8px",description:"canonical SCR-302 header lockup mark box"},{name:"--email-wordmark-font-size",value:"13px",description:"mark \u2194 wordmark \u2014 mirrors --logo-wordmark-gap (--space-2)"},{name:"--email-wordmark-font-weight",value:"700",description:"canonical wordmark size (\u2248 --font-size-xs 12.5px)"},{name:"--email-font-family-sans",value:'"Noto Sans JP", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic Medium", YuGothic, "M PLUS 2", Meiryo, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, Arial, sans-serif',description:'The DXS canonical face is Noto Sans JP (see src/styles/fonts.css; product override, direct * instruction). Email clients honour NO @font-face, so the stack ships the face NAME first and * degrades through the platform JP faces to a generic \u2014 a client with Noto Sans JP installed (or * a webmail that already loaded it, which is common \u2014 it is a widely preinstalled/CDN-cached * Google face, more likely to actually render than the niche M PLUS 2 ever was) renders the * canonical face, everything else lands on Hiragino (macOS/iOS) \u2192 Yu Gothic (Windows) \u2192 * M PLUS 2 \u2192 Meiryo \u2192 the system UI face \u2192 Arial \u2192 sans-serif. Family names are SINGLE-quoted * on export (the generator normalises) because an inline `style="\u2026"` attribute is double-quoted; * only ASCII aliases are listed, since a non-ASCII family name does not survive every transfer * encoding.'},{name:"--email-font-family-mono",value:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",description:"Tabular/reference strings \u2014 invoice ids, masked card numbers, ISO dates, amounts \u2014 are set in * the mono face in the canonical card. Mirrors --font-family-mono."},{name:"--email-body-font-size",value:"14px",description:"Tabular/reference strings \u2014 invoice ids, masked card numbers, ISO dates, amounts \u2014 are set in * the mono face in the canonical card. Mirrors --font-family-mono."},{name:"--email-body-line-height",value:"1.9",description:"1.9, NOT the web --line-height-body (1.7) \u2014 the canonical email card runs its JP body copy * looser than a screen, because a mail client gives the reader no density control."},{name:"--email-body-font-weight",value:"400",description:"1.9, NOT the web --line-height-body (1.7) \u2014 the canonical email card runs its JP body copy * looser than a screen, because a mail client gives the reader no density control."},{name:"--email-heading-font-size",value:"17px",description:"mirrors --font-weight-normal"},{name:"--email-heading-line-height",value:"1.7",description:"canonical card title (\u2248 --heading-h2, base \xD7 \u03C6^\xBC\xB2 \u2248 17.6px)"},{name:"--email-heading-font-weight",value:"500",description:"the title shares the card's body leading, not a tight ramp"},{name:"--email-cta-height",value:"44px",description:`TOUCH TARGET, deliberately DECOUPLED from --control-height-lg (36px). Do not "restore" the * mirror: email is a mobile-first, touch-only medium. There is no hover state, no precise * pointer, and mail clients do not reliably offer zoom or a focus affordance \u2014 so the web's AA * floor (SC 2.5.8, 24x24, which 36px clears) is the wrong bar here. 44px is the AAA target size * (SC 2.5.5, 44x44) and the Apple HIG 44pt / Material 48dp convention. A service that must keep * the old box sets --email-cta-height + --email-cta-line-height in its own theme.`},{name:"--email-cta-line-height",value:"44px",description:`TOUCH TARGET, deliberately DECOUPLED from --control-height-lg (36px). Do not "restore" the * mirror: email is a mobile-first, touch-only medium. There is no hover state, no precise * pointer, and mail clients do not reliably offer zoom or a focus affordance \u2014 so the web's AA * floor (SC 2.5.8, 24x24, which 36px clears) is the wrong bar here. 44px is the AAA target size * (SC 2.5.5, 44x44) and the Apple HIG 44pt / Material 48dp convention. A service that must keep * the old box sets --email-cta-height + --email-cta-line-height in its own theme.`},{name:"--email-cta-padding-x",value:"16px",description:"equal to the height: bulletproof vertical centring in Outlook"},{name:"--email-cta-radius",value:"6px",description:"mirrors --space-4"},{name:"--email-cta-font-size",value:"14px",description:"mirrors the --radius base (0.375rem)"},{name:"--email-cta-font-weight",value:"500",description:"mirrors --font-size-base"},{name:"--email-footer-font-size",value:"11px",description:"\u2500\u2500 Legal footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-footer-line-height",value:"1.8",description:"mirrors --font-size-2xs (base \xF7 \u03C6^\xBC\xB2 \u2248 11.1px)"},{name:"--email-footer-link-gap",value:"12px",description:"the legal band runs looser than the web --line-height-normal"},{name:"--email-footer-padding-top",value:"20px",description:"horizontal separation between footer links (--space-3)"},{name:"--email-footer-border-width",value:"1px",description:"space above the legal band"},{name:"--email-focus-border-width",value:"2px",description:"\u2500\u2500 Focus affordance (webmail preview panes that DO honour :focus) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-max-width",value:"520px",description:"\u2500\u2500 Mobile reflow \u2014 applied under the narrow media query and by inline fallbacks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-width",value:"100%",description:"the reflow breakpoint (card width + both gutters)"},{name:"--email-mobile-padding",value:"20px",description:"the card goes fluid below the breakpoint"},{name:"--email-mobile-page-padding",value:"12px",description:"reduced card inset \u2014 mirrors --space-5"},{name:"--email-mobile-heading-font-size",value:"16px",description:"One step below the 17px desktop title. The canonical 390px reference raster is unusable (it is * 435px wide and already clips the card \u2014 dxs-platform/platform#496), so this is the library's * documented contract rather than a measured canonical value."},{name:"--email-mobile-cta-width",value:"100%",description:"One step below the 17px desktop title. The canonical 390px reference raster is unusable (it is * 435px wide and already clips the card \u2014 dxs-platform/platform#496), so this is the library's * documented contract rather than a measured canonical value."},{name:"--error-surface-max-width",value:"32rem",description:"Measure of the surface block. Caps the status code + body + metadata column so a long ja/vi * headline wraps at a readable width in BOTH shells, including the application shell where * PageContainer is much wider than the copy wants to be."},{name:"--error-surface-gap",value:"var(--space-3)",description:"Rhythm between the status code, the EmptyState body, the metadata list and the progress meter."},{name:"--error-surface-padding-block",value:"var(--space-10)",description:"Block padding of the surface inside the shell it is placed in. Desktop (1440/1024) default; the * 390 step steps it down so a phone loses no vertical space (see styles/shell-layout.css)."},{name:"--error-surface-padding-block-compact",value:"var(--space-6)",description:"Block padding of the surface inside the shell it is placed in. Desktop (1440/1024) default; the * 390 step steps it down so a phone loses no vertical space (see styles/shell-layout.css)."},{name:"--error-surface-brand-gap",value:"var(--space-2)",description:"Brand slot (a Logo) above the status code, system mode only."},{name:"--error-surface-meta-gap",value:"var(--space-1)",description:"Metadata description list: row rhythm and the label\u2194value gap inside one row."},{name:"--error-surface-meta-row-gap",value:"var(--space-2)",description:"Metadata description list: row rhythm and the label\u2194value gap inside one row."},{name:"--error-surface-meta-border",value:"none",description:"Chrome, default quiet (#44). A service opts in with * `--error-surface-meta-border: 1px solid hsl(var(--border));`."},{name:"--error-surface-meta-padding-block",value:"0",description:"Chrome, default quiet (#44). A service opts in with * `--error-surface-meta-border: 1px solid hsl(var(--border));`."},{name:"--error-surface-progress-max-width",value:"18rem",description:"Width of the maintenance progress meter \u2014 narrower than the measure, so it reads as metadata * rather than as the primary content of the page."},{name:"--sheet-width-default",value:"22.5rem",description:"Feedback primitive tokens: dialog, alert, empty state."},{name:"--sheet-overlay-background",value:"initial",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--sheet-overlay-alpha",value:"40%",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--sheet-shadow",value:"var(--shadow-lg)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-header-space-gap",value:"0.375rem",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-title-block-space-gap",value:"var(--space-1)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-title-row-space-gap",value:"var(--space-3)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-extra-space-gap",value:"var(--space-2)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-footer-space-gap",value:"var(--space-2)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-title-font-size",value:"var(--font-size-lg)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-description-font-size",value:"var(--font-size-sm)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-body-space-block",value:"var(--space-1)",description:"The scroll region keeps a sliver of padding so a focused control's ring is never clipped at * the scroll edges \u2014 retune together with --focus-ring-width."},{name:"--sheet-close-offset",value:"var(--space-4)",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--sheet-close-rest-alpha",value:"0.7",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--sheet-header-close-space-inline-end",value:"var(--space-8)",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-width-default",value:"32rem",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-viewport-inset",value:"1.5rem",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-overlay-background",value:"initial",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-overlay-alpha",value:"60%",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-space-x",value:"var(--space-chrome-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-y",value:"var(--space-chrome-y)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-inset",value:"var(--dialog-space-y) var(--dialog-space-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-gap",value:"var(--space-stack-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-close-space-offset",value:"var(--space-4)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-header-space-gap",value:"var(--space-stack-xs)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-title-row-space-gap",value:"var(--space-3)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-title-block-space-gap",value:"var(--space-1)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-extra-space-gap",value:"var(--space-2)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-header-close-space-inline-end",value:"var(--space-8)",description:"Inline room the header reserves so a long title or the extra slot never runs under the * absolutely-positioned close button; retune together with --dialog-close-space-offset. * Mirrors --sheet-header-close-space-inline-end."},{name:"--dialog-close-rest-alpha",value:"0.7",description:"Close button rest opacity \u2014 quiet at rest, full on hover (rule #44). Was a hard 0.7 baked into * dialog-layout.css with no way for a theme to make the \u2715 louder. Mirrors * --sheet-close-rest-alpha so the two overlay siblings retune together."},{name:"--dialog-step-up-error-font-size",value:"var(--font-size-sm)",description:"Type-to-confirm challenge: the step-up failure line under the input. Its size + ink were * `text-sm text-destructive` on the component, i.e. unreachable from a theme."},{name:"--alert-radius",value:"var(--card-radius)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-space-inset",value:"var(--space-section-active)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-space-gap",value:"var(--space-inline-md)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-inner-space-gap",value:"var(--space-stack-sm)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-dismiss-space-offset",value:"var(--space-3)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-icon-size",value:"var(--icon-size-lg)",description:"Leading tone glyph (the \u26A0/\u2713/\u24D8 at the start of the strip). It was a bare `1.25rem` in * alert-layout.css with no token at all, so the ONLY ways to resize it were `!important` or a * forked stylesheet \u2014 the two things the icon axis's tier 2 exists to abolish (gh#326). Same * step, so nothing moves; a service now scopes `--alert-icon-size` to one Alert instead. * Deliberately NOT --scaling-multiplied: the literal it replaces did not track density."},{name:"--alert-dismiss-rest-alpha",value:"0.7",description:"Dismiss \u2715 \u2014 rest opacity (quiet at rest, full on hover, rule #44) and glyph size. The rest * alpha was a hard 0.7 in alert-layout.css whose `:hover` companion lived on the COMPONENT as * `hover:opacity-100`, so the two halves of one affordance sat in two files and neither was * themeable. Mirrors --sheet-close-rest-alpha. The icon size carries a raw rem, not * var(--space-N): it replaces a flat Tailwind `size-4` step and must not start tracking the * density axis."},{name:"--alert-dismiss-icon-size",value:"var(--icon-size-md)",description:"Dismiss \u2715 \u2014 rest opacity (quiet at rest, full on hover, rule #44) and glyph size. The rest * alpha was a hard 0.7 in alert-layout.css whose `:hover` companion lived on the COMPONENT as * `hover:opacity-100`, so the two halves of one affordance sat in two files and neither was * themeable. Mirrors --sheet-close-rest-alpha. The icon size carries a raw rem, not * var(--space-N): it replaces a flat Tailwind `size-4` step and must not start tracking the * density axis."},{name:"--alert-bg-alpha",value:"0.05",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--alert-border-alpha",value:"0.3",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--banner-border-block-end-width",value:"1px",description:"BANNER \u2014 the page-level Alert treatment (gh#255). Every constant a service would want to * match to its page grid is a knob (rule #45). * * --banner-radius and --banner-border-width are NOT declared here (gh#327). They used to be, * and both copies were dead: components/banner.css declares the same two names at the same * `:root`, base.css imports banner AFTER feedback, so banner won every time. Radius agreed at * `0`, but border-width did not \u2014 this file said `0` and banner.css says `1px`, and `1px` is * what alert-layout.css actually paints as the strip's block-end hairline. So anyone who read * THIS file for the default was told the strip has no rule when it has one, and the MCP token * catalog carried both entries with the two conflicting values. banner.css owns the strip * geometry; these two live there and only there. * * The inline inset tracks the live page gutter, so a banner mounted above a PageContainer lines * its text up with the page title instead of sitting at a second, unrelated margin."},{name:"--banner-space-block",value:"var(--space-3)",description:"BANNER \u2014 the page-level Alert treatment (gh#255). Every constant a service would want to * match to its page grid is a knob (rule #45). * * --banner-radius and --banner-border-width are NOT declared here (gh#327). They used to be, * and both copies were dead: components/banner.css declares the same two names at the same * `:root`, base.css imports banner AFTER feedback, so banner won every time. Radius agreed at * `0`, but border-width did not \u2014 this file said `0` and banner.css says `1px`, and `1px` is * what alert-layout.css actually paints as the strip's block-end hairline. So anyone who read * THIS file for the default was told the strip has no rule when it has one, and the MCP token * catalog carried both entries with the two conflicting values. banner.css owns the strip * geometry; these two live there and only there. * * The inline inset tracks the live page gutter, so a banner mounted above a PageContainer lines * its text up with the page title instead of sitting at a second, unrelated margin."},{name:"--banner-space-inline",value:"var(--space-page-active-x)",description:"Inline inset tracks the page gutter so a banner's text lines up with the page title. * `--space-page-active-x` steps down to the compact gutter on `.ui-page-container` only, and * custom properties inherit \u2014 so a banner rendered INSIDE the container picks the compact value * up for free, while the normal case (a banner mounted ABOVE the container, or in AppShell) would * keep the desktop gutter and sit 8px out at 390px. The compact step below is what actually keeps * the two aligned; retune it rather than hard-coding a mobile inset at the call site."},{name:"--banner-space-inline-compact",value:"var(--space-page-compact-x)",description:"Inline inset tracks the page gutter so a banner's text lines up with the page title. * `--space-page-active-x` steps down to the compact gutter on `.ui-page-container` only, and * custom properties inherit \u2014 so a banner rendered INSIDE the container picks the compact value * up for free, while the normal case (a banner mounted ABOVE the container, or in AppShell) would * keep the desktop gutter and sit 8px out at 390px. The compact step below is what actually keeps * the two aligned; retune it rather than hard-coding a mobile inset at the call site."},{name:"--banner-dismiss-space-offset",value:"var(--space-2)",description:"Dismiss offset is measured from the banner's own (shorter) block inset, so the \u2715 stays * optically centred on a one-line strip instead of floating at the inline-alert offset."},{name:"--dialog-content-glow",value:"0 0 0 0 transparent",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-y",value:"var(--space-10)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-x",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-description-max-width",value:"28rem",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-y",value:"var(--space-6)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-x",value:"var(--space-4)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-y",value:"var(--space-3)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-x",value:"var(--space-2)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-icon-foreground",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-tint",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-size",value:"var(--icon-size-4xl)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--empty-state-icon-glyph-size",value:"var(--icon-size-xl)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-row-gap",value:"var(--space-stack-sm)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-cell-gap",value:"var(--space-inline-lg)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-card-inset",value:"var(--space-section-active)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-radius",value:"var(--radius)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-background",value:"initial",description:"Skeleton placeholder fill \u2014 `initial` so the --muted default re-resolves at the call site under * a scoped theme (a :root binding to a role var freezes at :root). A service tints the shimmer to * its surface (rule #44) without forking the keyframes. Default = hsl(var(--muted))."},{name:"--skeleton-block-height",value:"1rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-caption-height",value:"0.75rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-title-height",value:"1.75rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-label-width",value:"6rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-detail-value-max-width",value:"28rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-stat-value-width",value:"8rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-stat-caption-width",value:"5rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--query-load-more-space-block-start",value:"var(--space-stack-md)",description:'QUERY LIFECYCLE FOOTERS \u2014 the two rows `<InfiniteQueryState>` appends under a paginated feed: * the "load more" button row and the "loading more\u2026" caption. Both gaps were Tailwind literals * on the component (`pt-4` / `pt-2`), so a service running a denser (or airier) feed could not * align them to its own vertical rhythm without forking the component (rule #45). They live in * the feedback tier because these ARE query feedback surfaces and there is no query stylesheet. * Defaults = var(--space-stack-md) (16px) and var(--space-stack-sm) (8px) \u2014 the exact values the * Tailwind steps resolved to, so adopting this changes nothing until a theme opts in.'},{name:"--query-loading-more-space-block-start",value:"var(--space-stack-sm)",description:'QUERY LIFECYCLE FOOTERS \u2014 the two rows `<InfiniteQueryState>` appends under a paginated feed: * the "load more" button row and the "loading more\u2026" caption. Both gaps were Tailwind literals * on the component (`pt-4` / `pt-2`), so a service running a denser (or airier) feed could not * align them to its own vertical rhythm without forking the component (rule #45). They live in * the feedback tier because these ARE query feedback surfaces and there is no query stylesheet. * Defaults = var(--space-stack-md) (16px) and var(--space-stack-sm) (8px) \u2014 the exact values the * Tailwind steps resolved to, so adopting this changes nothing until a theme opts in.'},{name:"--tooltip-max-width",value:"20rem",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-space-inline",value:"var(--space-2)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-space-block",value:"var(--space-1)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-radius",value:"var(--radius)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-font-size",value:"var(--font-size-xs)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-shadow",value:"var(--shadow-md)",description:"Raised-surface depth \u2014 role-mirror knob, so a flat service theme sets `none` once."},{name:"--tooltip-background",value:"initial",description:'Surface colours \u2014 `initial` so the popover roles re-resolve at the CALL SITE under a scoped * [data-tenant]/.dark theme (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). * Defaults = hsl(var(--popover)) fill \xB7 hsl(var(--popover-foreground)) text.'},{name:"--tooltip-foreground",value:"initial",description:'Surface colours \u2014 `initial` so the popover roles re-resolve at the CALL SITE under a scoped * [data-tenant]/.dark theme (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). * Defaults = hsl(var(--popover)) fill \xB7 hsl(var(--popover-foreground)) text.'},{name:"--tooltip-border-color",value:"initial",description:'Surface colours \u2014 `initial` so the popover roles re-resolve at the CALL SITE under a scoped * [data-tenant]/.dark theme (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). * Defaults = hsl(var(--popover)) fill \xB7 hsl(var(--popover-foreground)) text.'},{name:"--popover-width",value:"18rem",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-space-inset",value:"var(--space-4)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-radius",value:"var(--radius)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-shadow",value:"var(--shadow-md)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-header-space-gap",value:"var(--space-1)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-header-font-size",value:"var(--font-size-sm)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-surface-background",value:"initial",description:"Role-mirror knobs \u2014 `initial`, defaults resolve at the call site (see Tooltip above)."},{name:"--popover-surface-foreground",value:"initial",description:"Role-mirror knobs \u2014 `initial`, defaults resolve at the call site (see Tooltip above)."},{name:"--popover-surface-border-color",value:"initial",description:"Role-mirror knobs \u2014 `initial`, defaults resolve at the call site (see Tooltip above)."},{name:"--toast-icon-size",value:"var(--icon-size-md)",description:"TOAST (Sonner) \u2014 the status glyph in the toast's leading slot. * * WHY A KNOB AND NOT A UTILITY: sonner renders the toast body itself and takes the five status * glyphs through ONE `icons={{ success, info, warning, error, loading }}` config prop. That prop * is all-or-nothing \u2014 a consumer who wants a different glyph size must re-declare all five icons, * re-importing lucide and re-deriving the aria wiring. Routing the size through a token makes it * a one-line theme override instead (rule #45). * * Raw rem, not var(--space-N): it replaces a flat Tailwind `size-4` step, and the 16px box sonner * gives `[data-icon]` is itself fixed \u2014 scaling the glyph with density alone would overflow it. * * NAMESPACE NOTE: sonner publishes its own `--toast-*` custom properties (--toast-icon-margin-*, * --toast-svg-margin-*, --toast-button-margin-*, --toast-close-button-*). Neither name below * collides with those; keep it that way when adding to this group."},{name:"--toast-mobile-offset",value:"16px",description:"Viewport gutter of the mobile toast stack. Sonner passes `mobileOffset` straight into inline * CSS, so a var() string resolves normally. Flat 16px on purpose: this is a fixed inset from the * device edge (thumb reach / safe area), not a density-scaled gap inside a surface."},{name:"--form-label-width",value:"8rem",description:"Fixed aligned label column by default (gh#284) \u2014 `max-content` sized each field's label * column to its own label, so horizontal forms (especially columns={2} grids) had controls * starting at ragged x positions. 8rem mirrors --descriptions-label-width so edit forms and * show pages share the same optical grid; the Form/FormField `labelWidth` prop overrides."},{name:"--form-label-gap",value:"var(--space-4)",description:"Column gap between the label and its control in horizontal/inline layout."},{name:"--form-label-font-size",value:"var(--text-sm)",description:"Type in the label column. Inherits the body size by default, so nothing moves unless a * service opts in. It is a knob because the label column already is one: --form-label-width * lets a service align forms to its grid, and a service whose grid was drawn around a * smaller label had no way to say so \u2014 leaving it to hand-write font-size per label, which * is exactly what the width token exists to prevent."},{name:"--form-block-gap",value:"var(--space-4)",description:"Row rhythm between a Form's own top-level blocks (gh#295) \u2014 e.g. the field group's * CardContent and its CardFooter action row, or two FormFields that happen to be Form's OWN * direct children (no Card framing). Unchanged from the historical value."},{name:"--form-field-row-gap",value:"var(--space-3)",description:"Field-to-field row rhythm (gh#295) \u2014 mirrors --descriptions-row-gap so a read-only value * mixed in via `FormField.staticText` (gh#294) and a real Descriptions block share ONE canonical * rhythm. Applied via margin-based sibling spacing on FormField itself (not Form's flex `gap`, * which only reaches DIRECT children \u2014 dead in the common `Form > CardContent > FormField*` * composition every real Save-button form needs), so it holds at any DOM depth relative to Form."},{name:"--form-grid-row-gap",value:"var(--form-field-row-gap)",description:"Row rhythm between the ROWS of a `<Form columns={n}>` grid (gh#304). The grid path cannot use * the margin above \u2014 a per-item margin inside a grid double-counts the track gap AND leaves the * first item of row 1 unshifted while its row-mates drop, so row 1's columns end up misaligned. * The rhythm therefore rides the grid's own `row-gap`, and it DEFAULTS TO THE SAME * --form-field-row-gap the stacked path uses so `columns={1}` \u2014 and every `columns={n}` form once * it collapses to one column on a narrow container \u2014 is pixel-identical to a Form with no * `columns` at all. Retune this alone only to give multi-column forms a looser row rhythm than * stacked ones; retune --form-field-row-gap to move both together."},{name:"--form-grid-column-gap",value:"var(--space-4)",description:"Gutter between the COLUMNS of a `<Form columns={n}>` grid (gh#304). Was ResponsiveGrid's * generic 16px stack gap with no way to say otherwise; it is a form's inter-field gutter and a * service aligning forms to its design grid needs it as a knob (rule #45). The default keeps the * historical 16px, so nothing moves unless a theme opts in."},{name:"--legal-document-section-focus-ring-offset",value:"var(--space-1)",description:"Section anchor ring needs a gap so the mark does not touch the heading * (outline form \u2014 styles/focus-ring.css)."},{name:"--legal-document-measure-max-width",value:"46rem",description:"Section anchor ring needs a gap so the mark does not touch the heading * (outline form \u2014 styles/focus-ring.css)."},{name:"--legal-document-column-gap",value:"var(--space-10)",description:"Rail \u2194 document column gutter (two-column mode only)."},{name:"--legal-document-toc-width",value:"15rem",description:"Sticky table-of-contents rail width (two-column mode only)."},{name:"--legal-document-toc-inset-block-start",value:"var(--space-6)",description:"Distance the pinned rail keeps from the top of the scrollport."},{name:"--legal-document-toc-max-height",value:"calc(100dvh - var(--space-12))",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-gap",value:"var(--space-1)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-padding",value:"var(--space-1) var(--space-2)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-radius",value:"var(--radius)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-title-font-size",value:"var(--font-size-2xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-font-size",value:"var(--font-size-xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-marker-width",value:"var(--stroke-md)",description:"Leading marker on the active contents entry \u2014 a non-colour affordance so the * active state is never colour-only (WCAG 1.4.1)."},{name:"--legal-document-nav-gap",value:"var(--space-4)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-header-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-font-size",value:"var(--font-size-xs)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-gap",value:"var(--space-10)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-title-gap",value:"var(--space-3)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-body-line-height",value:"1.8",description:"Long-form leading \u2014 legal prose reads looser than UI text."},{name:"--legal-document-scroll-offset",value:"var(--space-6)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-footer-gap",value:"var(--space-4)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-toc-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-header-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-footer-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-meta-foreground",value:"initial",description:"\u2500\u2500 Role-mirror knobs \u2014 `initial` here, role default at the call site \u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-summary-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-background",value:"initial",description:"default = hsl(var(--foreground))"},{name:"--legal-document-toc-marker-color",value:"initial",description:"default = hsl(var(--accent))"},{name:"--list-row-padding-y",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-padding-x",value:"var(--space-4)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-gap",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-border",value:"initial",description:"Row divider \u2014 `initial` so the --border default re-resolves at the call site under a scoped * theme (a :root binding to a role var freezes at :root). Default = 1px solid hsl(var(--border))."},{name:"--list-row-body-min-width",value:"12rem",description:"Inline size the content column keeps before the trailing actions wrap onto their own line * (#45 \u2014 a service retunes the stack threshold to its grid). Clamped with min(\u2026, 100%) at the * call site, so a container narrower than the knob can never force page-root overflow (#224)."},{name:"--list-row-trailing-gap",value:"var(--space-2)",description:"Gap BETWEEN trailing actions \u2014 they also wrap among themselves at narrow widths (#224)."},{name:"--list-row-compact-padding-y",value:"initial",description:'\u2500\u2500 Compact inline-actions geometry (`density="compact"`, #246) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The canonical invitation / history row: a 36px leading avatar, a shrinkable title + * description body and one or two small trailing Buttons, all on ONE line inside the canonical * 358px card (326px content after the row\'s own inline inset) at a 390px viewport. These knobs * only LOWER the geometry \u2014 the #224 wrap contract (row + trailing cluster wrap, body clamped * with min(\u2026, 100%)) is untouched, so a cluster that genuinely cannot fit still wraps instead of * widening the page root. * Declared `initial` so the spacing defaults re-resolve at the CALL SITE \u2014 --space-2 is * density-scaled and re-declared inside a `.ui-density-*` subtree, so a :root binding would * freeze compact rows at the :root density (docs/TOKENS.md \u2014 the :root freeze rule). * Defaults = var(--space-2) block padding \xB7 var(--list-row-padding-x) inline padding \xB7 * var(--space-2) gap.'},{name:"--list-row-compact-padding-x",value:"initial",description:"Inline inset defaults to the default row axis so compact and default rows in the same Card keep * one optical axis; a service retunes it to its own grid (#45)."},{name:"--list-row-compact-gap",value:"initial",description:"Inline inset defaults to the default row axis so compact and default rows in the same Card keep * one optical axis; a service retunes it to its own grid (#45)."},{name:"--list-row-compact-body-min-width",value:"6rem",description:"Compact body threshold \u2014 small enough that avatar + body + two compact Buttons fit the * canonical column (36 + 8 + 96 + 8 + 146 = 294 \u2264 326), and still a readable measure."},{name:"--list-row-read-background",value:"initial",description:"Read (default) row surface \u2014 `initial`, documented default = transparent, i.e. the Card * surface shows through untouched (#44 \u2014 chrome defaults to the quietest state)."},{name:"--list-row-unread-background",value:"initial",description:"Unread emphasis surface (`unread` rows) \u2014 `initial` so the --muted default re-resolves at the * call site under a scoped theme. Documented default = hsl(var(--muted)), the same quiet * emphasis role the table header uses; it keeps the xs muted description line at WCAG AA * (4.63:1 light / 5.47:1 dark), which hsl(var(--accent)) would not (4.23:1 light)."},{name:"--list-row-indicator-color",value:"initial",description:"Unread indicator dot fill \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme. Documented default = hsl(var(--primary))."},{name:"--list-row-indicator-size",value:"var(--space-2)",description:"Diameter of the read/unread indicator dot; the gutter is reserved on read rows too so the * titles of a mixed list stay on one optical axis."},{name:"--logo-radius",value:"var(--radius)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-xs",value:"1.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-sm",value:"1.5rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-md",value:"1.75rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-lg",value:"2.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-xs",value:"var(--font-size-2xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-sm",value:"var(--font-size-xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-md",value:"var(--font-size-sm)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-lg",value:"var(--font-size-base)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-success-background",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-success-foreground",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-godx-color",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-identity-foreground",value:"48 9% 9%",description:'Identity-fill INK \u2014 the ink `mark="glyph"` sets its (caller-supplied) TEXT in when it sits on * the --brand identity fill. This is deliberately NOT --brand-foreground. * * --brand-foreground is the identity artwork\'s KNOCKOUT colour, not an ink: it tracks * --background in both themes (light 60 33% 99% = --background; dark 48 9% 9% = --background) * because `<Logo mark="godx">` punches its inner bar as an evenodd HOLE and the email mark has to * paint that hole as a solid fill to match. Negative space only has to clear WCAG 2.2 SC 1.4.11 * (3:1, non-text) \u2014 and at 3.67:1 on the emerald it does. * * The boxed glyph is a different thing: it renders real TEXT, so SC 1.4.3 applies at 4.5:1 (14px * bold is NOT "large text" \u2014 that needs 18.66px bold / 24px), and the knockout white measured * 3.67:1 \u2192 a genuine AA failure. This ink is 48 9% 9% (#191815), the same near-black spine the * DARK theme already inked the glyph with, so it is theme-INVARIANT: dark renders byte-identically * (6.89:1, unchanged) and light rises 3.67 \u2192 4.74:1. A service that re-themes --brand to a dark * fill overrides --logo-success-foreground (the public knob) to re-invert the ink. * * NOT a role-mirror knob: its default is a real value, not a role token, so there is no role to * freeze at :root and the `initial` + call-site rule (docs/TOKENS.md) does not apply to it.'},{name:"--logo-godx-size-xs",value:"1.5rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-sm",value:"1.75rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-md",value:"2rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-lg",value:"2.5rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size",value:"initial",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-wordmark-gap",value:"var(--space-2)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-xs",value:"var(--font-size-xs)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-sm",value:"var(--font-size-sm)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-md",value:"var(--font-size-base)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-lg",value:"var(--font-size-lg)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-weight",value:"700",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-letter-spacing",value:"-0.01em",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-family",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-color",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--pagination-gap",value:"var(--space-inline-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-item-gap",value:"var(--space-inline-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-size-width",value:"5.5rem",description:"Size-changer trigger MIN width (gh#286) - the trigger sizes to its content (w-max) so * localized labels (ja 100/\u30DA\u30FC\u30B8) never clip; this token only keeps short labels from * collapsing below the control rhythm."},{name:"--pagination-page-padding-x",value:"var(--space-1)",description:"Inline padding on a page button. The button keeps min-width: var(--control-height), so this * only takes effect once a number outgrows that box. Deliberately --space-1 rather than * --space-2: at the wider value a two-digit label (14.8px of text + 2 * 8px + 2px border) came * to 32.8px and pushed the common 1\u20132 digit buttons off square by 0.8px."},{name:"--pagination-total-font-size",value:"var(--font-size-sm)",description:"Inline padding on a page button. The button keeps min-width: var(--control-height), so this * only takes effect once a number outgrows that box. Deliberately --space-1 rather than * --space-2: at the wider value a two-digit label (14.8px of text + 2 * 8px + 2px border) came * to 32.8px and pushed the common 1\u20132 digit buttons off square by 0.8px."},{name:"--pagination-icon-size",value:"var(--control-icon-size)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-bar-gap",value:"var(--space-3)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-bar-padding-y",value:"var(--space-2)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-label-font-size",value:"var(--font-size-xs)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-picker-width-sm",value:"11rem",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-picker-width-md",value:"14rem",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-gap",value:"var(--space-2)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-item-gap",value:"var(--space-1)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-font-size",value:"var(--font-size-xs)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-separator-size",value:"var(--control-icon-size-sm)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-index-font-weight",value:"var(--font-weight-bold)",description:"Inline-step emphasis (gh#12). The step number's weight and tint are separate knobs so a * service can express progression the canonical way \u2014 an accent TINT at normal weight \u2014 instead * of the original bold. --steps-inline-index-color / --steps-inline-separator-color are * role-mirror knobs (docs/TOKENS.md): `initial` so their defaults (the inherited step colour and * the --muted-foreground role) resolve at the CALL SITE and a scoped [data-tenant]/.dark * override still reaches them. Defaults reproduce today's row byte for byte."},{name:"--steps-inline-index-color",value:"initial",description:"Inline-step emphasis (gh#12). The step number's weight and tint are separate knobs so a * service can express progression the canonical way \u2014 an accent TINT at normal weight \u2014 instead * of the original bold. --steps-inline-index-color / --steps-inline-separator-color are * role-mirror knobs (docs/TOKENS.md): `initial` so their defaults (the inherited step colour and * the --muted-foreground role) resolve at the CALL SITE and a scoped [data-tenant]/.dark * override still reaches them. Defaults reproduce today's row byte for byte."},{name:"--steps-inline-separator-color",value:"initial",description:"default = currentColor at the call site"},{name:"--app-setting-picker-compact-control-height",value:"var(--control-height-sm)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-padding-x",value:"var(--space-2)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-gap",value:"var(--space-1)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--breadcrumb-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--menubar-shortcut-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-max-inline-size",value:"100%",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-overflow",value:"auto",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-indicator-background",value:"initial",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-indicator-size",value:"2px",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-indicator-offset",value:"0px",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-root-gap",value:"0.5rem",description:"Gap between the tab strip and the panel below it."},{name:"--tabs-list-line-space-inset",value:"0px",description:"Inset of the `line` strip. The default/pill strip keeps its own padding box; the line strip is * a flat underlined rail, so its default is the quietest value \u2014 none (rule #44). Raise it to * give the rail breathing room above its hairline."},{name:"--tabs-trigger-line-radius",value:"0px",description:"`line` trigger box. Square corners because the selected state is the token-owned ::after bar, * never a pill; the hit box is wider/taller than the pill trigger so the underline spans a real * column of the rail."},{name:"--tabs-trigger-line-padding-x",value:"1rem",description:"`line` trigger box. Square corners because the selected state is the token-owned ::after bar, * never a pill; the hit box is wider/taller than the pill trigger so the underline spans a real * column of the rail."},{name:"--tabs-trigger-line-padding-y",value:"0.5rem",description:"`line` trigger box. Square corners because the selected state is the token-owned ::after bar, * never a pill; the hit box is wider/taller than the pill trigger so the underline spans a real * column of the rail."},{name:"--menubar-item-hover-background",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--menubar-item-hover-foreground",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--filter-bar-sticky-offset",value:"0px",description:"Sticky offset for the pinned filter strip (Toolbar `sticky`, #197). Default 0 (chrome, * rule #44); raise it so the strip parks below a fixed topbar."},{name:"--filter-bar-sticky-background",value:"initial",description:"Fill painted only when the filter strip is pinned. Role-mirror knob (rule #45): `initial` * so --background re-resolves at the call site under a scoped [data-tenant]/.dark theme."},{name:"--filter-bar-scroll-padding-y",value:"var(--space-1)",description:'Scrollbar gutter reserved under a `overflow="scroll"` filter strip (#216) so the inline * scrollbar never overlaps the controls. Rule #45 \u2014 a service theme retunes it to its grid * (0 on an overlay-scrollbar platform).'},{name:"--filter-bar-search-width",value:"20rem",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--filter-bar-filter-width",value:"var(--filter-picker-width-md)",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--filter-bar-chip-gap",value:"var(--space-2)",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--filter-bar-section-gap",value:"var(--space-2)",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--menu-item-height",value:"var(--band-height-md)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-radius",value:"var(--radius-sm)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-space-inline",value:"var(--space-2)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-space-gap",value:"var(--space-2)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-font-size",value:"var(--font-size-base)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-inset-space-inline-start",value:"var(--space-8)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-indicator-size",value:"0.875rem",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-indicator-inset-inline-start",value:"var(--space-2)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-icon-size",value:"var(--icon-size-md)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-content-space-inset",value:"var(--space-1)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-content-min-width",value:"10rem",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--dropdown-content-min-width",value:"8rem",description:"DropdownMenu is anchored to a small trigger, so it opens narrower than a context menu."},{name:"--menu-separator-space-block",value:"var(--space-1)",description:"DropdownMenu is anchored to a small trigger, so it opens narrower than a context menu."},{name:"--menu-separator-space-inline",value:"calc(var(--space-1) * -1)",description:"DropdownMenu is anchored to a small trigger, so it opens narrower than a context menu."},{name:"--navigation-menu-trigger-icon-size",value:"var(--icon-size-sm)",description:"NAVIGATION MENU \u2014 the disclosure chevron on a top-level trigger. It was a bare `0.9rem` in * navigation-layout.css: no token, so unreachable from an app (gh#326), and 14.4px, so off the * icon scale AND off the pixel grid. A stroked chevron drawn into a 14.4px box lands its path * on half pixels at 1x, which is a rendering defect rather than a rounding preference, so this * SNAPS to the nearest step \u2014 --icon-size-sm, 14px, \u22120.4px. The nearest step upward is 16px * (+1.6px) and would also fight the `opacity: 0.7` that makes this chevron deliberately quiet. * `sm` rather than `md` is the same call the other quiet chevrons in the system already made * (--month-picker-separator-icon-size, --steps-inline-separator-size). NOT * --scaling-multiplied: the literal it replaces did not track density."},{name:"--steps-dot-size",value:"0.625rem",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-dot-process-ring-width",value:"var(--stroke-xl)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-marker-border-width",value:"var(--stroke-md)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-marker-font-size",value:"var(--font-size-sm)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-marker-icon-size",value:"var(--icon-size-md)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-wait-icon-size",value:"var(--icon-size-xs)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-title-font-size",value:"var(--font-size-sm)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-title-font-size-compact",value:"var(--font-size-xs)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-subtitle-font-size",value:"var(--font-size-xs)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-vertical-space-gap",value:"var(--space-3)",description:"Vertical run: gap between marker and text, and the drop to the next step."},{name:"--steps-vertical-space-block-end",value:"var(--space-8)",description:"Vertical run: gap between marker and text, and the drop to the next step."},{name:"--steps-vertical-text-space-block-start",value:"var(--space-1)",description:"Vertical run: gap between marker and text, and the drop to the next step."},{name:"--steps-horizontal-text-space-block-start",value:"var(--space-2)",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--steps-horizontal-text-space-inline",value:"var(--space-2)",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--steps-connector-inset",value:"1.25rem",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--steps-connector-space-block-start",value:"var(--space-4)",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--app-setting-picker-locale-width",value:"10rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-timezone-width",value:"14rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-date-format-width",value:"11rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-time-format-width",value:"11rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-theme-width",value:"9rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-brand-width",value:"11rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-density-width",value:"10rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-font-size-width",value:"9rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-width-breakpoint",value:"40rem",description:"Below this the trigger hugs its content instead of taking the per-kind width, so a picker * dropped into a narrow topbar never stretches the bar (gh#165)."},{name:"--app-setting-picker-icon-size",value:"var(--icon-size-md)",description:"Below this the trigger hugs its content instead of taking the per-kind width, so a picker * dropped into a narrow topbar never stretches the bar (gh#165)."},{name:"--app-setting-picker-icon-rest-alpha",value:"0.7",description:"Below this the trigger hugs its content instead of taking the per-kind width, so a picker * dropped into a narrow topbar never stretches the bar (gh#165)."},{name:"--scroll-area-anchor-offset",value:"3rem",description:'How close to the bottom edge the reader still counts as "following the stream" for * `<ScrollArea anchor="bottom">`. Inside this band new content keeps the viewport pinned to the * newest item; one pixel beyond it the reader is reading history and anchoring NEVER moves them * again until they come back (WCAG 3.2.5 \u2014 no change of context on request of the machine). * * It is a rule #45 knob because the right distance is a function of the row height a service * renders: one line of a dense audit log is ~20px, a chat bubble with an avatar is ~64px, and * "one row from the bottom" is what the reader actually means. Expressed in rem on purpose \u2014 it * then tracks the user\'s font size, so the band is still one row at 200% zoom (WCAG 1.4.4) * instead of collapsing to a third of a row. The `anchorOffset` prop overrides it per instance; * px/rem/em are all accepted.'},{name:"--separator-rule-size",value:"var(--stroke-hairline)",description:"Rule weight, both orientations and both halves of a labelled rule."},{name:"--separator-rule-color",value:"initial",description:"Rule colour. Role-mirror knob \u2192 declared `initial` so the --border default re-resolves at the * CALL SITE under a scoped [data-tenant] / .dark theme (docs/TOKENS.md \u2014 the :root freeze rule). * Documented default = hsl(var(--border))."},{name:"--separator-label-gap",value:"initial",description:"Gap between the label and each rule half. `initial` so the default re-resolves at the call * site: --space-* is density-scaled and re-declared inside a `.ui-density-*` subtree, so a * :root binding would freeze the gap at the :root density. Documented default = var(--space-3)."},{name:"--separator-label-inset",value:"initial",description:'Length of the SHORT rule half when labelAlign is `start` / `end` \u2014 the label\'s inset from the * inline edge (the Slack/Mattermost stream convention). It is a grid track on the INLINE axis, * so it flips with the writing direction under dir="rtl" with no extra rule. `initial` for the * same density reason as the gap. Documented default = var(--space-6).'},{name:"--separator-label-font-size",value:"var(--font-size-xs)",description:"Label type ramp (#46) \u2014 steps of the modular scale, never a literal. `xs` is the quiet * stream/divider voice; --separator-label-line-height keeps ja/vi descenders and tone marks * intact (cf. #254); --separator-label-font-weight lets a service raise an unread watermark * without touching the day divider."},{name:"--separator-label-line-height",value:"var(--line-height-normal)",description:"Label type ramp (#46) \u2014 steps of the modular scale, never a literal. `xs` is the quiet * stream/divider voice; --separator-label-line-height keeps ja/vi descenders and tone marks * intact (cf. #254); --separator-label-font-weight lets a service raise an unread watermark * without touching the day divider."},{name:"--separator-label-font-weight",value:"var(--font-weight-medium)",description:"Label type ramp (#46) \u2014 steps of the modular scale, never a literal. `xs` is the quiet * stream/divider voice; --separator-label-line-height keeps ja/vi descenders and tone marks * intact (cf. #254); --separator-label-font-weight lets a service raise an unread watermark * without touching the day divider."},{name:"--separator-label-color",value:"initial",description:"Label colour. Role-mirror \u2192 `initial`; documented default = hsl(var(--muted-foreground))."},{name:"--separator-tone-muted-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-muted-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-primary-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-primary-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-success-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-success-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-warning-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-warning-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-destructive-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-destructive-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-info-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-info-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--sheet-responsive-breakpoint-width",value:"48rem",description:'Viewport width at and below which `SheetContent responsive="auto"` renders the mobile bottom * sheet instead of the desktop side panel. Read at runtime by useSheetResponsiveMode() (a CSS * @media cannot resolve a custom property), so a service moves the drawer breakpoint from ONE * knob for every overlay that opts into the responsive contract \u2014 Sheet and OrgSwitcher alike. * 48rem = 768px mirrors the library\'s canonical mobile line (useIsMobile). Accepts px/rem/em.'},{name:"--sheet-bottom-max-height",value:"85dvh",description:'Block size cap for the responsive BOTTOM presentation only (never for a plain * `side="bottom"` sheet, which stays content-sized). Keeps the sheet below the viewport so the * SheetBody scrolls and the page behind it stays visible/dismissable.'},{name:"--sidebar-section-label-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-product-tenant-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-background",value:"initial",description:'Sidebar nav count pill colours \u2014 the pill had a font-size knob and NO colour knob, so a rail * that needed to tell "unread" from "mentions you" had to nest a <Badge> inside `badge` and got * two pills, one inside the other. `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--secondary)) fill \xB7 hsl(var(--muted-foreground)) text.'},{name:"--sidebar-badge-foreground",value:"initial",description:'Sidebar nav count pill colours \u2014 the pill had a font-size knob and NO colour knob, so a rail * that needed to tell "unread" from "mentions you" had to nest a <Badge> inside `badge` and got * two pills, one inside the other. `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--secondary)) fill \xB7 hsl(var(--muted-foreground)) text.'},{name:"--sidebar-badge-destructive-background",value:"initial",description:'The EMPHASIS pair, read only by `SidebarItemProp.badgeTone="destructive"` (rule #44 \u2014 a * neutral row never resolves them). Defaults = hsl(var(--destructive)) fill \xB7 * hsl(var(--destructive-foreground)) text, the canonical AA-checked pair.'},{name:"--sidebar-badge-destructive-foreground",value:"initial",description:'The EMPHASIS pair, read only by `SidebarItemProp.badgeTone="destructive"` (rule #44 \u2014 a * neutral row never resolves them). Defaults = hsl(var(--destructive)) fill \xB7 * hsl(var(--destructive-foreground)) text, the canonical AA-checked pair.'},{name:"--sidebar-user-focus-ring-alpha",value:"0.45",description:"Softened focus ring on the tinted shell grounds: at full alpha the ring * reads as a SELECTED item rather than a focused one (styles/focus-ring.css)."},{name:"--topbar-chip-icon-size",value:"1.125rem",description:"Topbar glyphs. Three separate sizes, all previously baked as literals in shell-layout.css. * * --topbar-chip-icon-size is off the SCALE but on the pixel GRID, and those are different * findings. Read the rule it styles: `display: grid; place-items: center; font-weight: 700` \u2014 * it is a letter MEDALLION, not a glyph, so the half-pixel-stroke argument that made the * NavigationMenu chevron snap from 14.4px to 14px does not apply here. 18px is a whole pixel * sitting between --icon-size-md (16) and -lg (20); snapping it would be a visible 2px box * change bought for nothing. Declared, not silently tolerated. * scale-exempt: 18px letter medallion, a whole pixel between two icon steps, not a glyph"},{name:"--sidebar-product-caret-icon-size",value:"var(--icon-size-sm)",description:"Sidebar product-switcher caret \u2014 a real glyph, previously a bare 0.875rem."},{name:"--topbar-icon-size",value:"var(--icon-size-md)",description:"Sidebar product-switcher caret \u2014 a real glyph, previously a bare 0.875rem."},{name:"--topbar-caret-icon-size",value:"var(--icon-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--topbar-icon-focus-ring-alpha",value:"0.45",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-user-role-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--org-switcher-meta-foreground",value:"var(--muted-foreground)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-nav-sub-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-flyout-title-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--topbar-chip-icon-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--kbd-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-logo-mark-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-avatar-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-user-name-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--topbar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--app-shell-bar-height",value:"var(--band-height-2xl)",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-bar-inset",value:"var(--space-page-x)",description:"Inline inset and slot gap of the AppShell top bar. * * ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS (gh#330). The bar sits in the SAME grid track as * `.app-main`, directly above the page, so its first control and the page title are on one * vertical edge \u2014 or they are supposed to be. These two knobs used to name their own values * (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and * nothing reconciled them: measured in Chromium on /isolate/layout-app-shell the topbar's * content started at x=80 while the page header's started at x=88 (1512px), and because the two * sides also stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even * constant \u2014 8px at 1512, 12px between 720 and 900, 4px below 720. A consumer cannot correct a * drift that changes with the viewport. * * The page gutter WINS the axis, for three reasons: it is read by seven regions (page header, * toolbar, body, footer, PageContainer.Inset, the DataTable full-bleed compensation, Banner) * against this knob's one element; the bar is the region that must line up with the page, not * the other way round (the rail is a different track with its own inset); and moving the bar * moves exactly one element in one composition, while moving --space-page-x would move every * page in every consumer app. * * The NAMES stay \u2014 a theme that already sets `--app-shell-bar-inset` keeps working, and a bar * that genuinely wants to sit tighter than its page still has its own knob. Only the DEFAULTS * now point at the owner. `-compact` applies below the PAGE's 720px step (not the shell's 900px * restructure), so both sides tighten on the same line \u2014 see styles/shell-layout.css."},{name:"--app-shell-bar-inset-compact",value:"var(--space-page-compact-x)",description:"Inline inset and slot gap of the AppShell top bar. * * ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS (gh#330). The bar sits in the SAME grid track as * `.app-main`, directly above the page, so its first control and the page title are on one * vertical edge \u2014 or they are supposed to be. These two knobs used to name their own values * (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and * nothing reconciled them: measured in Chromium on /isolate/layout-app-shell the topbar's * content started at x=80 while the page header's started at x=88 (1512px), and because the two * sides also stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even * constant \u2014 8px at 1512, 12px between 720 and 900, 4px below 720. A consumer cannot correct a * drift that changes with the viewport. * * The page gutter WINS the axis, for three reasons: it is read by seven regions (page header, * toolbar, body, footer, PageContainer.Inset, the DataTable full-bleed compensation, Banner) * against this knob's one element; the bar is the region that must line up with the page, not * the other way round (the rail is a different track with its own inset); and moving the bar * moves exactly one element in one composition, while moving --space-page-x would move every * page in every consumer app. * * The NAMES stay \u2014 a theme that already sets `--app-shell-bar-inset` keeps working, and a bar * that genuinely wants to sit tighter than its page still has its own knob. Only the DEFAULTS * now point at the owner. `-compact` applies below the PAGE's 720px step (not the shell's 900px * restructure), so both sides tighten on the same line \u2014 see styles/shell-layout.css."},{name:"--app-shell-bar-gap",value:"var(--space-3)",description:"Inline inset and slot gap of the AppShell top bar. * * ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS (gh#330). The bar sits in the SAME grid track as * `.app-main`, directly above the page, so its first control and the page title are on one * vertical edge \u2014 or they are supposed to be. These two knobs used to name their own values * (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and * nothing reconciled them: measured in Chromium on /isolate/layout-app-shell the topbar's * content started at x=80 while the page header's started at x=88 (1512px), and because the two * sides also stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even * constant \u2014 8px at 1512, 12px between 720 and 900, 4px below 720. A consumer cannot correct a * drift that changes with the viewport. * * The page gutter WINS the axis, for three reasons: it is read by seven regions (page header, * toolbar, body, footer, PageContainer.Inset, the DataTable full-bleed compensation, Banner) * against this knob's one element; the bar is the region that must line up with the page, not * the other way round (the rail is a different track with its own inset); and moving the bar * moves exactly one element in one composition, while moving --space-page-x would move every * page in every consumer app. * * The NAMES stay \u2014 a theme that already sets `--app-shell-bar-inset` keeps working, and a bar * that genuinely wants to sit tighter than its page still has its own knob. Only the DEFAULTS * now point at the owner. `-compact` applies below the PAGE's 720px step (not the shell's 900px * restructure), so both sides tighten on the same line \u2014 see styles/shell-layout.css."},{name:"--app-shell-sidebar-width",value:"16rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-rail-width",value:"4rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-page-max-width",value:"80rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-main-background",value:"hsl(var(--muted) / 0.4)",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-width",value:"22.5rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-background",value:"initial",description:"Mobile-drawer scrim \u2014 `initial` so the shared --overlay-background default re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding freezes at :root). Default = * var(--app-shell-mobile-nav-alpha) of the global scrim, i.e. rgb(0 0 0 / 0.2) out of the box."},{name:"--app-shell-mobile-nav-alpha",value:"40%",description:"Share of the global --overlay-background this drawer's scrim uses. A slide-in drawer washes the * page more lightly than a modal dialog; keeping it a RATIO means a service retints the whole * system with one --overlay-background and every overlay keeps its calibrated depth."},{name:"--app-shell-mobile-nav-inset",value:"var(--space-1)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--app-shell-mobile-nav-icon-size",value:"var(--icon-size-lg)",description:'Hamburger glyph in the drawer trigger. Bigger ON PURPOSE than the trigger Button\'s own * `size="sm"` icon step (--control-icon-size-sm, 0.875rem) \u2014 it is the ONLY navigation * affordance on a phone, so it reads at the 1.25rem step. A flat rem, not a density-scaled * alias: it must not move with the density axis, or the one tap target that opens navigation * shrinks on a compact page. Consumed as a `size-[var(...)]` utility so it outranks * `.ui-button--sm svg` (see the comment at the call site in app-shell.tsx).'},{name:"--service-role-panel-list-gap",value:"0.25rem",description:"ServiceRolePanel (gh#319). `.ui-service-role-panel` shipped as a bare hook \u2014 the class was on * the root but no rule existed anywhere, so the role rail's rhythm lived entirely as Tailwind * literals inside the component and no theme could reach it (#45). The panel is a thin * composition over MasterDetail, which still owns ALL the two-track geometry; these knobs cover * only the role LIST inside the master rail. * * Flat rems, not `--space-*`: the literals they replace read Tailwind's own `--spacing` grid, * which this package never remaps and which does not follow the density axis. Aliasing them * would start scaling the rail inside a `.ui-density-*` subtree \u2014 a visual change, not a * refactor."},{name:"--service-role-panel-row-gap",value:"0.25rem",description:"between role rows (was `gap-1`)"},{name:"--service-role-panel-item-title-gap",value:"0.375rem",description:"select button \u2194 delete button (was `gap-1`)"},{name:"--service-role-panel-item-padding-block",value:"0.5rem",description:"The role row is a MULTI-LINE button (name over a description/member-count line), so it opts * out of the control height grid (`h-auto`) and sets its own block padding \u2014 that is what the * `py-2` literal was doing. It needs the raised `.ui-service-role-panel` ancestor specificity * (0,2,0) to beat `.ui-button--md { padding-block: var(--button-space-block) }` (0,1,0): * layout.css is imported BEFORE control.css and both are `@layer components`, so an equal * (0,1,0) rule here would silently lose."},{name:"--sidebar-brand-mark-size",value:"1.375rem",description:"The role row is a MULTI-LINE button (name over a description/member-count line), so it opts * out of the control height grid (`h-auto`) and sets its own block padding \u2014 that is what the * `py-2` literal was doing. It needs the raised `.ui-service-role-panel` ancestor specificity * (0,2,0) to beat `.ui-button--md { padding-block: var(--button-space-block) }` (0,1,0): * layout.css is imported BEFORE control.css and both are `@layer components`, so an equal * (0,1,0) rule here would silently lose."},{name:"--sidebar-nav-item-height",value:"var(--band-height-md)",description:"The role row is a MULTI-LINE button (name over a description/member-count line), so it opts * out of the control height grid (`h-auto`) and sets its own block padding \u2014 that is what the * `py-2` literal was doing. It needs the raised `.ui-service-role-panel` ancestor specificity * (0,2,0) to beat `.ui-button--md { padding-block: var(--button-space-block) }` (0,1,0): * layout.css is imported BEFORE control.css and both are `@layer components`, so an equal * (0,1,0) rule here would silently lose."},{name:"--sidebar-nav-item-font-size",value:"var(--font-size-xs)",description:"ON the type scale (gh#329). This was `0.8125rem` \u2014 13px, which is not a step: the golden scale * runs \u2026/ratio\xB2 \u2248 11.1 \xB7 /ratio \u2248 12.5 \xB7 base 14\u2026, so 13 sat BETWEEN two steps and every nav row * in the rail read off the system's type rhythm. It had no mechanical anchor either \u2014 unlike the * auth field label below, which is pinned to an artboard y-coordinate and keeps its literal with * a declared `scale-exempt` \u2014 only the checked-in hi-fi source's number. * * GEOMETRY MOVES, by 0.53px: 13px \u2192 --font-size-xs (\u224812.47px), the step the sub-row * (--sidebar-nav-sub-font-size) and the flyout title already take. Nothing reflows \u2014 the row is a * fixed 2rem with `align-items: center`, and 1.5 \xD7 12.47 = 18.7px sits inside it with the same * headroom 19.5px had. What is gained is that a service overriding --font-size-base now moves the * rail's labels with the rest of its type, instead of leaving one constant behind."},{name:"--sidebar-nav-item-line-height",value:"1.5",description:"Line box of the nav LABEL, not the row (gh#254): `.sb-label` clips, so its line box is its clip * box, and the row's `line-height: 1` shears descenders and Vietnamese tone marks. Keep >= 1.2."},{name:"--auth-account-summary-email-line-height",value:"1.5",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--topbar-start-truncate-line-height",value:"1.5",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-icon-size",value:"var(--icon-size-md)",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-gap",value:"0.625rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-padding-x",value:"0.625rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-gap",value:"2px",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-radius",value:"calc(var(--radius) - 1px)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-nav-scroll-padding",value:"var(--space-3) var(--space-2)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-section-gap",value:"var(--space-4)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-section-label-padding-x",value:"var(--space-2)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-section-label-padding-bottom",value:"var(--space-1)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--topbar-search-max-width",value:"none",description:"Default `none` (gh#296): the search trigger fills its whole `Topbar` center slot \u2014 flush to * whatever sits in `end` (a locale picker, notification bell, account menu) \u2014 rather than * floating as a fixed ~420px box with dead space on either side at normal desktop widths. A * consumer that wants a capped, centered search box (the previous look) sets this explicitly."},{name:"--topbar-height",value:"auto",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-inset",value:"0px",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-gap",value:"var(--space-2)",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-center-compact-display",value:"none",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-trigger-height",value:"var(--band-height-xl)",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-trigger-padding-x",value:"var(--space-2)",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-avatar-size",value:"1.75rem",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-menu-width",value:"16rem",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-sheet-max-height",value:"75dvh",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-state-min-height",value:"8rem",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--sidebar-item-active-color",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-tint",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-background",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--sidebar-item-active-foreground",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--auth-shell-control-height",value:"var(--control-height-comfortable)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-heading-size",value:"var(--font-size-2xl)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-card-max-width",value:"24rem",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-bar-padding",value:"var(--space-5) var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-main-padding",value:"var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-canonical-control-height",value:"var(--band-height-lg)",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-card-max-width",value:"22.5rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-heading-size",value:"var(--font-size-base)",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding",value:"1rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding-mobile",value:"0.9375rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-compact-card-inset",value:"var(--space-6)",description:"INLINE (start/end) inset of the compact auth card \u2014 the column the title, fields and actions * align to. Independent of the block axis below (gh#232)."},{name:"--auth-shell-card-padding-block-compact",value:"initial",description:"BLOCK (top/bottom) padding of the compact auth card \u2014 the knob that tunes canonical Login card * HEIGHT without narrowing the column. Bound to the card's --card-space-shell-y, so it really * reaches CardContent's block edges (before gh#232 it was wired only to --card-space-body-y and * the rendered body kept the inset on both block edges \u2014 the documented token did nothing). * Declared `initial` so the default mirrors the LIVE --auth-shell-compact-card-inset resolved at * the CALL SITE: the canonical card renders exactly as before (24px block = 24px inline), and a * service that re-tunes the inset inside a scoped `[data-tenant]` still drags the block default * with it (a :root binding would freeze at the :root inset \u2014 docs/TOKENS.md)."},{name:"--auth-shell-card-body-gap-compact",value:"var(--space-3)",description:"Header\u2194body gap inside the compact auth card (the breathing room under the title before the * first field). Its own knob since gh#232 \u2014 it used to ride on * --auth-shell-card-padding-block-compact, which conflated the section gap with the card's block * padding and made the block knob un-tunable. Default is the pre-gh#232 12px rhythm."},{name:"--auth-shell-card-gap-compact",value:"var(--space-3)",description:"IN-SLOT stack gap of the compact auth card (title \u2195 description inside the header)."},{name:"--auth-shell-main-align",value:"center",description:"Main-axis alignment of the auth column. Default `center` = today's vertically-centred card; * a flow whose intro + card + footnote stack is tall opts into `start` via this knob."},{name:"--auth-shell-card-stack-gap",value:"0px",description:"Gap between the direct sections of the auth column (intro \xB7 card \xB7 remember row). Default 0 * (quiet, rule #44) so an existing single-card consumer is untouched; presets opt in."},{name:"--auth-shell-login-card-max-width",value:"22.5rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-flow-offset-block",value:"14.4375rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-flow-offset-block-mobile",value:"13.8125rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-inline",value:"1rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-inline-mobile",value:"0.9375rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-block-end",value:"1rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-identity-slot-block-size",value:"7rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-card-stack-gap",value:"1.25rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-card-padding-block-compact",value:"var(--auth-shell-compact-card-inset)",description:"Canonical SCR-001 block inset is a FLAT 24px \u2014 the same measure as the inline inset. The card * draws its own 1px border and a border-box rect already contains it, so the former * `+ 2 x --control-border-width` double-counted the border and pushed the passkey CTA to y=390 * instead of the canonical y=388 (gh#263)."},{name:"--auth-shell-device-card-max-width",value:"23.75rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding",value:"1rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding-mobile",value:"0.9375rem 0.3125rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-otp-slot-inline-size",value:"1.71875rem",description:'CODE FIELD measure \u2014 the preset owns it (gh#12). A device grant is the one canonical flow whose * PRIMARY control is the code field, so leaving it on the generic square --otp-slot-size made the * preset own the page but not its own subject: two 4-slot `appearance="grouped"` boxes rendered * 146x38 (4 x the canonical 36px control tier + the 1px group border) against a 112x54 artboard. * These are the per-slot boxes that ADD UP to that artboard group: * inline 27.5px x 4 + 2 x 1px group border = 112px * block 52px + 2 x 1px group border = 54px * Literal artboard lengths, like every other preset measure here \u2014 they must not drift with * --scaling. A service that groups its code differently overrides --otp-slot-inline-size / * --otp-slot-block-size on its own field; the generic square knob is untouched everywhere else.'},{name:"--auth-shell-device-otp-slot-block-size",value:"3.25rem",description:'CODE FIELD measure \u2014 the preset owns it (gh#12). A device grant is the one canonical flow whose * PRIMARY control is the code field, so leaving it on the generic square --otp-slot-size made the * preset own the page but not its own subject: two 4-slot `appearance="grouped"` boxes rendered * 146x38 (4 x the canonical 36px control tier + the 1px group border) against a 112x54 artboard. * These are the per-slot boxes that ADD UP to that artboard group: * inline 27.5px x 4 + 2 x 1px group border = 112px * block 52px + 2 x 1px group border = 54px * Literal artboard lengths, like every other preset measure here \u2014 they must not drift with * --scaling. A service that groups its code differently overrides --otp-slot-inline-size / * --otp-slot-block-size on its own field; the generic square knob is untouched everywhere else.'},{name:"--auth-shell-context-card-max-width",value:"25rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding",value:"var(--space-6)",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding-mobile",value:"var(--space-6) 0",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-card-stack-gap",value:"1rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-recovery-card-max-width",value:"27rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding",value:"1rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding-mobile",value:"0.9375rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-registration-card-max-width",value:"22.5rem",description:"Registration preset (gh#256) \u2014 the canonical sign-up measure. Two things make it structurally * different from every preset above, and both are the reason it could not be expressed by * re-using `login`: * * 1. LONG-FORM SCROLLING. A registration card is the tallest surface in the hosted-identity set * (name \xB7 email \xB7 password \xB7 confirm \xB7 strength meter \xB7 consent \xB7 submit \xB7 provider row), and * a vertically CENTRED tall card clips its own top on a short viewport \u2014 the top overflows * above the scroll origin and becomes unreachable. So the column is start-aligned and the * block-start inset becomes ordinary page padding the user can scroll past. * 2. FOOTER CLEARANCE. The legal/consent footer must never be flush against the submit button * at the end of a long scroll, so the block-end inset is a knob of its own rather than * mirroring the block-start one. * * The 360px form measure and the 15px mobile inline gutter match the canonical Login flow exactly * (22.5rem card, x=15 at 390 \u21D2 width=360), so a user moving sign-in \u2192 sign-up on a phone never * sees the surface jump."},{name:"--auth-shell-registration-main-padding-block-start",value:"9.5rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-block-start-mobile",value:"8.875rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-inline",value:"1rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-inline-mobile",value:"0.9375rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-block-end",value:"3rem",description:"Footer clearance \u2014 the gap the legal footer keeps below the end of a long form."},{name:"--auth-shell-registration-main-padding-block-end-mobile",value:"2rem",description:"Footer clearance \u2014 the gap the legal footer keeps below the end of a long form."},{name:"--auth-shell-registration-card-stack-gap",value:"1.25rem",description:"Footer clearance \u2014 the gap the legal footer keeps below the end of a long form."},{name:"--auth-shell-registration-identity-slot-block-size",value:"7rem",description:"Fixed identity track, the same 112px `login` proved (gh#237). Without it the card anchor moves * with the title/requester line count, so the canonical y above would hold for exactly one copy * length and drift for every other. The slot absorbs absent / one-line / two-line identity * content instead."},{name:"--auth-shell-divider-gap",value:"0.625rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-label-font-size",value:"var(--font-size-2xs)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-label-line-height",value:"calc(19 / 11)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-rule-color",value:"var(--border)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-label-color",value:"var(--muted-foreground)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-identity-gap",value:"0.375rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-requester-gap",value:"0.375rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-requester-icon-size",value:"var(--icon-size-md)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-requester-glyph-size",value:"var(--icon-size-2xs)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-min-height",value:"var(--control-height-comfortable)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-gap",value:"var(--space-2)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-padding",value:"var(--space-1) var(--space-2)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-identity-min-width",value:"12rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-avatar-size",value:"var(--control-height-sm)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-avatar-glyph-size",value:"var(--control-icon-size-sm)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-email-font-size",value:"var(--font-size-sm)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-footer-content-gap",value:"0.375rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-footer-text-font-size",value:"var(--font-size-2xs)",description:"The same 11px \u2192 2xs snap as the divider label above (gh#329); this one carried no artboard * anchor at all, only the literal. GEOMETRY MOVES by 0.11px."},{name:"--auth-stack-gap",value:"var(--space-3)",description:"The same 11px \u2192 2xs snap as the divider label above (gh#329); this one carried no artboard * anchor at all, only the literal. GEOMETRY MOVES by 0.11px."},{name:"--auth-shell-field-label-gap",value:"var(--space-1)",description:"The same 11px \u2192 2xs snap as the divider label above (gh#329); this one carried no artboard * anchor at all, only the literal. GEOMETRY MOVES by 0.11px."},{name:"--auth-shell-field-label-font-size",value:"0.75rem",description:"scale-exempt: SCR-001 pins the email input at y=489; --font-size-xs (12.47px) drifts the 18px label box to 18.7px (gh#263)"},{name:"--auth-shell-field-label-line-height",value:"1.5",description:"scale-exempt: SCR-001 pins the email input at y=489; --font-size-xs (12.47px) drifts the 18px label box to 18.7px (gh#263)"},{name:"--centered-shell-bar-height",value:"var(--app-shell-bar-height)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-bar-padding-x",value:"var(--space-page-x)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-bar-padding-x-compact",value:"var(--space-page-compact-x)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-main-padding",value:"var(--space-6)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-main-padding-inline-compact",value:"var(--space-page-compact-x)",description:"Inline-only compact steps. They exist as their own knobs rather than as a redefinition of the * shorthands above, so `--centered-shell-main-padding` keeps meaning all four sides and a service * that already sets it is not broken by this fix."},{name:"--centered-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"Inline-only compact steps. They exist as their own knobs rather than as a redefinition of the * shorthands above, so `--centered-shell-main-padding` keeps meaning all four sides and a service * that already sets it is not broken by this fix."},{name:"--centered-shell-footer-padding-inline-compact",value:"var(--space-page-compact-x)",description:"Inline-only compact steps. They exist as their own knobs rather than as a redefinition of the * shorthands above, so `--centered-shell-main-padding` keeps meaning all four sides and a service * that already sets it is not broken by this fix."},{name:"--centered-shell-column-offset-block",value:"0",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-sm",value:"32rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-md",value:"46rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-lg",value:"64rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-landing-max-width",value:"67.5rem",description:'CenteredShell \xB7 public-landing preset (gh#252) \u2014 the token-owned geometry of a PUBLIC marketing * / product landing page: one content measure shared by header, main column and footer, the * section rhythm between page sections, the flat (chrome-quiet) card presentation and the hero * heading tier. Every value is a service-themeable knob (rule #45): a landing composition is a * COMPOSITION of real primitives (docs/COMPOSITION-VS-COMPONENT.md \u2014 Hero/Navbar/Footer FAIL the * Framework-Component Test), so the consumer must never own geometry or a media query. These * knobs are read ONLY under `.ui-centered-shell[data-preset="public-landing"]`, so the default * shell \u2014 which emits no `data-preset` at all \u2014 is byte-identical to before.'},{name:"--centered-shell-landing-inset-inline",value:"var(--space-6)",description:"1080px \u2014 header/main/footer share ONE measure"},{name:"--centered-shell-landing-inset-inline-compact",value:"var(--space-4)",description:"24px page gutter \u2265 40rem"},{name:"--centered-shell-landing-main-padding-block",value:"var(--space-10)",description:"16px page gutter < 40rem"},{name:"--centered-shell-landing-main-padding-block-compact",value:"var(--space-6)",description:"40px"},{name:"--centered-shell-landing-section-gap",value:"var(--space-10)",description:"24px"},{name:"--centered-shell-landing-section-gap-compact",value:"var(--space-6)",description:"rhythm BETWEEN page sections"},{name:"--centered-shell-landing-footer-padding-block",value:"var(--space-8)",description:"rhythm BETWEEN page sections"},{name:"--centered-shell-landing-card-shadow",value:"none",description:"Chrome is a token, default quiet (rule #44): a landing surface is FLAT \u2014 no card elevation \u2014 * so a service opts back IN with `--centered-shell-landing-card-shadow: var(--card-shadow)`."},{name:"--centered-shell-landing-heading-size",value:"var(--font-size-display)",description:"Hero heading tier. The preset re-points `--heading-h1` (same mechanism as * `.ui-auth-shell` \u2192 `--auth-shell-heading-size`), so a hero title is a real `Heading level={1}` * and never a consumer font-size."},{name:"--centered-shell-landing-heading-size-compact",value:"var(--font-size-3xl)",description:"Hero heading tier. The preset re-points `--heading-h1` (same mechanism as * `.ui-auth-shell` \u2192 `--auth-shell-heading-size`), so a hero title is a real `Heading level={1}` * and never a consumer font-size."},{name:"--centered-shell-landing-background",value:"initial",description:'Role-mirror knob \u2014 MUST stay `initial`, with the role default at the CALL SITE * (`var(--centered-shell-landing-background, var(--background))`), so a scoped * `[data-tenant]` / `.dark` override of `--background` still reaches it (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented default = hsl(var(--background)) \u2014 a public * page is the plain page canvas, not the app\'s `--muted` chrome.'},{name:"--app-shell-bar-height",value:"var(--band-height-3xl)",description:"Rule #24 companion (gh#291): on coarse pointers --control-height grows to 2.75rem * (44px tap floor), and the shell bar sits flush with the viewport top \u2014 a 3rem bar * leaves the control's 3px focus ring painting ABOVE y=0, off-screen, which no * overflow setting can recover. 3.5rem gives 6px of breathing per side."},{name:"--sidebar-nav-item-foreground",value:"initial",description:"Resting nav row + label (also the resting sub-row). Default = hsl(var(--muted-foreground))."},{name:"--sidebar-nav-item-hover-foreground",value:"initial",description:"Hovered nav row + label. Default = hsl(var(--foreground))."},{name:"--sidebar-nav-item-disabled-foreground",value:"initial",description:"`aria-disabled` nav row + label. Default = the resting row colour."},{name:"--sidebar-nav-icon-foreground",value:"initial",description:"Nav ICON (`.sb-icon`, incl. the collapsed rail and group triggers). Default = currentColor, * i.e. the row colour \u2014 the pre-gh#228 look. Set this alone to get canonical dark icons next to * muted labels."},{name:"--sidebar-nav-icon-hover-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-active-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-disabled-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--table-row-height-compact",value:"var(--band-height-sm)",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-default",value:"var(--band-height-md)",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-comfortable",value:"var(--band-height-xl)",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height",value:"calc(var(--table-row-height-default) * var(--scaling))",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-padding-y",value:"var(--space-2)",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-space-x",value:"var(--control-padding-x)",description:"Table component tokens: row height, cell padding."},{name:"--table-border-color",value:"initial",description:"Cell-grid rule colour for `<Table bordered>` (gh#274) \u2014 the outer frame and the vertical * rules between columns. Declared `initial` so the default re-resolves to the LIVE --border * role at the call site (a :root binding to hsl(var(--border)) freezes at the :root value and * a scoped [data-tenant] override of --border would never reach it \u2014 docs/TOKENS.md). * Default = hsl(var(--border))."},{name:"--table-pagination-padding-y",value:"initial",description:"Cell-grid rule colour for `<Table bordered>` (gh#274) \u2014 the outer frame and the vertical * rules between columns. Declared `initial` so the default re-resolves to the LIVE --border * role at the call site (a :root binding to hsl(var(--border)) freezes at the :root value and * a scoped [data-tenant] override of --border would never reach it \u2014 docs/TOKENS.md). * Default = hsl(var(--border))."},{name:"--table-pagination-padding-x",value:"initial",description:"Cell-grid rule colour for `<Table bordered>` (gh#274) \u2014 the outer frame and the vertical * rules between columns. Declared `initial` so the default re-resolves to the LIVE --border * role at the call site (a :root binding to hsl(var(--border)) freezes at the :root value and * a scoped [data-tenant] override of --border would never reach it \u2014 docs/TOKENS.md). * Default = hsl(var(--border))."},{name:"--table-surface-min-inline-size",value:"640px",description:'DataTable surface \u2014 narrow-viewport legibility FLOOR (gh#253). Below the `sm` viewport step a * multi-column admin grid whose columns are `white-space: nowrap` would otherwise be crushed, so * the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. * This shipped as a hard-coded `min-w-[640px]` utility on the surface element, which left a * service that wants a narrower (or no) floor with no route but forking the component (#45). * Default = the previous literal, in px so the floor releases at EXACTLY the same width as the * px-based `sm` media query that clears it (a rem value would drift under a non-16px root and * re-introduce the scroll between the two thresholds). Set `0` to opt out entirely \u2014 which is * what `preset="action-collection"` does, because there the priority measures own the width.'},{name:"--table-head-font-size",value:"var(--font-size-xs)",description:'DataTable surface \u2014 narrow-viewport legibility FLOOR (gh#253). Below the `sm` viewport step a * multi-column admin grid whose columns are `white-space: nowrap` would otherwise be crushed, so * the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. * This shipped as a hard-coded `min-w-[640px]` utility on the surface element, which left a * service that wants a narrower (or no) floor with no route but forking the component (#45). * Default = the previous literal, in px so the floor releases at EXACTLY the same width as the * px-based `sm` media query that clears it (a rem value would drift under a non-16px root and * re-introduce the scroll between the two thresholds). Set `0` to opt out entirely \u2014 which is * what `preset="action-collection"` does, because there the priority measures own the width.'},{name:"--table-header-background",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-header-foreground",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-pin-shadow",value:"-6px 0 6px -5px hsl(var(--foreground) / 0.12)",description:"Inline-end shadow that lifts a pinned (sticky) action column off the body it scrolls over."},{name:"--table-row-striped-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-hover-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-selected-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-action-collection-primary-width",value:"18%",description:'Table \xB7 action-collection preset (gh#253) \u2014 the canonical dense approval / action queue. * Column PRIORITY measures replace the desktop intrinsic widths (a nowrap free-text column is * what makes a five-column queue wider than its card and forces a horizontal scroll). Nothing * here is read unless `preset="action-collection"` is set, so every existing table is untouched. * Percentages so the ratio holds at any card width; the action affordance is an absolute measure * because it must never be squeezed below its touch target.'},{name:"--table-action-collection-secondary-width",value:"22%",description:"requester / subject"},{name:"--table-action-collection-meta-width",value:"12%",description:"target / resource"},{name:"--table-action-collection-actions-width",value:"3.5rem",description:"timestamps, ids \u2014 lowest priority"},{name:"--table-action-collection-primary-width-compact",value:"24%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-secondary-width-compact",value:"22%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-meta-width-compact",value:"20%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-actions-width-compact",value:"2.75rem",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-primary-width-floor",value:"6rem",description:'Wide-collection FLOORS \u2014 the compact measures from SEVEN columns up (gh#262). * * Percentage measures stop working once a queue carries enough columns for the shares to sum * past 100%: `table-layout: fixed` NORMALIZES them back into the table\'s specified width, so at * ~10 columns on a 390px frame a "24%" column really renders at a tenth of the frame, every * column drops below one CJK glyph, and JA headers shred into a vertical one-character-per-line * column (the failure mode WCAG 2.2 SC 1.4.10 Reflow exists to prevent). Cell `min-inline-size` * and `max(%, length)` widths are equally inert in the fixed algorithm, and the intrinsic * (max-content) size of a fixed-layout table is degenerate \u2014 all verified in Chromium \u2014 so a * LENGTH is the only floor an engine respects: length columns are never squeezed, and when * their sum outgrows the container the table\'s used width grows past its specified 100% * (CSS 2.1 \xA717.5.2.1) into the keyboard-reachable overflow-x wrapper, which scrolls. * ONE-dimensional scrolling of a data table is explicitly permitted by SC 1.4.10; * one-character-per-line is not readable at all. * * The stylesheet switches tiers on a COLUMN BUDGET, not on width: up to six columns the * percentage ratios above still resolve to readable measures at every acceptance artboard * (320 \xB7 375 \xB7 390), so the canonical five-column approval queue keeps its documented * scroll-free 390 frame byte-for-byte; from seven columns the shares are guaranteed to * over-sum and the floors + intentional scroll take over. That budget is the preset\'s * documented column-count ceiling (gh#262 expectation 3): past six columns the preset stops * compressing and starts scrolling. * * Units are rem, not ch: `ch` tracks the "0" glyph (~half a CJK em), so a ch-denominated floor * would silently halve for the JA copy these floors exist to protect. At the compact type tier * (font-size-xs = 0.75rem, space-2 inline padding each side) the 5rem meta floor keeps a ~4rem * text measure = 5 full-width glyphs per line \u2014 a JA header wraps to two readable lines, never * a character column.'},{name:"--table-action-collection-secondary-width-floor",value:"5.5rem",description:'Wide-collection FLOORS \u2014 the compact measures from SEVEN columns up (gh#262). * * Percentage measures stop working once a queue carries enough columns for the shares to sum * past 100%: `table-layout: fixed` NORMALIZES them back into the table\'s specified width, so at * ~10 columns on a 390px frame a "24%" column really renders at a tenth of the frame, every * column drops below one CJK glyph, and JA headers shred into a vertical one-character-per-line * column (the failure mode WCAG 2.2 SC 1.4.10 Reflow exists to prevent). Cell `min-inline-size` * and `max(%, length)` widths are equally inert in the fixed algorithm, and the intrinsic * (max-content) size of a fixed-layout table is degenerate \u2014 all verified in Chromium \u2014 so a * LENGTH is the only floor an engine respects: length columns are never squeezed, and when * their sum outgrows the container the table\'s used width grows past its specified 100% * (CSS 2.1 \xA717.5.2.1) into the keyboard-reachable overflow-x wrapper, which scrolls. * ONE-dimensional scrolling of a data table is explicitly permitted by SC 1.4.10; * one-character-per-line is not readable at all. * * The stylesheet switches tiers on a COLUMN BUDGET, not on width: up to six columns the * percentage ratios above still resolve to readable measures at every acceptance artboard * (320 \xB7 375 \xB7 390), so the canonical five-column approval queue keeps its documented * scroll-free 390 frame byte-for-byte; from seven columns the shares are guaranteed to * over-sum and the floors + intentional scroll take over. That budget is the preset\'s * documented column-count ceiling (gh#262 expectation 3): past six columns the preset stops * compressing and starts scrolling. * * Units are rem, not ch: `ch` tracks the "0" glyph (~half a CJK em), so a ch-denominated floor * would silently halve for the JA copy these floors exist to protect. At the compact type tier * (font-size-xs = 0.75rem, space-2 inline padding each side) the 5rem meta floor keeps a ~4rem * text measure = 5 full-width glyphs per line \u2014 a JA header wraps to two readable lines, never * a character column.'},{name:"--table-action-collection-meta-width-floor",value:"5rem",description:'Wide-collection FLOORS \u2014 the compact measures from SEVEN columns up (gh#262). * * Percentage measures stop working once a queue carries enough columns for the shares to sum * past 100%: `table-layout: fixed` NORMALIZES them back into the table\'s specified width, so at * ~10 columns on a 390px frame a "24%" column really renders at a tenth of the frame, every * column drops below one CJK glyph, and JA headers shred into a vertical one-character-per-line * column (the failure mode WCAG 2.2 SC 1.4.10 Reflow exists to prevent). Cell `min-inline-size` * and `max(%, length)` widths are equally inert in the fixed algorithm, and the intrinsic * (max-content) size of a fixed-layout table is degenerate \u2014 all verified in Chromium \u2014 so a * LENGTH is the only floor an engine respects: length columns are never squeezed, and when * their sum outgrows the container the table\'s used width grows past its specified 100% * (CSS 2.1 \xA717.5.2.1) into the keyboard-reachable overflow-x wrapper, which scrolls. * ONE-dimensional scrolling of a data table is explicitly permitted by SC 1.4.10; * one-character-per-line is not readable at all. * * The stylesheet switches tiers on a COLUMN BUDGET, not on width: up to six columns the * percentage ratios above still resolve to readable measures at every acceptance artboard * (320 \xB7 375 \xB7 390), so the canonical five-column approval queue keeps its documented * scroll-free 390 frame byte-for-byte; from seven columns the shares are guaranteed to * over-sum and the floors + intentional scroll take over. That budget is the preset\'s * documented column-count ceiling (gh#262 expectation 3): past six columns the preset stops * compressing and starts scrolling. * * Units are rem, not ch: `ch` tracks the "0" glyph (~half a CJK em), so a ch-denominated floor * would silently halve for the JA copy these floors exist to protect. At the compact type tier * (font-size-xs = 0.75rem, space-2 inline padding each side) the 5rem meta floor keeps a ~4rem * text measure = 5 full-width glyphs per line \u2014 a JA header wraps to two readable lines, never * a character column.'},{name:"--table-action-collection-flex-width-floor",value:"5rem",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-font-size-compact",value:"var(--font-size-xs)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-cell-space-x-compact",value:"var(--space-2)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-cell-padding-y-compact",value:"var(--space-2)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-row-height-compact",value:"var(--table-row-height-compact)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-min-inline-size-compact",value:"0",description:'Compact-tier LEGIBILITY FLOOR \u2014 the measure below which the preset stops fitting the table * to its container and lets the scroll region it already owns take over. * * The percentage budget above is sized for ONE column per priority tier plus one free-text * column. A queue that repeats a tier \u2014 two `secondary` columns, three `meta` columns, or an * unmarked column beside them \u2014 asks for more than 100%, and under `table-layout: fixed` the * surplus is taken out of the columns rather than out of the table: measured on a seven-column * Japanese admin queue at 390, every column landed at 44\u201359px and CJK labels wrapped at ONE to * TWO characters per line (dxs-platform/platform#680). That is a WCAG 2.2 SC 1.4.10 Reflow (AA) * failure, and it is the failure mode this floor removes. * * Scrolling is the correct escape, not a concession: SC 1.4.10 exempts "parts of the content * which require two-dimensional layout for usage or meaning", and its own note names data * tables as the example. A table that scrolls horizontally inside its card conforms; a table * whose cells are one character wide does not. * * Default `0` so nothing changes for any existing consumer: a queue that fits its priority * budget keeps fitting. A consumer whose queue carries more columns than the budget sets this * to the measure at which its narrowest column is still readable (roughly 5rem per column for * Japanese at the compact type tier) and the table scrolls instead of crushing.'},{name:"--table-stacked-collection-card-padding-y",value:"var(--space-stack-sm)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-card-padding-x",value:"var(--space-inline-md)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-card-gap",value:"var(--space-stack-sm)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-cell-padding-y",value:"var(--space-stack-xs)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-label-font-size",value:"var(--font-size-xs)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-select-column-width",value:"2.5rem",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-sort-icon-size",value:"var(--icon-size-xs)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-toolbar-icon-size",value:"var(--icon-size-md)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-pagination-icon-size",value:"var(--icon-size-md)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-pagination-font-size",value:"var(--font-size-sm)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-skeleton-check-size",value:"1rem",description:"Skeleton placeholders mimic the real content they stand in for: a checkbox square and a * text line at the row's cap height. Retune with --table-cell-padding-y to keep the loading * state the same height as the loaded one."},{name:"--table-skeleton-line-block-size",value:"1rem",description:"The skeleton bar's BLOCK SIZE. `--table-skeleton-line-height` was a length on the * line-height axis, whose scale is unitless ratios (gh#324) \u2014 a mis-named height, and the * only raw value on that axis, which is what kept the axis ungated. Renamed rather than * left, with the old name kept as a DEPRECATED ALIAS: it is a published token a consumer * theme may already override, and styles/table-layout.css still reads the old name, so an * override of either spelling keeps working. Prefer the new name; the alias may be removed * in a future major."},{name:"--table-skeleton-line-height",value:"var(--table-skeleton-line-block-size)",description:"The skeleton bar's BLOCK SIZE. `--table-skeleton-line-height` was a length on the * line-height axis, whose scale is unitless ratios (gh#324) \u2014 a mis-named height, and the * only raw value on that axis, which is what kept the axis ungated. Renamed rather than * left, with the old name kept as a DEPRECATED ALIAS: it is a published token a consumer * theme may already override, and styles/table-layout.css still reads the old name, so an * override of either spelling keeps working. Prefer the new name; the alias may be removed * in a future major."},{name:"--table-skeleton-radius",value:"var(--radius-sm)",description:"The skeleton bar's BLOCK SIZE. `--table-skeleton-line-height` was a length on the * line-height axis, whose scale is unitless ratios (gh#324) \u2014 a mis-named height, and the * only raw value on that axis, which is what kept the axis ungated. Renamed rather than * left, with the old name kept as a DEPRECATED ALIAS: it is a published token a consumer * theme may already override, and styles/table-layout.css still reads the old name, so an * override of either spelling keeps working. Prefer the new name; the alias may be removed * in a future major."},{name:"--table-font-size",value:"var(--font-size-sm)",description:"The table's own base type step (gh#319). Was a `text-sm` utility on the <table>; the theme * remaps `--text-sm` to `--font-size-sm`, so this default is byte-identical."},{name:"--table-line-height",value:"calc(1.25 / 0.875)",description:"MANDATORY companion to --table-font-size \u2014 the gh#260 trap, again. `text-sm` also set * line-height from Tailwind's `--text-sm--line-height`, and styles/base.css remaps `--text-sm` * but NOT that companion. Without this the table would silently inherit ambient page leading * the moment the font-size moved into CSS. Same unitless ratio Tailwind ships."},{name:"--table-row-border-width",value:"var(--stroke-hairline)",description:'Horizontal row rule (chrome, #44) \u2014 the divider between body rows AND under the header row. * Owned here rather than as TableRow\'s `border-b` utility so the "last row draws no rule" * rule in `@layer components` can actually zero it; a utility would outrank it by layer. * Colour is deliberately NOT set: it inherits the global `* { border-color: hsl(var(--border)) }` * so a scoped [data-tenant]/.dark override of --border still reaches every row.'},{name:"--table-flush-divider-width",value:"var(--table-row-border-width)",description:"Flush divider (chrome, #44/#45) \u2014 the ONE edge a full-bleed table keeps when it sits inside * `<CardContent flush>`: the line between a plain CardHeader and the first row. The other three * edges coincide with the card's own frame and are erased (gh#305); this one has nothing behind * it, so without it the header band floats (gh#306). Scoped by construction to the case that * needs it \u2014 a BANDED header, a CardBar or a headerless card already draw their own line there * and never reach this token \u2014 so a service tunes only the plain-header divider: `0` for a * borderless full-bleed table, heavier for a stronger band. Colour and style are NOT set here: * only the width is restored, so the surface's own border declaration still owns them and a * scoped [data-tenant]/.dark override of --border reaches it. * Default = the same hairline the table's other rules draw."},{name:"--toggle-count-min-width",value:"var(--space-4)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-space-inline",value:"var(--space-1)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-font-size",value:"var(--font-size-xs)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-radius",value:"var(--radius-pill)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-gap",value:"0",description:"Quiet default (#44) \u2014 the toggle's own flex gap already separates label from pill; this knob * ADDS to it for a service whose design grid wants the counter pushed further out."},{name:"--toggle-count-background",value:"initial",description:"\u2500\u2500 Counter pill \xB7 unpressed colours. Defaults = hsl(var(--muted)) fill (quiet: 1.09:1 against * the chip's own surface, so at rest the count reads as text, #44) and hsl(var(--foreground)) * text (14.25:1 on that fill). Opaque on purpose \u2014 a translucent fill would take its contrast * from whatever surface the chip currently has, and lose ~0.7 on hover."},{name:"--toggle-count-color",value:"initial",description:"\u2500\u2500 Counter pill \xB7 unpressed colours. Defaults = hsl(var(--muted)) fill (quiet: 1.09:1 against * the chip's own surface, so at rest the count reads as text, #44) and hsl(var(--foreground)) * text (14.25:1 on that fill). Opaque on purpose \u2014 a translucent fill would take its contrast * from whatever surface the chip currently has, and lose ~0.7 on hover."},{name:"--toggle-pressed-count-background",value:"initial",description:"\u2500\u2500 Counter pill \xB7 PRESSED colours. Defaults = hsl(var(--primary-foreground)) fill, * hsl(var(--primary)) text \u2014 the pressed chip's OWN pair, swapped. Not `--primary` on * `--primary`: the pressed chip is already filled with --primary, so a --primary pill would be * invisible on it. The swap also flips the pill from near-invisible to solid, which is one of * the encodings that keeps the pressed state off colour alone (WCAG 1.4.1)."},{name:"--toggle-pressed-count-color",value:"initial",description:"\u2500\u2500 Counter pill \xB7 PRESSED colours. Defaults = hsl(var(--primary-foreground)) fill, * hsl(var(--primary)) text \u2014 the pressed chip's OWN pair, swapped. Not `--primary` on * `--primary`: the pressed chip is already filled with --primary, so a --primary pill would be * invisible on it. The swap also flips the pill from near-invisible to solid, which is one of * the encodings that keeps the pressed state off colour alone (WCAG 1.4.1)."},{name:"--toggle-pressed-border-color",value:"initial",description:"\u2500\u2500 Pressed chrome. Default = hsl(var(--primary)), i.e. the pressed fill itself, so out of the * box this border is quiet (#44) and the pressed state is carried by the fill inversion + the * pill inversion + the forced-colors rule below. A service whose pressed fill is a PALE tint * (where those inversions get weak) opts into a visible outline by overriding this."},{name:"--toggle-count-forced-outline-width",value:"var(--stroke-hairline)",description:"Under forced-colors every fill is flattened to a system colour, so neither inversion encodes * anything any more. This outline is the state's structural fallback there \u2014 drawn on the * PRESSED pill only, and as `outline` (not `border`) so it costs no layout."},{name:"--upload-dropzone-space-inset",value:"var(--space-10)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-radius",value:"var(--radius-lg)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-border-width",value:"var(--stroke-md)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-icon-size",value:"var(--icon-size-3xl)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-hint-space-block-start",value:"var(--space-3)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-hint-font-size",value:"var(--font-size-sm)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-meta-space-block-start",value:"var(--space-1)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-meta-font-size",value:"var(--font-size-xs)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-tile-size",value:"6rem",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-radius",value:"var(--radius-md)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-space-gap",value:"var(--space-3)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-icon-size",value:"var(--icon-size-xl)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-label-space-block-start",value:"var(--space-1)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-label-font-size",value:"var(--font-size-xs)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-overlay-font-size",value:"var(--font-size-xs)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-overlay-background",value:"initial",description:"Busy scrim over a tile/frame \u2014 role-mirror knob, `initial` so --background re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme. Default = hsl(var(--background) / 0.7)."},{name:"--upload-overlay-alpha",value:"70%",description:"Busy scrim over a tile/frame \u2014 role-mirror knob, `initial` so --background re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme. Default = hsl(var(--background) / 0.7)."},{name:"--upload-remove-space-inset",value:"var(--space-1)",description:"REMOVE affordance \u2014 the \u2715 / trash chip pinned to a tile or avatar."},{name:"--upload-remove-offset",value:"var(--space-1)",description:"REMOVE affordance \u2014 the \u2715 / trash chip pinned to a tile or avatar."},{name:"--upload-remove-icon-size",value:"var(--icon-size-sm)",description:"REMOVE affordance \u2014 the \u2715 / trash chip pinned to a tile or avatar."},{name:"--upload-picture-max-width",value:"20rem",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-radius",value:"var(--radius-md)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-preview-max-height",value:"12rem",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-empty-height",value:"8rem",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-empty-label-space-block-start",value:"var(--space-2)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-empty-label-font-size",value:"var(--font-size-sm)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-overlay-font-size",value:"var(--font-size-sm)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-badge-radius",value:"var(--radius-md)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-offset",value:"var(--space-2)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-space-inline",value:"var(--space-2)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-space-block",value:"0.125rem",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-font-size",value:"var(--font-size-xs)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-avatar-size",value:"6rem",description:"AVATAR \u2014 the round single-image variant."},{name:"--upload-avatar-border-width",value:"var(--stroke-md)",description:"AVATAR \u2014 the round single-image variant."},{name:"--upload-avatar-change-space-block",value:"var(--space-1)",description:"AVATAR \u2014 the round single-image variant."},{name:"--upload-avatar-change-background",value:"rgb(0 0 0 / 0.7)",description:'The "change" strip sits ON the photo, so its scrim is intentionally a fixed dark wash rather * than a surface role \u2014 it must stay legible over an arbitrary user image in either theme.'},{name:"--upload-avatar-change-foreground",value:"#fff",description:'The "change" strip sits ON the photo, so its scrim is intentionally a fixed dark wash rather * than a surface role \u2014 it must stay legible over an arbitrary user image in either theme.'},{name:"--upload-avatar-pending-delete-alpha",value:"0.4",description:"Alpha of an image the user has marked for deletion but not yet committed."},{name:"--upload-draft-space-inline",value:"var(--space-3)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-space-block",value:"var(--space-2)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-space-gap",value:"var(--space-2)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-radius",value:"var(--radius-md)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-font-size",value:"var(--font-size-sm)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-icon-size",value:"var(--icon-size-sm)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-row-space-inline",value:"var(--space-3)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-space-block",value:"var(--space-2)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-space-gap",value:"var(--space-3)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-radius",value:"var(--radius-md)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-font-size",value:"var(--font-size-sm)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-icon-size",value:"var(--icon-size-md)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-crop-dialog-max-width",value:"24rem",description:"CROP DIALOG \u2014 the 1:1 avatar cropper (`UploadCropDialog`). The 66-literal upload pass above * never reached this file, so the whole cropper was still baked on the component: the dialog was * pinned to `max-w-sm`, the round preview to `size-48`, the zoom row to `px-2`. An avatar is one * of the most service-specific sizes there is (a 96px tile service wants a smaller crop stage * than a 200px one), and the crop stage must stay in proportion with --upload-avatar-size \u2014 * which is exactly the pair rule #45 exists to keep tunable together. * * The dialog width intentionally stays a knob of its own rather than reading * --dialog-width-default: the cropper is deliberately NARROWER than a normal dialog so the round * stage reads as the subject, not as a small element in a wide box."},{name:"--upload-crop-preview-size",value:"12rem",description:"CROP DIALOG \u2014 the 1:1 avatar cropper (`UploadCropDialog`). The 66-literal upload pass above * never reached this file, so the whole cropper was still baked on the component: the dialog was * pinned to `max-w-sm`, the round preview to `size-48`, the zoom row to `px-2`. An avatar is one * of the most service-specific sizes there is (a 96px tile service wants a smaller crop stage * than a 200px one), and the crop stage must stay in proportion with --upload-avatar-size \u2014 * which is exactly the pair rule #45 exists to keep tunable together. * * The dialog width intentionally stays a knob of its own rather than reading * --dialog-width-default: the cropper is deliberately NARROWER than a normal dialog so the round * stage reads as the subject, not as a small element in a wide box."},{name:"--upload-crop-preview-radius",value:"var(--radius-pill)",description:"CROP DIALOG \u2014 the 1:1 avatar cropper (`UploadCropDialog`). The 66-literal upload pass above * never reached this file, so the whole cropper was still baked on the component: the dialog was * pinned to `max-w-sm`, the round preview to `size-48`, the zoom row to `px-2`. An avatar is one * of the most service-specific sizes there is (a 96px tile service wants a smaller crop stage * than a 200px one), and the crop stage must stay in proportion with --upload-avatar-size \u2014 * which is exactly the pair rule #45 exists to keep tunable together. * * The dialog width intentionally stays a knob of its own rather than reading * --dialog-width-default: the cropper is deliberately NARROWER than a normal dialog so the round * stage reads as the subject, not as a small element in a wide box."},{name:"--upload-crop-zoom-space-inline",value:"var(--space-2)",description:"Zoom row inset \u2014 the slider is inset from the dialog edge so its thumb's focus ring is never * flush against the dialog padding."}];var h={issue:"https://github.com/godx-jp/godxjp-ui/issues/163",standard:"docs/FRAME-COVERAGE-STANDARD.md",ledger:"preview/frame-coverage.ledger.json",recordedAt:"2026-07-30",warning:"UNTESTED means no executed case demonstrates this dimension. It is NOT a pass. Do not infer support from a happy-path example, a similarly-named frame, or a rendered screenshot.",verdicts:{covered:"An executed case proves this dimension.",untested:"No executed case. NOT a pass \u2014 report it as UNTESTED to the user.","not-applicable":"The dimension cannot exist for this export; the ledger carries the written reason."},requiredViewports:[320,375,390,768,1024,1280,1440,1920],containerWidths:[240,320,480,640,960],totals:{exports:286,dimensionCells:4004,covered:66,untested:2197,notApplicable:1741,exportsWithoutFrame:0},dimensions:[{id:"variants",title:"Variants",axis:"visual"},{id:"tones",title:"Tones",axis:"visual"},{id:"sizes",title:"Sizes",axis:"visual"},{id:"shapes",title:"Shapes",axis:"visual"},{id:"density",title:"Spacing / density",axis:"visual"},{id:"ownership",title:"Controlled / uncontrolled / reset",axis:"ownership"},{id:"states",title:"Disabled / read-only / loading / empty / error / success",axis:"state"},{id:"async",title:"Async: loading / reject / retry / cancel / offline",axis:"async"},{id:"responsive",title:"Responsive viewport matrix",axis:"responsive"},{id:"rtl",title:"RTL / direction",axis:"international"},{id:"contentStress",title:"Long / localized content stress",axis:"contentStress"},{id:"keyboard",title:"Keyboard / focus order / focus return",axis:"accessibility"},{id:"accessibleName",title:"Accessible name / description / error association",axis:"accessibility"},{id:"reducedMotion",title:"Preferences: reduced motion / forced colors / 200% zoom / coarse touch",axis:"preferences"}],knownGaps:[{id:"button-contract-gaps",targets:["Button"],dimensions:["states","contentStress","rtl","keyboard","reducedMotion"],cases:["implicit form `type` inside a form","loading combined with asChild","count / overflowCount / showZero overflow","long Japanese and Vietnamese labels","RTL","coarse touch target"]},{id:"card-contract-gaps",targets:["Card"],dimensions:["variants","density","rtl","accessibleName"],cases:["`variant` \xD7 `accent` \xD7 `size` matrix",'`density="tight"` spacing mode',"nested heading levels","RTL accent rail"]},{id:"card-subcomponent-contract-gaps",targets:["CardBar","CardContent","CardFooter","CardHeader"],dimensions:["rtl","accessibleName","contentStress"],cases:["CardBar `extra` slot","CardContent `tight` / `flush` / `solo`","CardFooter `flush` / `separated`","CardHeader `banded`","RTL"]},{id:"empty-state-contract-gaps",targets:["EmptyState"],dimensions:["variants","contentStress","accessibleName"],cases:["`section` variant","`compact` variant","without icon / description / action","heading level (`titleAs` / `titleLevel`) and live-region semantics"]},{id:"progress-contract-gaps",targets:["Progress"],dimensions:["tones","contentStress"],cases:["destructive tone","`over={true}` (value beyond 100%)"]},{id:"carousel-contract-gaps",targets:["Carousel"],dimensions:["ownership","contentStress","keyboard","reducedMotion","rtl"],cases:["single slide","plugins / setApi","autoplay pause","arrow-key traversal alongside tabs","touch / swipe","RTL"]},{id:"data-table-contract-gaps",targets:["DataTable"],dimensions:["states","async","keyboard","accessibleName","density"],cases:["error / prerequisite / background refresh","server and manual modes (manualPagination / manualSorting / manualFiltering)","zero-page and single-page pagination","accessible table and action-column labels","invalid row ids (getRowId)","keyboard behaviour","virtualization"]},{id:"charts-contract-gaps",targets:["LineChart","BarChart","AreaChart","PieChart"],dimensions:["async","reducedMotion","keyboard","contentStress","responsive","rtl"],cases:["loading / error","reduced motion","keyboard datum navigation","invalid / null / NaN data","mobile axis collision","RTL"]},{id:"locale-stress-gaps",targets:["Timeline","TreeList","PasswordStrength"],dimensions:["contentStress","accessibleName"],cases:["long localized Japanese / Vietnamese content","hard-coded screen-reader strings that never reach `t()`"]}]},z=[{name:"Accordion",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionContent",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AccordionItem",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionTrigger",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AccountChip",group:"layout",frame:"docs/layout/account-chip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Activity",group:"general",frame:"docs/general/activity.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes","tones","variants"],notApplicable:["density","ownership","shapes","states"]},{name:"Alert",group:"feedback",frame:"docs/feedback/alert.tsx",covered:["tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"AlertActions",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertContent",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDescription",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]},{name:"AlertDialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"AlertDialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogRoot",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertMutationFeedback",group:"query",frame:"docs/query/mutation-feedback.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertQueryError",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertTitle",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AppSettingPicker",group:"navigation",frame:"docs/navigation/app-setting-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AppShell",group:"layout",frame:"docs/layout/app-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AreaChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"AspectRatio",group:"layout",frame:"docs/layout/aspect-ratio.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthAccountSummary",group:"layout",frame:"docs/layout/auth-account-summary.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AuthDivider",group:"layout",frame:"docs/layout/auth-divider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthFooter",group:"layout",frame:"docs/layout/auth-footer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthIdentity",group:"layout",frame:"docs/layout/auth-identity.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthShell",group:"layout",frame:"docs/layout/auth-shell.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"AuthStack",group:"layout",frame:"docs/layout/auth-stack.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Avatar",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:["shapes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states","tones","variants"]},{name:"AvatarFallback",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AvatarImage",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Badge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:["shapes","tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states"]},{name:"Banner",group:"feedback",frame:"docs/feedback/banner.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"BarChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"BranchScopePicker",group:"data-entry",frame:"docs/data-entry/branch-scope-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Breadcrumb",group:"layout",frame:"docs/navigation/breadcrumb.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Button",group:"general",frame:"docs/general/button/index.tsx",covered:["shapes","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","tones"]},{name:"ButtonRefetch",group:"query",frame:"docs/query/button-refetch.tsx",covered:["shapes","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","tones"]},{name:"Calendar",group:"data-entry",frame:"docs/data-entry/calendar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Card",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"CardAction",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardBar",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardContent",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardCover",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardDescription",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardFooter",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardHeader",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardTitle",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Carousel",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CarouselContent",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselDots",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselItem",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselNext",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CarouselPrevious",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Cascader",group:"data-entry",frame:"docs/data-entry/cascader.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CenteredShell",group:"layout",frame:"docs/layout/centered-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Checkbox",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CheckboxGroup",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CodeBlock",group:"data-display",frame:"docs/data-display/code-block.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Collapsible",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CollapsibleContent",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CollapsibleTrigger",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ColorPicker",group:"data-entry",frame:"docs/data-entry/color-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Command",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandEmpty",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandGroup",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandInput",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandItem",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CommandList",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandPalette",group:"data-entry",frame:"docs/data-entry/command-palette.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CompactBarTrend",group:"charts",frame:"docs/charts/compact-bar-trend.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ContextMenu",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuCheckboxItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"ContextMenuLabel",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuPortal",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioGroup",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuSeparator",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuShortcut",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSub",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ContextMenuTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"CredentialReveal",group:"data-display",frame:"docs/data-display/credential-reveal.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"DataState",group:"query",frame:"docs/query/data-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DataTable",group:"data-display",frame:"docs/data-display/data-table/index.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["shapes","sizes","tones","variants"]},{name:"DatePicker",group:"data-entry",frame:"docs/data-entry/date-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DateRangePicker",group:"data-entry",frame:"docs/data-entry/date-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Descriptions",group:"data-display",frame:"docs/data-display/descriptions.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Dialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogBody",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogClose",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"DialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenu",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuCheckboxItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"DropdownMenuLabel",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuPortal",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuSeparator",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuShortcut",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSub",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DropdownMenuTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"EmptyState",group:"data-display",frame:"docs/data-display/empty-state.tsx",covered:["tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"ErrorSurface",group:"layout",frame:"docs/layout/error-surface/index.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"Field",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FilterBar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"FilterBarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Flex",group:"layout",frame:"docs/layout/flex.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Form",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones","variants"]},{name:"FormErrors",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FormErrorsProvider",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FormField",group:"data-entry",frame:"docs/data-entry/form-field/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Heading",group:"general",frame:"docs/general/typography.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"HoverCard",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"HoverCardContent",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"HoverCardTrigger",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InfiniteQueryState",group:"query",frame:"docs/query/infinite-query-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Input",group:"data-entry",frame:"docs/data-entry/input.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"InputOTP",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"InputOTPGroup",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSeparator",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSlot",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Label",group:"data-entry",frame:"docs/data-entry/label.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LegalDocumentShell",group:"layout",frame:"docs/layout/legal-document-shell/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LineChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ListRow",group:"data-display",frame:"docs/data-display/list-row.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones","variants"]},{name:"Logo",group:"general",frame:"docs/general/logo.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"MasterDetail",group:"layout",frame:"docs/layout/master-detail.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Menubar",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarCheckboxItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"MenubarLabel",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarMenu",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioGroup",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarSeparator",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarShortcut",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSub",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarSubContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSubTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"MenubarTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MonthPicker",group:"data-entry",frame:"docs/data-entry/month-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MonthRangePicker",group:"data-entry",frame:"docs/data-entry/month-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"NavigationMenu",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuContent",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuIndicator",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuItem",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuLink",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuList",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuTrigger",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuViewport",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NumberInput",group:"data-entry",frame:"docs/data-entry/number-input.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"OrgSwitcher",group:"layout",frame:"docs/layout/org-switcher.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PageContainer",group:"layout",frame:"docs/layout/page-container.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"Pagination",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationContent",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationEllipsis",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationItem",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PaginationLink",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationNext",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationPrevious",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PasswordInput",group:"data-entry",frame:"docs/data-entry/password-input.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"PasswordStrength",group:"data-entry",frame:"docs/data-entry/password-strength.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PermissionMatrix",group:"data-display",frame:"docs/data-display/permission-matrix.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"PieChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Popover",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PopoverAnchor",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverContent",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverDescription",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverHeader",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTitle",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTrigger",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PrefetchLink",group:"query",frame:"docs/query/prefetch-link.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Progress",group:"data-display",frame:"docs/data-display/progress.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","variants"]},{name:"Prose",group:"data-display",frame:"docs/data-display/prose.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"QrCode",group:"data-display",frame:"docs/data-display/qr-code.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Radio",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroup",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroupRoot",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioItem",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Rating",group:"data-entry",frame:"docs/data-entry/rating.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ResizableHandle",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanel",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanelGroup",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResponsiveGrid",group:"layout",frame:"docs/layout/responsive-grid.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Reveal",group:"general",frame:"docs/general/reveal.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollArea",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollBar",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SearchInput",group:"data-entry",frame:"docs/data-entry/search-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Select",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectContent",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectGroup",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectItem",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectLabel",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollDownButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollUpButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectSeparator",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectTrigger",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"SelectValue",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"Separator",group:"layout",frame:"docs/layout/separator.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"ServiceCatalogCta",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCard",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCardSkeleton",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceRolePanel",group:"layout",frame:"docs/layout/service-role-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Sheet",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SheetBody",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetClose",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SheetContent",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetDescription",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetFooter",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetHeader",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"SheetOverlay",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetPortal",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTitle",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTrigger",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Sidebar",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarHeader",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarItem",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"SidebarSection",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Skeleton",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonDetail",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonRows",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonStat",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonTable",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Slider",group:"data-entry",frame:"docs/data-entry/slider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SplitPane",group:"layout",frame:"docs/layout/split-pane.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"StatCard",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"StatusBadge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:["shapes","tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states"]},{name:"Steps",group:"navigation",frame:"docs/navigation/steps.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Switch",group:"data-entry",frame:"docs/data-entry/switch.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"Table",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableBody",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableCell",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHead",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHeader",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableRow",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tabs",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsContent",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TabsList",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsTrigger",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TagInput",group:"data-entry",frame:"docs/data-entry/tag-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Text",group:"general",frame:"docs/general/typography.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"Textarea",group:"data-entry",frame:"docs/data-entry/textarea.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]},{name:"Timeline",group:"data-display",frame:"docs/data-display/timeline.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones"]},{name:"TimePicker",group:"data-entry",frame:"docs/data-entry/time-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Toaster",group:"feedback",frame:"docs/feedback/toast.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Toggle",group:"data-entry",frame:"docs/data-entry/toggle.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones"]},{name:"ToggleGroup",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones"]},{name:"ToggleGroupItem",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones"]},{name:"Toolbar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ToolbarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tooltip",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TooltipContent",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipProvider",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipTrigger",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Topbar",group:"layout",frame:"docs/layout/topbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Transfer",group:"data-entry",frame:"docs/data-entry/transfer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TreeList",group:"data-display",frame:"docs/data-display/tree-list.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TreeSelect",group:"data-entry",frame:"docs/data-entry/tree-select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TwoFactorSetup",group:"feedback",frame:"docs/feedback/two-factor-setup.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Upload",group:"data-entry",frame:"docs/data-entry/upload.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]}],le=new Map(z.map(t=>[t.name.toLowerCase(),t]));function L(t){return le.get(String(t??"").trim().toLowerCase())}var u=[{number:1,title:"Storybook is mandatory",body:"Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."},{number:2,title:"Tokens, not utilities",body:"Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden."},{number:3,title:"Radix for interactive primitives",body:"Anything with keyboard / ARIA / portal wraps the relevant Radix primitive."},{number:4,title:"shadcn-style ownership",body:"Primitives are thin wrappers; consumers can fork the source in place."},{number:5,title:"One i18next singleton",body:"`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`."},{number:6,title:"WCAG 2.1 AA baseline",body:"Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."},{number:7,title:"SemVer 2.0 + Keep a Changelog 1.1",body:"Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."},{number:8,title:"Inclusive naming",body:"`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."},{number:9,title:"No marketing speak",body:'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'},{number:10,title:"English is canonical for docs",body:"Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."},{number:11,title:"Submodule discipline",body:"Never push a pin to a SHA not on the submodule remote."},{number:12,title:"Branch + PR workflow",body:"`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."},{number:13,title:"TypeScript strict",body:"Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."},{number:14,title:"Every third-party library is shadcn / Radix-recommended",body:"Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."},{number:15,title:"No `@apply` re-encoding tokens",body:"Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."},{number:16,title:"CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",body:"A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."},{number:17,title:"`src/stories/` \u2194 `src/components/` parity",body:"Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."},{number:18,title:"`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",body:"Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."},{number:19,title:"No service-specific anything",body:'`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'},{number:20,title:'No "platform-only" exports',body:"Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."},{number:21,title:"Every component honours every theme axis",body:"`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values."},{number:22,title:"100% match to the design canon",body:'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'},{number:23,title:"Concept-first prop API",body:"One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."},{number:24,title:"Mobile-first",body:"Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."},{number:25,title:"Stories are docs; UI is the primitive",body:"When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."},{number:26,title:"Library isolation",body:"`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."},{number:27,title:"Per-group folder structure",body:"Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."},{number:28,title:"`src/` folder taxonomy",body:"Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."},{number:29,title:"Stories consume framework primitives only",body:"No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."},{number:30,title:"Story `render` returns JSX directly",body:"No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."},{number:31,title:"No nested wrapper / convenience primitives",body:"One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."},{number:32,title:"No redundant props",body:"Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."},{number:33,title:"Stories / source / docs name-synchronized",body:"No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only)."},{number:34,title:"Storybook source panel = real, copy-paste-ready code",body:'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`).'},{number:35,title:"Status chips never wrap",body:"A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."},{number:36,title:"Badge tone/icon are the colour escape hatch",body:"For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."},{number:37,title:"DataTable is full-width \u2014 never inside a narrow grid column",body:"A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"},{number:38,title:"FilterBar stays OUT of CardContent flush",body:"`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."},{number:39,title:"Long text columns get an explicit width",body:"For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."},{number:40,title:"Pages are mobile-first",body:'Author and verify every page at 320\u2013390px FIRST. Page sections are spaced by `PageContainer` itself (`--page-body-gap` between its direct children). Inside a section spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction="col"`, control rows = the default `direction="row"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout (the static audit rejects them). Styles are loaded whole: `@godxjp/ui/styles` or `styles/core`, never a hand-picked set of layers.'},{number:41,title:"Drawer & dialog footer layout",body:'Sheet/Dialog/AlertDialog footers are a pinned action bar (Ant Design Drawer footer): the footer sticks to the bottom, SheetFooter draws a full-bleed top border, and actions are RIGHT-aligned with the PRIMARY button rightmost (Cancel/secondary to its left). A destructive / clear / reset action goes far-LEFT \u2014 give that button `className="mr-auto"`. NEVER stack footer buttons full-width or center them.'},{number:42,title:"Props & Tokens Before Customization",body:"Before reaching for a Tailwind class, inline `style`, or extra CSS, you MUST first check whether the component already supports the need via a PROP, a design TOKEN, or a layout/typography PRIMITIVE. godx-ui is meant to be enough on its own (Ant-Design-style): `className` is for genuine one-offs only \u2014 never to redo what an API already does. Specifically: (1) NEVER hand-roll typography \u2014 no `text-[13px]`/`text-[11px]` arbitrary px (bypasses the golden type scale), no `font-medium`/`font-semibold`/`text-muted-foreground` on a raw `<span>`; use `<Text size tone weight tabular mono>` / `<Heading level>`. (2) NEVER hand-roll a trivial flex/grid wrapper; use `<Flex>` / `<ResponsiveGrid>` / `<PageContainer>`. (3) NEVER set a control's radius/height/colour with a utility when a `shape`/`size`/`tone`/token exists."},{number:43,title:"Every form control goes through FormField",body:"Consumers MUST wrap every labelled form control (Input, Select, DatePicker, DateRangePicker, NumberInput, Radio.Group, Checkbox groups, range pairs, ...) in FormField \u2014 it owns the label (aria-labelledby, never a dangling <label for>), auto-generates/injects the control id, and wires aria-describedby/aria-errormessage/aria-invalid. Bare controls are the rare exception (e.g. a toolbar quick-filter with its own aria-label) and must carry id/name + aria-label themselves. Never hand-roll a label+control stack with Text/Label."},{number:44,title:"Chrome is a token, default quiet",body:"Any decorative chrome a component draws \u2014 dividers, separator borders, and the padding that exists only to space that chrome \u2014 MUST read a token; never hard-code it in `src/styles/*.css` (a hard-coded `border-bottom: 1px solid hsl(var(--border))` leaves consumers no off-switch short of a variant fork). The DEFAULT is the quietest state (`none` / balanced rhythm); a service theme opts IN, e.g. `--page-header-divider: 1px solid hsl(var(--border))`."},{number:45,title:"Every service-tunable constant gets a knob",body:'When component CSS encodes a geometry choice that a service plausibly re-tunes to match its design handoff \u2014 form label column width, label\u2194control gap, header insets \u2014 it MUST be a documented component token (current value as the default). The theme sets it ONCE globally; props (`labelWidth`) override per instance; Form\u2192FormField priority stays intact. The test: "would a service theme.css want to change this to match its design grid?" If yes and the only route is forking CSS, that is a library gap \u2014 fix the library, don\'t patch the app.'},{number:46,title:"Typography is tokens, default is base",body:"The DEFAULT body size is `--font-size-base`; components render body/UI text at `base`, not at the `sm` alias. Smaller-by-design text (badge, section label, caption) is a component token defaulting to a small step (`--badge-font-size: var(--font-size-xs)`), so a service re-tunes that part without moving the global scale. The `sm`/`xs` tokens stay for the explicit `<Text size>` API. Every component token is surfaced in the MCP `get_component` output (check:mcp-token-sync)."},{number:47,title:"The layer contract \u2014 cascade layers, not specificity",body:'Every rule this package ships is inside a cascade layer, and layer order beats specificity outright. Two consequences. (1) INSIDE the package: `@layer components` is EARLIER than Tailwind\'s `utilities`, so a utility a component emits on its own element (`<table class="text-sm">`) silently outranks the component rule meant to own that property \u2014 no selector can win. A responsive re-point that must beat such a utility goes in `@layer godxjp-ui-responsive`, declared after Tailwind in `styles/base.css` and therefore LAST; it is reserved for `@container`/`@media` re-points, never static rules.'}];function T(t){return u.find(a=>a.number===t)}var b=[{name:"common-fixes",tagline:"Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",tags:["fixes","migration","bug","cardstat","statusbadge","datatable","sidebar","gotcha","review"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2114
|
+
</ServiceRolePanel>`,docPath:"layout/service-role-panel.tsx",storyPath:"layout/ServiceRolePanel.stories.tsx",rules:[24,40]}];function N(t){let a=t.trim().toLowerCase();return v.find(e=>e.name.toLowerCase()===a)}function F(t){return v.filter(a=>a.group===t)}var k=[{name:"ValueProp<T = string>",concept:"Abstract controlled value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs","SearchSelect"]},{name:"DefaultValueProp<T = string>",concept:"Abstract uncontrolled initial value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs"]},{name:"OnValueChangeProp<T = string>",concept:"Callback for abstract value changes. DOM events continue to use onChange.",values:["(value: T) => void"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Transfer","settings pickers"]},{name:"OpenProp / DefaultOpenProp / OnOpenChangeProp",concept:"Disclosure state.",values:["boolean","(open: boolean) => void"],usedBy:["Dialog","Sheet","Popover"]},{name:"SizeProp",concept:"Shared public size names.",values:["xs","sm","md","lg"],usedBy:["Button","Steps","Switch"],notes:"Component-specific subsets must be documented. Old alias small is sm."},{name:"ToneProp",concept:"Semantic status/color intent.",values:["default","success","warning","destructive","info","muted","neutral"],usedBy:["Badge","Alert"],notes:"Status values belong in tone, not variant."},{name:"GapProp",concept:"Shared layout gap scale.",values:["xs","sm","md","lg","xl"],usedBy:["Flex"],notes:'The single shared gap scale on Flex \u2014 the one layout primitive (default direction="row"; use direction="col" for vertical rhythm; these replaced the removed Stack/Inline).'},{name:"TitleProp",concept:"Primary heading text.",values:["React.ReactNode"],usedBy:["PageContainer","PageHeader","EmptyState","Dialog"]},{name:"DensityProp",concept:"Page/subtree density.",values:["compact","default","comfortable"],usedBy:["PageContainer"]},{name:"CenteredShellPresetProp",concept:"Named whole-page SHELL contract for CenteredShell \u2014 the page measure, section rhythm, chrome flatness and hero heading tier, all owned by component tokens.",values:["default","public-landing"],usedBy:["CenteredShell"],notes:'public-landing is the PUBLIC marketing/product landing geometry: one 67.5rem measure shared by the header bar, the centred column and the footer, the section rhythm, flat elevation-free cards and the hero h1 tier. "default" emits NO data-preset at all, so the existing shell box is untouched. Pair with `Flex hideBelow`/`hideFrom` for header region visibility \u2014 a landing must never own a page-local media query or a max-width wrapper.'},{name:"TablePresetProp",concept:"Named collection contract for a table \u2014 how a dense collection behaves when its container gets narrow.",values:["default","action-collection"],usedBy:["Table","DataTable"],notes:"action-collection swaps the desktop intrinsic column widths for table-layout: fixed + the token-owned column PRIORITY measures below `collapseBelow`, so a five-column approval queue keeps every column \u2014 including row actions \u2014 inside a 390px frame with no page-local CSS, no hidden column and no horizontal scroll. It changes the SIZING model only: no display change, no role rewriting, no card transformation, so table semantics and screen-reader navigation are identical at every width. ONE contract, two entry points: the `Table` primitive (mark `priority` on each TableHead/TableCell yourself) and the TanStack `DataTable` (mark `priority` on the ColumnDef; it stamps both cells for you, and additionally releases the surface's `--table-surface-min-inline-size` floor). Both read the same `--table-action-collection-*` tokens \u2014 there is no parallel DataTable family."},{name:"TableColumnPriorityProp",concept:"Relative importance of a table column, used by the action-collection preset to allocate the narrow-frame measure.",values:["primary","secondary","meta","actions"],usedBy:["Table","DataTable"],notes:"On the raw `Table`, set it on BOTH the TableHead and the TableCell of a column; on `DataTable` set it once on the `ColumnDef` and it is stamped onto the <th> and every <td>. `actions` reserves its measure first so the row-action affordance can never be pushed outside the viewport; a column with no priority takes the remaining space (the free-text column)."},{name:"AuthShellPresetProp",concept:"Named hosted-identity FLOW MEASURE for AuthShell \u2014 the card max-width plus the desktop and mobile page gutters, all owned by component tokens.",values:["default","device-authorization","context-selection"],usedBy:["AuthShell"],notes:'Orthogonal to AuthShell\'s `variant` (which owns control density + heading size) and applied after it, so `variant="canonical" preset="device-authorization"` composes. device-authorization = 380px card / 5px inline gutter at 390; context-selection = 25rem card, edge-to-edge on mobile. Selecting a preset REPLACES any consumer-side --auth-shell-card-max-width override \u2014 a page-local width is the anti-pattern these presets exist to remove.'},{name:"AvatarShapeProp",concept:"Avatar geometry \u2014 WHAT the identity mark represents: a person or an entity. Deliberately NOT the control ShapeProp (default|pill|sharp), whose `sharp` is --radius-sharp: 0 and cannot express the rounded rect an entity mark needs.",values:["circle","square"],usedBy:["Avatar"],notes:'circle (default, inert) = the round --radius-pill person avatar on the muted surface; square = the entity-header organization/service mark \u2014 a compact rounded square on the brand surface driven by --avatar-square-{radius,size,background,foreground}. Choose by meaning, never by look: a className="rounded-md bg-primary" override on the call site is the anti-pattern this prop removes.'}];function B(t){let a=t.trim().toLowerCase().replace(/prop(?:<.*>)?$/i,"");return k.find(e=>e.name.toLowerCase().replace(/prop(?:<.*>)?$/i,"")===a)}var C=[{name:"--wa-*",category:"primitive",tier:"primitive",role:"Neutral decorative Japanese accent primitives for charts/tags/decoration only."},{name:"--chart-1..6",category:"primitive",tier:"primitive",role:"Neutral decorative chart series palette. The @godxjp/ui/charts components (LineChart/BarChart/AreaChart/PieChart) read these by series index automatically \u2014 a service rethemes every chart at once by overriding --chart-1..6; per-series/per-slice overrides go through the component's series.color / colors props."},{name:"--space-0..12",category:"primitive",tier:"primitive",role:"Raw spacing scale."},{name:"--icon-size-{2xs,xs,sm,md,lg,xl,2xl,3xl,4xl}",category:"primitive",tier:"primitive",role:'Raw ICON scale \u2014 10 / 12 / 14 / 16 (md, the default) / 20 / 24 / 36 / 40 / 48 px. Icon was the last geometric axis with no scale: 28 component tokens declared raw numbers and between them used only these nine values, so this NAMES an existing vocabulary rather than inventing one. It is a FIXED LIST, not a `base * ratio^n` scale like --font-size-*, and deliberately so: 14/16 = 0.875 but 20/16 = 1.25, no single ratio generates the steps. Type can sit between pixels because hinting and antialiasing carry it; a 1px-stroke glyph cannot, so every step lands on a whole pixel at a 16px root. NOT --scaling-multiplied here: density is opted into by the tokens that want it (`calc(var(--icon-size-md) * var(--scaling))`, as --control-icon-size does), which keeps the scale itself the crisp whole-pixel list. TIER 1 vs TIER 2 \u2014 a value used in MORE THAN ONE place belongs on the scale and a service retunes it once; a value used in exactly ONE place (a 6px status dot) does NOT, and is set at the call site on that component\'s own --*-icon-size / --*-glyph-size token (`style={{ "--menu-icon-size": "6px" }}` or a `[data-\u2026]`-scoped theme rule). An inline custom property wins by inheritance proximity, so tier 2 never needs !important, a :root override, or a fork.'},{name:"--stroke-{hairline,sm,md,lg,xl,2xl}",category:"primitive",tier:"primitive",role:"Raw STROKE scale \u2014 the thickness of a painted LINE: 1 (hairline) / 1.5 / 2 (md, the emphasis stroke) / 3 / 4 / 6 px. The census called `width` the worst axis in the system at 91% raw; the census was right and the diagnosis was not, because `-width` is THREE concerns wearing one suffix \u2014 the thickness of a drawn line, the measure of a container (a dialog, a reading column, an auth card) and the content width of a field (how wide a picker must be to hold its longest label). Only the first is a vocabulary, and it repeats hard: six values across ~20 tokens spanning borders, focus rings, accent rails, selected-state markers and presence strokes. The other two are tier 2 by nature \u2014 their values appear in exactly one place \u2014 and are deliberately NOT on a scale. PX, NEVER REM: a stroke is a device line whose job is to read as one crisp rule at any type size, so it must not grow with the root font-size (`--slider-track-height: 0.375rem` is a different decision and stays put). NOT --scaling-multiplied: --scaling is a DENSITY knob, and a 1px divider at 0.92px would blur rather than tighten. --focus-ring-width is a member of this scale (`var(--stroke-md)`), so retuning line weight retunes the rings with it. --stroke-hairline sits BELOW --stroke-sm rather than being called xs because 1px is not really a step: it is the device hairline, the one thickness a service retunes the EXISTENCE of rather than the value (cardinal rule #44)."},{name:"--band-height-{xs,sm,md,lg,xl,2xl,3xl}",category:"primitive",tier:"primitive",role:"Raw BAND-HEIGHT scale \u2014 the vertical extent of a horizontal strip that content is centred in: 24 / 28 / 32 (md, the default control and row band) / 36 / 44 (the WCAG 2.2 AA touch floor, rule #24) / 48 / 56 px. `height` split the same way `width` did: a control, a table row, a menu item, a sidebar nav row and the app-shell top bar are ONE decision sharing seven values, each declared by two to five tokens, while a chart plot's height, a transfer pane's min-height and a popover's max-height are container measures chosen per surface with almost no repeated value and stay literal. The case for this is that `PageContainer`'s chrome band had no height token while `AppShell`'s bar did, so nothing could be aligned to it \u2014 the axis had no name. NOT A SUBSTITUTE FOR --control-height-*: the control tier is a RUNTIME LADDER (it multiplies by --scaling, steps \xB1--space-1 for sm/lg/xs, and `@media (pointer: coarse)` lifts the whole thing to the 44px tap floor); this scale is the STATIC vocabulary the ladder is ANCHORED on (`--control-height-default: var(--band-height-md)`), exactly as --font-size-base anchors the type scale. Point a band token at --control-height-* to \"reuse a step\" and you silently enrol it in density AND in the coarse-pointer growth \u2014 that is a geometry change, not a rename, and it is why --table-row-height-default reads var(--band-height-md) instead. NOT --scaling-multiplied here: whether a band breathes with density is a per-token decision (--control-height opts in, --app-shell-bar-height deliberately does not)."},{name:"--overlay-z-index",category:"semantic",tier:"semantic",role:"The ONE stacking layer every portaled overlay sits on \u2014 Tooltip, Popover, Select, DropdownMenu, ContextMenu/Menubar, Sheet (overlay AND panel), and since #319 also Dialog, AlertDialog and the CommandPalette, scrim and content alike. Each of those used to hard-code `z-50` independently, so an app that mounts the library under its own stacking context (a sticky masthead, a third-party chat widget) had to fight a separate literal per primitive \u2014 and whichever one it missed rendered underneath. Stacking is a SYSTEM decision, not a per-primitive one: raise every overlay together by overriding this once. Default 50, byte-identical to the literals it replaced. The DataTable sticky header deliberately stays BELOW it, so a menu opened from a sticky column header still wins. NOTE: overlays render into a portal on document.body, so a scoped `[data-tenant]` / `.dark` override must also sit on the portal container to reach them."},{name:"--font-size-*",category:"primitive",tier:"primitive",role:"Raw typography scale."},{name:"--font-sans-base",category:"semantic",tier:"semantic",role:`The default sans face. FONT-AGNOSTIC by default (pure system stack) \u2014 the library ships no hardcoded brand font. Override this for one face everywhere. --font-family-sans defaults to it; --font-family-display / --font-family-body default to --font-family-sans (override those for a dual display+body brand). Consumers supply the actual @font-face (next/font, @fontsource, self-host); the opt-in @godxjp/ui/styles/fonts OVERWRITES this (and --font-sans-vi) with the bundled DXS stack \u2014 "Noto Sans JP" primary (incl. the Vietnamese coverage) with "M PLUS 2" as the fallback. BUNDLE CHANGED IN v18.12.20 (product override, direct instruction): v16 bundled Noto Sans JP + Montserrat, v18.12.0\u201318.12.19 bundled M PLUS 2 + Noto Sans JP, v18.12.20+ bundles Noto Sans JP + M PLUS 2 \u2014 if a design spec names an earlier bundle's faces, set the tokens explicitly instead of relying on the bundle. IMPORT ORDER IS LOAD-BEARING: styles/fonts must be imported AFTER styles/base, because foundation.css declares this system-stack default on :root at the same specificity (0,1,0) and the later declaration wins (the all-in-one @godxjp/ui/styles entry already orders it correctly \u2014 issue #210).`},{name:"--font-sans-{ja,ko,vi,zh-hans,zh-hant}",category:"semantic",tier:"semantic",role:'Per-language font SLOT tokens. styles/base.css wires each [lang] to read its slot with --font-sans-base as fallback, so a consumer switches a locale\'s face by setting e.g. `--font-sans-ja: "Noto Sans JP", var(--font-sans-base)` \u2014 NO [lang] selectors to write. Empty by default. zh-hans matches lang zh|zh-Hans|zh-CN; zh-hant matches zh-Hant|zh-TW. Deciding which @font-face ships on which locale route is the consumer\'s font pipeline, not the library. The opt-in @godxjp/ui/styles/fonts only fills --font-sans-vi (with the bundled "Noto Sans JP", "M PLUS 2" stack); the other slots stay empty and fall back to --font-sans-base.'},{name:"--duration-{fast,base,slow}",category:"primitive",tier:"primitive",role:"Motion durations (150 / 250 / 500ms). Read these instead of a literal `0.5s` for enter/transition timing (rule #2). the reference design keeps motion short; honour `prefers-reduced-motion` at the call site."},{name:"--ease-{standard,emphasized,decelerate,accelerate}",category:"primitive",tier:"primitive",role:"Motion easing curves. `standard` for most transitions, `emphasized` for entrances/overlays (the vaul drawer curve), `decelerate` for settling in, `accelerate` for exits. Read instead of a literal `cubic-bezier(\u2026)`."},{name:"--reveal-distance",category:"primitive",tier:"primitive",role:"Distance (10px) a revealed element travels on enter (translateY/-X). Read instead of a literal `translateY(10px)` for staggered reveals."},{name:"--reveal-stagger-step",category:"primitive",tier:"primitive",role:"One step (60ms) of the Reveal stagger ladder \u2014 `<Reveal delay={n}>` waits n \xD7 this before entering, so a column of reveals cascades. Read instead of a literal `.04s`/`.09s` per-item delay."},{name:"--duration-loop",category:"primitive",tier:"primitive",role:"The AMBIENT/LOOPING interval (1400ms) \u2014 the cycle member of the motion tier. `--duration-{fast,base,slow}` time a TRANSITION (A to B, then stop); a loop needs an interval, so every ambient affordance in the product breathes at one rate. `Activity` reads it through `--activity-interval`."},{name:"--activity-interval",category:"component",tier:"component",role:"Per-component override of the ambient loop interval (defaults to `var(--duration-loop)`). A service tunes its typing/sync rhythm without moving every loop in the product (cardinal rule #45). Declared in the MOTION tier (tokens/foundation.css) beside `--reveal-stagger-step`, not in tokens/components/activity.css, because component-token names must carry a geometry/colour property word and there is none for a duration."},{name:"--activity-stagger-step",category:"component",tier:"component",role:"Offset (160ms) between the three `Activity variant='dots'` marks \u2014 the LOOP counterpart to `--reveal-stagger-step`, named to match. In RTL the dots reverse with the flex row, so the cascade follows the reading direction with no extra rule. Declared in the motion tier for the same reason as `--activity-interval`."},{name:"--activity-mark-size",category:"component",tier:"component",role:"Diameter (0.25em) of one `dots` mark. `em`-based so the whole mark tracks `--activity-font-size-*` (and therefore `--font-size-base` and the density axis) instead of pinning a px per size step."},{name:"--activity-mark-offset",category:"component",tier:"component",role:"Travel (0.25em) of one `dots` step \u2014 the LOOP counterpart to `--reveal-distance`, named to match. It is ALSO the height the mark row reserves, so a dot at the top of its arc is never clipped and the row never grows mid-cycle."},{name:"--activity-mark-rest-alpha",category:"component",tier:"component",role:"Opacity (0.35) of a mark at the trough of its cycle. Deliberately never 0: a mark that vanishes reads as a rendering bug, and it is what makes the `prefers-reduced-motion` frame (animation dropped) land on a legible resting state."},{name:"--activity-pulse-mark-size",category:"component",tier:"component",role:"Diameter (0.5em) of the single `variant='pulse'` mark \u2014 bigger than one `dots` dot because it carries the whole affordance alone."},{name:"--activity-gap",category:"component",tier:"component",role:"Mark-to-mark and mark-to-label gap (defaults to `var(--space-1)`)."},{name:"--activity-font-size-{xs,sm,md,lg}",category:"component",tier:"component",role:"Root size ladder for `Activity`. The mark is em-based, so this ONE value scales the whole indicator; the steps mirror the `Text` size the label renders at, so mark and label stay optically paired."},{name:"--activity-bar-{width,height,radius,segment-width,track-alpha}",category:"component",tier:"component",role:"The indeterminate `variant='bar'` sweep: track measure (4rem \u2014 an inline measure beside a label, not a 100% fill), thickness, corner, the moving segment's share of the track (40%), and the track's tint alpha (0.2)."},{name:"--activity-color",category:"component",tier:"component",role:"Mark ink for `Activity`. ROLE-MIRROR KNOB \u2014 declared `initial` at :root with the role default at the CALL SITE (`hsl(var(--activity-color, var(--muted-foreground)))`), so a scoped `[data-tenant]` / `.dark` override of `--muted-foreground` still reaches it (docs/TOKENS.md \xB7 'Role-mirror knobs MUST be initial'). It retints the default `tone='muted'`; an explicit non-muted `tone` sets its own semantic role."},{name:"--shadow-color",category:"primitive",tier:"primitive",role:"RGB channels (space-separated, e.g. `12 26 49`) that tint the WHOLE elevation ramp (--shadow-xs..2xl) at once. Default `0 0 0`. Single-brand :root only \u2014 a scoped override does not re-resolve the ramp (the steps compute at :root); for a scoped/multi-tenant card lift set --card-shadow to a literal value."},{name:"--shadow-glow",category:"primitive",tier:"primitive",role:"Opt-in brand GLOW halo layered on the primary CTA's resting shadow. Default invisible (`0 0 0 0 transparent`, valid inside a comma shadow list). A service sets the whole value, e.g. `--shadow-glow: 0 8px 20px hsl(var(--primary) / .32)` \u2014 works scoped under [data-tenant] because the service declares it inside the scope."},{name:"--focus-ring-color",category:"primitive",tier:"primitive",role:"Themeable hue of EVERY keyboard-focus ring (HSL components, default var(--ring)). A leaf token, so it re-resolves at the element that paints the ring \u2014 override it once, even scoped under [data-tenant], to retint all focus rings. Pair with --focus-ring-width."},{name:"--focus-ring-width",category:"primitive",tier:"primitive",role:"Thickness (2px) of the solid keyboard-focus ring. Leaf token \u2014 :focus-visible rules read it directly as `0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color))`, never via an intermediate composite (which would freeze at :root). Propagates scoped."},{name:"--gradient-{brand,hero,glow}",category:"primitive",tier:"primitive",role:"Opt-in decorative gradient fills, default `none`. --gradient-hero paints the PageContainer header (hero banner); --gradient-glow paints the AppShell .app-main (ambient brand wash); --gradient-brand is a spare. A service sets the full gradient, e.g. `--gradient-glow: radial-gradient(60% 80% at 50% 0%, hsl(var(--primary) / .25), transparent)`."},{name:"--primary",category:"semantic",tier:"semantic",role:"Action color role."},{name:"--brand / --brand-foreground",category:"semantic",tier:"semantic",role:'The IDENTITY role \u2014 the canonical GoDX emerald (light #009766 = oklch(0.595 0.137 162.94); dark lifts lightness only, #00b87c). It is what `<Logo mark="godx">` / `tone="success">` / the godx wordmark / the `@godxjp/ui/email` brand capsule paint, and it is distinct from THREE lookalikes: --primary (the ACTION colour \u2014 re-theme it freely, the mark never moves), --success (\u82E5\u7AF9 #68be8d, the STATUS green for badges/progress/"saved" text \u2014 the mark once borrowed this and shipped \u0394E76 \u2248 17.5 off canonical), and --text-brand (the blue brand-NUMERAL text slot behind the Tailwind `text-brand` utility). There is no `bg-brand` utility: `--color-brand` is already taken by --text-brand, so identity surfaces read `hsl(var(--brand))` directly in component CSS. Re-brand a service by overriding --brand once (scoped `[data-tenant]` works \u2014 every logo knob resolves it at the call site).'},{name:"--success",category:"semantic",tier:"semantic",role:'Success STATUS role (\u82E5\u7AF9 wakatake #68be8d) \u2014 badges, alerts, progress fills, timeline done dots, `tone="success"` text. NOT the product identity: the GoDX mark reads --brand.'},{name:"--warning",category:"semantic",tier:"semantic",role:"Warning status role."},{name:"--destructive",category:"semantic",tier:"semantic",role:"Destructive/error status role."},{name:"--info",category:"semantic",tier:"semantic",role:"Information status role."},{name:"--attention",category:"semantic",tier:"semantic",role:"Attention status role."},{name:"--border / --input",category:"semantic",tier:"semantic",role:"TWO roles, NOT synonyms \u2014 never set one to the other. --border is DECORATIVE chrome: table rules, card edges, section dividers, Separator. WCAG 2.2 SC 1.4.11 does not reach it (a divider is not \"required to identify a component or its state\") and this system's dense JP grid depends on it staying quiet \u2014 light `30 7% 83%`, dark `45 6% 22%`. --input is the CONTROL BOUNDARY: the 1px edge of Input/Textarea/Select/outline Button/TagInput/the composite date field/the topbar search (the Tailwind `border-input` utility reads it), plus the Switch's unchecked track fill. A field has no fill of its own and no shadow, so that edge is the whole visual claim that you may type there \u2014 it owes SC 1.4.11's 3:1 against EVERY surface a control sits on, not just the plain page: light `30 7% 53%` (3.47:1 on --background/--card/--popover, 3.18:1 on --muted/--secondary, 3.35:1 on a zebra row), dark `45 6% 47%` (4.22:1 on --background, 3.88:1 on --card/--popover, 3.17:1 on --muted). They shipped sharing one value and put every text field at 1.46:1. A service re-theming neutrals MUST move the two independently; `src/tokens/__tests__/input-boundary-contrast.test.ts` recomputes the ratios and fails below 3:1 or if the two roles are re-coupled. To quieten the Switch off-track without dragging --input back down, override --switch-unchecked-background instead."},{name:"--page-header-divider",category:"semantic",tier:"semantic",role:"PageContainer header bottom divider. Default none; a service theme opts in with `1px solid hsl(var(--border))`."},{name:"--page-footer-divider",category:"semantic",tier:"semantic",role:"PageContainer footer top divider \u2014 the third page-chrome band, on the same contract as `--page-header-divider` and `--page-toolbar-divider` (its rule used to be a hard literal in layout.css, so a page could not turn it off at all). Declared `initial` and resolved at the CALL SITE as `var(--page-footer-divider, 1px solid hsl(var(--border)))`, so a scoped [data-tenant]/.dark override still reaches it. It is the ONE chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where the line separating the actions from the page content is the behaviour every existing page already draws \u2014 the old literal IS the fallback, so unset is byte-identical. Set `--page-footer-divider: none` when the footer content already carries its own frame (a chat composer is a bordered Card, and the shell's full-width rule otherwise stacks a second line right above it)."},{name:"--overlay-background",category:"semantic",tier:"semantic",role:"Modal scrim \u2014 the single backdrop colour shared by every overlay (Dialog, AlertDialog, Sheet, Drawer, AppShell mobile nav). Default `rgb(0 0 0 / 0.5)`. A service tints it once, e.g. a navy `rgb(12 26 49 / .55)`. HOW IT REACHES AN OVERLAY: each surface declares its own `--*-overlay-background` knob `initial` and resolves a SHARE of this colour at the call site, `color-mix(in srgb, var(--overlay-background) var(--*-overlay-alpha), transparent)`, so the calibrated per-surface depth survives (Sheet/drawer 40% = 0.2, Dialog 60% = 0.3) while ONE override here retints them all. NOTE: portaled overlays render outside a [data-tenant] subtree, so for multi-tenant scoping put the tenant attribute on the portal container too."},{name:"--page-toolbar-{background,pad-block,divider}",category:"semantic",tier:"semantic",role:"PageContainer `toolbar` band chrome \u2014 the FIXED strip between the page header and the (scrolling) body. All three knobs are quiet by default (rule #44). `--page-toolbar-background` (default `transparent`) is the band's GROUND: set it ONCE in a service theme \u2014 `--page-toolbar-background: hsl(var(--card));` \u2014 to lift the band off the page ground the way a chat channel's workflow rail or a collection's filter strip usually wants. It is bound at `:root` rather than `initial` because its default is a plain CSS keyword, not another role token, so there is nothing for a scoped override to re-resolve; it is the `background` shorthand, so a gradient works too. NEVER write `className=\"bg-card\"` on the strip at the call site \u2014 that is hand-laid page chrome, it is invisible to per-tenant theming, and it paints the strip rather than the band (so it misses the page gutters and the `measure` cap). `--page-toolbar-pad-block` (default 0) is the band's own block inset. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (the container's band gap is cancelled on the band itself \u2014 chrome is attached, not a third section floating between two voids), so there is no outside space to tune. It stays 0 even now that the band can be painted: a TRANSPARENT band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height is taken from the scroll viewport the slot exists to protect. The corollary is that a theme which PAINTS the band must also set the inset here (`--page-toolbar-pad-block: var(--space-2)`) \u2014 the two go together, and neither belongs in a `py-*` utility at the call site. `--page-toolbar-divider` is declared `initial` and resolved at the CALL SITE as `var(--page-toolbar-divider, var(--page-header-divider))` \u2014 so ONE `--page-header-divider` opt-in rules the whole page chrome consistently, a scoped [data-tenant]/.dark override of it still reaches the band (a `:root` binding would freeze it), and `--page-toolbar-divider: none` silences just the band. `variant=\"ghost\"` keeps the divider quiet regardless (it does NOT clear the background \u2014 ghost is about rules and pads, not grounds). The band's inline gutters and its `measure` cap are shared with the header and body \u2014 they are not separate knobs, which is what keeps the three bands aligned."},{name:"--page-title-font-size-chrome",category:"semantic",tier:"semantic",role:'PageContainer title step under `headerScale="chrome"` \u2014 a page whose top row IS chrome (a chat channel name, a mail subject, an IDE tab) rather than a document title. Default `var(--heading-h3)` = `--font-size-base` (14px), the body step, so the row reads as a label ON the surface instead of the page\'s headline; a consumer chat header measured 61px with a 20px h1 against a design that wanted ~40px at the `sm` step. Deliberately a THIRD knob beside `--page-title-font-size` / `--page-title-font-size-compact`: those two are one document title at two viewport sizes (a responsive step), this is a different KIND of page and holds at every width \u2014 the compact rule must not pull a chrome header back up to h2. Read ONLY when the prop is passed, so a document page never touches it (rule #44). A service retunes the chrome step here once; never override `--page-title-font-size` at a call site to fake it.'},{name:"--page-subtitle-font-size-chrome",category:"semantic",tier:"semantic",role:'PageContainer SUBTITLE step under `headerScale="chrome"` \u2014 the caption under a chrome top row (a channel purpose line, a mail preview), not a document\'s standfirst. Default `var(--font-size-2xs)` (ratio\u207B\xB2, ~11px), two steps below the document default `--font-size-base`. It is a fourth knob beside `--page-subtitle-font-size` / `-compact` for the same reason the title step is: those two are ONE document subtitle at two viewport sizes, this is a different KIND of page and holds at every width \u2014 and it has to out-rank the 720px compact rule, which is already a compound selector, or a chrome caption would step back UP on a phone. Two things it fixes at once. Hierarchy: at the document step the chrome subtitle rendered at `--font-size-base`, the IDENTICAL step the chrome TITLE takes, so a channel name and its purpose line read as one undifferentiated block. Height: the step drives the LINE BOX at the inherited `--line-height-body` (14px x 1.7 = 23.8px vs 11px x 1.7 = 18.9px), so a band whose whole point is to leave room for the transcript stops spending ~5px of it on a caption. Type only \u2014 line-height, colour and weight stay with the base rule, so a wrapped JA/VI purpose line keeps its rhythm. Read ONLY when the prop is passed (rule #44); a service retunes the caption step here once, never with a `text-*` utility at the call site.'},{name:"--page-pad-block-start-chrome",category:"semantic",tier:"semantic",role:"PageContainer TOP inset under `headerScale=\"chrome\"` \u2014 the second half of the same fact the title step answers: chrome sits ON the frame's edge. Default `0px`, so a chrome page opens flush; a document page keeps `--space-page-active-y` (24px, 16px below the 720px step) because a title needs air above it. Measured on a consumer chat screen: an otherwise correctly-sized channel head started at y=24 against a design that starts it at y=0, and those 24px came straight off the transcript viewport (617px in the design, 587px in the app). It is a knob rather than a literal (rule #44) so a service whose grid wants its chrome inset writes `--page-pad-block-start-chrome: var(--space-2)` once instead of forking the page shell, and it is read ONLY when the prop is passed, so no document page resolves it. Block-start only \u2014 the page's bottom edge belongs to `stickyFooter`, which zeroes it for its own reason."},{name:"--page-header-min-block-size-chrome",category:"semantic",tier:"semantic",role:"PageContainer BAND HEIGHT under `headerScale=\"chrome\"` \u2014 the third half of the same fact the title step and the flush top edge answer. A document header is content-height, correctly: a title is as tall as the title is. Chrome is furniture, and furniture needs a band that things centre INTO. Without one, the band's vertical centre is a function of its own copy \u2014 measured in Chromium on the catalogue page, 42.02px with an `extra` control and 40.38px without \u2014 so nothing in the page can be aligned to it and no service can aim at a number. Default `auto` (rule #44, the quiet state): no floor, content-height, byte-identical to every page shipped before the token existed, chrome pages included. The BAND-HEIGHT AXIS HAS ONE OWNER and it is `--app-shell-bar-height` (`--centered-shell-bar-height` already reads it), so the one line a service writes to put its page chrome on the shell bar's band is `--page-header-min-block-size-chrome: var(--app-shell-bar-height)` \u2014 the band becomes 48px and the title column and `extra` both centre at y=24 regardless of copy length. It is NOT that value by default because a chrome `PageContainer` is not necessarily inside an `AppShell` (the canonical chat composition renders one inside a bordered region, a SplitPane pane, a Card), and a band that is not trying to line up with a bar has no business inheriting that bar's height. A MIN, never a height: a taller `extra` (an `lg` control, a wrapped JA/VI channel name) still fits instead of overflowing. The header's own `--page-header-pad-bottom` lives INSIDE the band (border-box); under the canonical chrome composition (`variant=\"ghost\"`) that pad is 0 and the centring is exact."},{name:"--page-header-pad-bottom",category:"semantic",tier:"semantic",role:"PageContainer header bottom inset. Defaults to page top padding minus the section gap so the title band is vertically balanced."},{name:"--master-detail-rail-{compact,standard}",category:"semantic",tier:"semantic",role:"MasterDetail fixed rail track \u2014 18.75rem (300px) / 20rem (320px). The `railWidth` prop picks the preset; a service theme retunes the presets themselves. Consumers never author a grid track."},{name:"--master-detail-gap",category:"semantic",tier:"semantic",role:"Gap between the MasterDetail master and detail regions (default --space-stack-md), side by side and stacked."},{name:"--master-detail-collapse-below",category:"semantic",tier:"semantic",role:"MasterDetail stacking threshold (default 40rem / the `sm` step), measured against the composition's OWN inline size, not the viewport: wider keeps the split, narrower stacks master above detail. A real knob \u2014 the layout resolves it inside a flex-basis calc(), because a media/container-query CONDITION cannot read a var(). The `collapseBelow` prop overrides it per instance (rule #45)."},{name:"--master-detail-master-viewport-{compact,standard}",category:"semantic",tier:"semantic",role:'Bounded MasterDetail master viewport \u2014 20rem (320px) / 28rem (448px), selected by the semantic `masterViewport` prop. `masterViewport="auto"` (the default) applies NO bound, so existing compositions are unchanged; the presets cap the collection\'s block size and scroll it INSIDE the region, which is what stops a long real collection from pushing the detail below the fold once the layout stacks. Retune the presets in a service theme exactly like --master-detail-rail-*; never author a pixel max-height in consumer CSS.'},{name:"--master-detail-master-viewport-inset",category:"semantic",tier:"semantic",role:"Focus-ring room inside a bounded MasterDetail master region (default --space-1 / 4px), and its scroll-padding. An overflow container clips BOTH axes, so without this inset the focus ring of a row at the region's edge would be cut off; it also keeps a row scrolled into view off the clip edge. Only applies when `masterViewport` is compact/standard."},{name:"--page-header-extra-measure",category:"semantic",tier:"semantic",role:'Inline measure of the PageContainer header `extra` region while `headerLayout="responsive-inline"` and the page is below the 640px step (default 11rem / 176px). The default `headerLayout="stack"` never reads it \u2014 `extra` keeps wrapping onto its own full-width line under the subtitle. At >=640px both arrangements are identical, so this token only governs the compact range.'},{name:"--page-measure-{narrow,medium}",category:"semantic",tier:"semantic",role:'Bounded PageContainer MEASURE presets \u2014 42rem (672px) / 48rem (768px), selected by the semantic `measure` prop. They are OUTER measures: the package-owned page gutters (--space-page-active-x, 24px each side) sit INSIDE the cap, so the VISIBLE surface is 624px / 720px, and at 390px nothing binds (358px surface at the 16px compact gutter). Unlike variant="narrow" \u2014 which caps only .ui-page-body \u2014 the measure caps the page HEADER and the BODY together, so a header `extra` action ends flush with the body surface. `measure="default"` (the default) reads NEITHER token and matches no rule, so existing pages are unchanged. Retune the presets in a service theme; never author a page-local max-width.'},{name:"--badge-space-*",category:"component",tier:"component",role:"Badge spacing."},{name:"--button-count-{min-width,space-inline,font-size} / --button-count-{background,color} / --button-count-{default,destructive,secondary}-{background,color}",category:"component",tier:"component",role:"Button's COUNTER PILL (`count` / `overflowCount` / `showZero`). The COLOUR knobs exist because the pill used to tint itself translucently over whatever surface the button happened to have (`bg-primary-foreground/15` on filled variants, `bg-foreground/8` on the outline family), which means its contrast was a FUNCTION of that surface rather than a property of the pill \u2014 and five variant x theme x hover combinations measured below the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: default 3.88 light, destructive 4.29 dark, and the outline family 4.32 light at rest, 3.64 light on hover, 3.68 dark on hover. The two worst were HOVER states, which is exactly why a screenshot sweep never found them: `--accent` only exists under the cursor. The fix is the treatment validated on Toggle: OPAQUE role fills, so the ratio no longer depends on the variant or on hover. Each filled variant wears its OWN label pair SWAPPED (default --primary on --primary-foreground = 5.04 light / 7.07 dark; destructive 6.10 / 5.53; secondary 14.25 / 12.40), which makes the pill exactly as legible as the label beside it and impossible to make worse without making the button itself unreadable first \u2014 a promise a fixed colour could not make across re-themes. The outline family shares one pill, --foreground on --muted (14.25 / 12.40), whose --muted fill sits 1.09:1 against the button's own ground, so at rest the counter still reads as quiet text rather than a badge: it is the legibility of the DIGITS that rose, not the loudness of the pill. All eight colour knobs are ROLE-MIRROR knobs -- `initial` at :root with the role default at the CALL SITE -- so a scoped `.dark`/`[data-tenant]` override of the role reaches the pill. The geometry knobs are byte-identical to Toggle's, asserted by a token-parity test."},{name:"--toggle-count-{min-width,space-inline,font-size,radius,gap} / --toggle-count-{background,color} / --toggle-pressed-count-{background,color} / --toggle-pressed-border-color / --toggle-count-forced-outline-width",category:"component",tier:"component",role:"Toggle's COUNTER PILL (`count` / `overflowCount` / `showZero` / `countLabel`) \u2014 the counted, PRESSED chip Button could not express. SHARE vs SPLIT is the whole point of this group. GEOMETRY is shared: the five geometry knobs carry Button's exact counter values off the same primitive scale (var(--space-4) / var(--space-1) / var(--font-size-xs) / var(--radius-pill) / 0), so a Toggle count set beside a Button count measures identically in a browser (12.47px, 16px min-inline, 4px inline padding, pill radius, tabular-nums) and a token-parity test fails if either side moves alone. `--toggle-count-gap` is ADDITIVE to the toggle's own flex gap and defaults to the quiet 0 (rule #44). COLOUR is deliberately NOT shared: Button's pill sits on a static variant surface and tints itself translucently, but Toggle's pill sits on a surface that INVERTS when pressed, where that treatment cannot clear WCAG 1.4.3 AA at the xs step (measured 3.82:1, and 4.39:1 even at the lowest usable alpha \u2014 the ceiling is the chip label's own 5.04:1 and any tint only lowers it). So Toggle uses OPAQUE role fills that make its contrast independent of the chip's variant and hover surface, and inverts them with the state: unpressed --foreground on --muted (14.18:1 light / 12.44:1 dark), pressed --primary on --primary-foreground \u2014 the chip label's own pair, swapped (5.04:1 light / 7.05:1 dark), so the pill is exactly as legible as the label beside it. All five colour knobs are ROLE-MIRROR knobs: `initial` at :root with the role default at the CALL SITE, so a scoped `.dark`/`[data-tenant]` override of the role actually reaches the pill. `--toggle-pressed-border-color` defaults to the pressed fill itself (quiet, #44) and is the knob a service overrides when its pressed fill is a PALE tint that weakens the inversions. `--toggle-count-forced-outline-width` (1px) draws the pressed pill's outline under `forced-colors: active`, where every fill is flattened and neither inversion encodes anything \u2014 together with the chip border following the system Highlight, that keeps the pressed state off colour alone (WCAG 1.4.1)."},{name:"--logo-godx-{size,size-xs|sm|md|lg,color} / --logo-success-{background,foreground} / --logo-identity-foreground",category:"component",tier:"component",role:'The GoDX IDENTITY mark. COLOUR: its default is the --brand IDENTITY role (canonical emerald) \u2014 deliberately independent of --primary, so a re-themed action colour never recolours the brand, AND of --success, the \u82E5\u7AF9 STATUS green it must never default to (`tone="success"` names the semantic SLOT, not the status role). Role-mirror knobs: declared `initial` at :root with the role default at the CALL SITE (`hsl(var(--logo-godx-color, var(--brand)))`), so a scoped `.dark`/`[data-tenant]` override of --brand actually reaches the mark. BOX: `--logo-godx-size-{xs,sm,md,lg}` (1.5 / 1.75 / 2 / 2.5rem) is the identity mark\'s own size ramp \u2014 the `size` prop drives it exactly like --logo-size-* drives the boxed glyph, one tier above its glyph sibling because the artwork is a capsule inside a square viewBox. `--logo-godx-size` is the PIN, not the base: `initial` by default (so the tiers apply) and, set once by a service theme, it freezes the mark at that box on EVERY tier. INK: `--logo-identity-foreground` (48 9% 9%, #191815) is the colour the boxed `mark="glyph"` sets its TEXT in on the identity fill \u2014 deliberately NOT --brand-foreground. --brand-foreground is the artwork KNOCKOUT colour (it tracks --background in both themes, because `mark="godx"` punches its inner bar as an evenodd HOLE and the email mark paints that hole solid to match) and as negative space only owes WCAG 2.2 SC 1.4.11\'s 3:1. The boxed glyph renders real TEXT, so SC 1.4.3 applies at 4.5:1 \u2014 14px bold is NOT "large text" (that needs 18.66px bold / 24px) \u2014 and knockout white on the emerald measured only 3.67:1, a straight AA failure. The ink is theme-INVARIANT (it is the same near-black spine the dark theme already resolved to), so dark is unchanged at 6.89:1 and light rises to 4.74:1. It is NOT a role-mirror knob \u2014 its default is a real value, not a role, so there is no role to freeze and it is declared at :root, not `initial`. Re-theming --brand to a DARK fill? Override the public knob --logo-success-foreground to re-invert the ink. Applies to `<Logo mark="godx">` / `tone="success"`.'},{name:"--logo-wordmark-{gap,font-size-xs|sm|md|lg,font-weight,letter-spacing,font-family,color}",category:"component",tier:"component",role:'Wordmark/lockup knobs for `<Logo wordmark="\u2026" />` \u2014 the mark\u2194wordmark gap, the per-size-tier wordmark type ramp, and its face/weight/tracking/colour. The package ships NO wordmark artwork: `--logo-wordmark-font-family` defaults (at the call site) to --font-family-display, so the wordmark is typeset in the design-system face; pass an inline <svg> as `wordmark` when a real logotype exists. `--logo-wordmark-color` is a role-mirror knob (`initial`) whose call-site default is hsl(var(--foreground)), or the --brand IDENTITY role on the godx / tone="success" lockup \u2014 never --primary and never the --success status green. Setting it once in a service theme re-colours every lockup with no page CSS.'},{name:"--card-*",category:"component",tier:"component",role:"Card surface, border, spacing, and typography."},{name:"--control-*",category:"component",tier:"component",role:"Shared form control heights, padding, icons, and focus chrome."},{name:"--control-affix-{inset-inline-end,action-size,action-radius,icon-size,rest-alpha} / --control-trigger-space-inline-end",category:"component",tier:"component",role:"OVERLAY trailing affix \u2014 the clear \u2715 / chevron a select-family trigger parks ON TOP of its inline end. One set for Select, SearchSelect, Cascader and TreeSelect (`.ui-control-affix-action` / `-icon` / `-indicator`); the Dialog and Sheet close \u2715 borrow `--control-affix-icon-size` so every overlay \u2715 in the system is one size. Before #319 each of those hard-coded the same `end-2 size-6 rounded-sm opacity-50` stack, so retuning affix weight meant chasing the same literal through four components and missing one. `--control-affix-rest-alpha` (0.5) is the resting weight and is deliberately QUIETER than the inside-field set's 0.7 (`--control-inline-affix-rest-alpha`): an overlay affix floats over the selected value, so at equal weight the \u2715 starts reading as content. `--control-trigger-space-inline-end` (2.25rem, a raw rem because the `pe-9` it replaced is a flat Tailwind step, not a density-scaled one) is the inline room the trigger reserves so a long selected label never runs UNDER the affix \u2014 grow the affix without growing this and the label collides with it; shrink it without shrinking this and you ship dead space at every trigger's end."},{name:"--control-inline-affix-{size,icon-size,space-gap,inset-inline,rest-alpha,space-inline-end,pair-space-inline-end}",category:"component",tier:"component",role:"INSIDE-FIELD affix \u2014 the leading/trailing controls Input, Textarea, TimePicker and DatePicker render WITHIN the field box, as opposed to the overlay set (`--control-affix-*`) that a select-family trigger parks on top of. Kept a SEPARATE set on purpose, not an alias: these sit on the field's own surface rather than over it, so they rest heavier \u2014 `--control-inline-affix-rest-alpha` is 0.7 against the overlay's 0.5. Collapse the two into one knob and one side always loses: either the inline affixes go too faint to find, or the overlay \u2715 starts fighting the value it sits on. `--control-inline-affix-space-inline-end` (2.25rem) reserves room for ONE affix; a field parking TWO (TimePicker's clear+clock, DatePicker's clear+calendar) reserves `--control-inline-affix-pair-space-inline-end` (3.5rem) instead \u2014 retune only the single knob and the two-affix fields run their own text under the icons. Textarea has no single line to centre on, so it pins its clear to the top-end corner via `--textarea-clear-inset-block-start` rather than centring like Input."},{name:"--textarea-autogrow-{line-height,min-height-rows,max-height-rows,box-inset} / --control-multiline-padding-block",category:"component",tier:"component",role:"Textarea `autoGrow` geometry \u2014 the box that grows with its content. The floor and ceiling are counted in TEXT ROWS (`--textarea-autogrow-min-height-rows` 1, `--textarea-autogrow-max-height-rows` 8), not pixels, because a row count survives a density change and a `--font-size-base` retheme while a px ceiling silently stops matching the visible line count. A service sets its composer's resting and maximum height once in theme; the `minRows` / `maxRows` props override per instance \u2014 the same theme-global / prop-local priority as `--form-label-width` vs `labelWidth`. `--textarea-autogrow-line-height` is `initial` at :root and read as `var(--textarea-autogrow-line-height, var(--line-height-normal))` at the call site, so a service that retunes the multiline line-height for CJK legibility retunes the row unit with it instead of getting a ceiling that no longer means eight visible lines. `--textarea-autogrow-box-inset` is the non-text part of the box the row maths adds back (block padding + border, both edges) and `--control-multiline-padding-block` is that block padding, shared with `.ui-control-multiline` itself so the two cannot drift. The floor is additionally clamped up to `--control-height`, so a resting one-row composer still lines up with the Input / Button on its row rather than undercutting the control tier. Every default reproduces today's geometry when `autoGrow` is absent."},{name:"--control-composite-field-*",category:"component",tier:"component",role:"The bordered two-input shell (`.ui-control-composite-field`) shared by DateRangePicker, MonthPicker and MonthRangePicker: one field box wrapping two inputs plus a separator so the pair reads as a SINGLE control instead of two adjacent ones. `--control-composite-field-space-gap` (`--space-2`) is the rhythm between the halves and the separator. Shared deliberately \u2014 before #319 each picker carried its own literal and the range fields drifted into slightly different boxes, the tell that a design system is not actually one system. The box's height, border, disabled state and focus chrome still come from `--control-*` / `.ui-control`, so retune this only for the internal gap; anything else belongs on the control family."},{name:"--menu-item-{height,radius,space-inline,space-gap,font-size,inset-space-inline-start} / --menu-content-{space-inset,min-width}",category:"component",tier:"component",role:"ONE row rhythm for every popup list surface: ContextMenu, Menubar, DropdownMenu AND Select's listbox all lay their rows out against these (`.ui-select-item` sits in the same rule as `.ui-dropdown-menu-item`), so a service retunes menu density once instead of four times. Before #319 the height was a literal `2rem` in the CSS and DropdownMenu had not been converted at all \u2014 it carried its whole box as Tailwind literals on the component. Set `--menu-item-height` and all four surfaces resize together; patch one component's padding instead and you get the four-menus-that-almost-match tell. `--menu-item-inset-space-inline-start` is the indicator column an inset row (a checkbox/radio row with no indicator of its own) reserves so its label stays aligned with its checked siblings \u2014 move it whenever you change `--menu-indicator-size`, or the two columns desynchronise. `--menu-content-min-width` (10rem) and `--menu-content-space-inset` are the panel, not the row; DropdownMenu re-points the min-width at `--dropdown-content-min-width` (8rem) because it anchors to a small trigger, so override that one to widen dropdowns alone."},{name:"--table-*",category:"component",tier:"component",role:"Table row/cell sizing."},{name:"--table-pagination-padding-{y,x}",category:"component",tier:"component",role:"DataTable.Pagination footer inset. The footer used to declare `padding-top` ONLY, so in the documented flush container (`<Card><CardContent flush><DataTable/>`) the 'rows per page' label and the page-size Select sat flush against the container edge and its closing border. It now owns `padding-block` + `padding-inline`. Both knobs are declared `initial` with the default at the CALL SITE \u2014 block `var(--space-stack-sm)` (the old padding-top value, now on both block edges), inline `var(--table-cell-space-x)` so the label lands on the same optical axis as the first column's text. `initial` is load-bearing: both defaults are density-scaled and re-declared inside a `.ui-density-*` subtree, and a `:root` binding would freeze the footer at the `:root` density. Consumers no longer need a local `.ui-data-table-pagination` override; a service on another grid retunes either axis."},{name:"--table-surface-min-inline-size",category:"component",tier:"component",role:'DataTable surface narrow-viewport width FLOOR. Below the `sm` viewport step a multi-column admin grid whose cells are `white-space: nowrap` would be crushed, so the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. This shipped as a hard-coded `min-w-[640px] sm:min-w-0` utility pair ON the surface element \u2014 the literal that forced the horizontal scroll at 390 \u2014 leaving a service that wants a narrower (or no) floor with no route but forking the component (rule #45). Default `640px`, byte-identical to the old utility, and deliberately px so the floor releases at exactly the same width as the px-based `sm` media query that clears it (a rem value drifts under a non-16px root and re-introduces the scroll between the two thresholds). Set `0` to opt out; `DataTable preset="action-collection"` already does, because there the column-priority measures own the width.'},{name:"--form-label-width",category:"component",tier:"component",role:"Label column width in horizontal Form layout. Default max-content; a service theme sets it once (e.g. 110px) \u2014 the labelWidth prop overrides per form/field."},{name:"--form-label-gap",category:"component",tier:"component",role:"Label\u2194control column gap in horizontal Form layout. Default 16px (--space-4)."},{name:"--dialog-* / --alert-* / --skeleton-*",category:"component",tier:"component",role:"Feedback component sizing and spacing."},{name:"--banner-{radius,border-width,space-inset-block,space-inset-inline}",category:"component",tier:"component",role:'Banner strip geometry \u2014 the full-bleed presentation of the Alert primitive (`<Banner>` / Alert variant="banner"). radius 0 (square strip corners), a single hairline block-end rule (tone-coloured by the alert rules), block inset denser than the inline card (--space-3), inline inset defaulting to the page gutter (--space-page-active-x) so banner text aligns with page content. Colour stays entirely with `tone`; a service retunes the strip once here \u2014 a consumer never writes CSS to place a banner.'},{name:"--page-header-status-gap",category:"semantic",tier:"semantic",role:"Gap between the page title and its status/meta band (PageContainer `status`), and between the band's own items. Default --space-inline-sm. The band shares the title line and wraps under it on compact viewports; a page that never passes `status` reads neither the token nor the wrapping row."},{name:"--sheet-responsive-breakpoint-width / --sheet-bottom-max-height",category:"component",tier:"component",role:`The responsive drawer / detail-panel contract (SheetContent responsive="auto"). --sheet-responsive-breakpoint-width (default 48rem = 768px, the library's canonical mobile line) is the viewport width at and below which the desktop side panel becomes a mobile bottom sheet; --sheet-bottom-max-height (default 85dvh) caps THAT bottom presentation only (a plain side="bottom" sheet stays content-sized). Because a CSS @media cannot resolve a custom property, the breakpoint is read off :root at runtime by the exported useSheetResponsiveMode() hook \u2014 which is also what OrgSwitcher responsive="auto" uses, so ONE knob moves the popover\u2192sheet and side\u2192bottom line for every overlay at once. Accepts px/rem/em.`},{name:"--scroll-area-anchor-offset",category:"component",tier:"component",role:'ScrollArea `anchor="bottom"` stickiness band: how close to the bottom edge the reader still counts as "following the stream". Inside it, arriving content keeps the viewport pinned to the newest item; one pixel beyond it the reader is reading history and anchoring never moves them again until they come back (WCAG 3.2.5). Sized to the row height a service renders \u2014 a dense audit line is ~20px, a chat bubble with an avatar ~64px, and "one row from the bottom" is what the reader means. In rem so the band is still one row at 200% zoom. A CSS comparison cannot express it, so the component reads the token off the element at mount (px/rem/em accepted); the `anchorOffset` prop overrides it per instance. The rail geometry knobs (--scroll-area-bar-size / -bar-padding / --scroll-area-thumb-radius) live with the data-display tier.'},{name:"--qr-code-*",category:"component",tier:"component",role:"QrCode scanner-safe foreground/background and xs/sm/md/lg natural-size tiers. Keep foreground/background at strong contrast; the defaults remain dark-on-light in dark application themes."},{name:"--chart-trend-plot-height{,-xs,-sm,-md,-lg}",category:"component",tier:"component",role:"CompactBarTrend plot-height tiers (xs 3.5rem = dashboard summary-card density, sm 5rem, md 7.5rem, lg 10rem). The `size` prop picks a step via data-size; a service retunes the whole scale here. This is why a consumer NEVER writes an inline height for a compact trend."},{name:"--chart-trend-bar-{gap,radius,max-width,min-height}",category:"component",tier:"component",role:"CompactBarTrend mark geometry: gap between bars (default --space-2), rounded data-end radius (--radius-sm), the per-mark width ceiling (1.5rem, so a 3-point trend does not render slabs) and the min height floor (2px, so a zero value still reads as a plotted category). All service-tunable (rule #45)."},{name:"--chart-trend-bar-background{,-alpha} / --chart-trend-bar-emphasis-background",category:"component",tier:"component",role:"CompactBarTrend fills \u2014 role-mirror knobs declared `initial` with the role default at the call site. Muted marks default to hsl(var(--muted-foreground) / --chart-trend-bar-background-alpha) (0.75, which clears the 3:1 non-text contrast floor); the emphasized 'current' mark defaults to hsl(var(--primary)). Point them at --chart-N to tint a trend to a series colour."},{name:"--chart-trend-baseline-border",category:"component",tier:"component",role:"CompactBarTrend baseline rule. QUIET by default (rule #44 \u2014 resolves to `none`); a service opts in with `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-{tick-gap,tick-font-size,footer-gap}",category:"component",tier:"component",role:"CompactBarTrend category-tick and activity-footer rhythm \u2014 the plot\u2194tick gap, tick type size (--font-size-xs) and the plot\u2194footer gap."},{name:"--email-shell-{width,padding,page-padding,border-width} / --email-card-{radius,reference-height} / --email-stack-gap{,-sm}",category:"component",tier:"component",role:"Transactional-email shell geometry: the 480px card width, its 32px inset, the 24px viewport gutter, the 1px edge, the 10px radius (mirrors --card-radius) and the block rhythm. The canonical invitation reference measures 480\xD7407 (--email-card-reference-height is a visual-regression target, NEVER a fixed height on the card). Values are LITERAL px \u2014 email clients resolve neither var() nor rem \u2014 and are consumed through the `@godxjp/ui/email` export (EMAIL_SHELL), never by a stylesheet."},{name:"--email-{body,heading}-{font-size,line-height,font-weight} / --email-font-family-{sans,mono}",category:"component",tier:"component",role:`Email type ramp, reconciled with the SCR-302 canonical reference. Body 14px/1.9, title 17px/1.7 at weight 500 (a transactional title is CALM, not bold) \u2014 deliberately looser and quieter than the web ramp, because a mail client gives the reader no density control, so do NOT 'fix' it back to --heading-h1/--line-height-body. --email-font-family-sans names the canonical Noto Sans JP face first and degrades Hiragino \u2192 Yu Gothic \u2192 M PLUS 2 \u2192 Meiryo \u2192 system UI \u2192 Arial \u2192 sans-serif; no email client honours @font-face, so the stack is the whole fallback contract. --email-font-family-mono (mirrors --font-family-mono) sets invoice ids, masked card numbers, ISO dates and amounts. Family names export SINGLE-quoted so the value drops into a double-quoted style="\u2026" attribute unescaped (emailInlineStyle throws on a double quote). Exposed as EMAIL_TYPOGRAPHY.`},{name:"--email-mark-{width,height,gap} / --email-wordmark-{font-size,font-weight}",category:"component",tier:"component",role:'Email brand-mark LOCKUP. The ARTWORK is byte-identical to <Logo mark="godx" /> (the 32\xD732 capsule + internal glyph, asserted by test); only the rendered box is email-specific \u2014 the canonical header sets a 22px mark, an 8px gap (mirrors --logo-wordmark-gap) and a 13px/700 wordmark (mirrors --logo-wordmark-font-weight). In that pairing the mark is DECORATIVE (render it with label: "") and the wordmark carries the accessible name, so the brand is announced once. Exposed as EMAIL_BRAND_MARK; the svg / dataUri / tableHtml deliveries all derive from this one geometry source.'},{name:"--email-cta-{height,line-height,padding-x,radius,font-size,font-weight} / --email-focus-border-width",category:"component",tier:"component",role:"The single primary email CTA, reconciled with the SCR-302 canonical reference: 44px tall with line-height EQUAL to the height so the label centres without flexbox in Outlook, 16px inline padding (--space-4), the 6px --radius base, 14px/500 label. The height is a TOUCH TARGET and is deliberately DECOUPLED from --control-height-lg (36px), which it mirrored until 18.8.0 \u2014 do not restore the mirror. Email is touch-only: no hover, no precise pointer, no dependable zoom, so the web AA floor (SC 2.5.8, 24\xD724, which 36px clears) is the wrong bar. 44px is SC 2.5.5 Target Size (Enhanced, AAA, 44\xD744) plus the Apple HIG 44pt / Material 48dp convention, and the mobile reflow still takes the CTA full-bleed. Fill/label are EMAIL_COLORS.primary / primaryForeground, derived from the --primary role \u2014 never paste a canonical hex, or the email stops tracking a re-theme. --email-focus-border-width mirrors --focus-ring-width for the webmail panes that honour :focus-visible. Exposed as EMAIL_CTA / EMAIL_FOCUS."},{name:"--email-footer-{font-size,line-height,link-gap,padding-top,border-width} / --email-mobile-*",category:"component",tier:"component",role:"Legal-footer typography (11px/1.8 quiet type per the SCR-302 canonical, 12px between adjacent links, 20px above the hairline) and the narrow-viewport reflow (520px breakpoint, fluid card, 20px inset, 12px gutter, 16px heading \u2014 one step under the 17px desktop title, full-bleed CTA). The canonical 390px reference raster is unusable (435px wide and already clipping the card, dxs-platform/platform#496), so the mobile ramp is the library's documented contract rather than a measured value. Exposed as EMAIL_FOOTER / EMAIL_MOBILE; every reflow rule needs an inline fallback because most clients strip <style>."},{name:"--auth-shell-device-{card-max-width,main-padding,main-padding-mobile,otp-slot-inline-size,otp-slot-block-size}",category:"component",tier:"component",role:'AuthShell `preset="device-authorization"` measure \u2014 the canonical OAuth device-grant artboard: a 23.75rem/380px card at 1440/1024 and a 15px-block \xB7 5px-inline page gutter at 390 (so the card renders x=5px, width=380px), PLUS the code field the flow is actually about. The preset now owns its OTP slot box and hands it to --otp-slot-{inline,block}-size: 27.5x52 per slot, i.e. a 4-slot `appearance="grouped"` box of 112x54 with its 1px group border. Without it the field fell back to the square --control-height tier and rendered 146x38 against that artboard \u2014 the preset owned the page but not its subject. LITERAL lengths, not --space-*, because the artboard specifies exact device pixels that must not drift with --scaling. Selecting the preset replaces a consumer-side --auth-shell-card-max-width override or a forked `.auth-shell--wide` class; the canonical 360px/15px default is untouched.'},{name:"--auth-shell-context-{card-max-width,main-padding,main-padding-mobile,card-stack-gap}",category:"component",tier:"component",role:'AuthShell `preset="context-selection"` measure \u2014 the organisation/context picker: a 25rem card on desktop/tablet, edge-to-edge on mobile (0 inline gutter), and a 1rem rhythm between the three direct sections of the auth column (intro \xB7 choice card \xB7 "remember" row).'},{name:"--auth-shell-recovery-{card-max-width,main-padding,main-padding-mobile}",category:"component",tier:"component",role:'AuthShell `preset="account-recovery"` measure \u2014 the SCR-008 27rem/432px panel, shared by the password-recovery panel (request \xB7 sent \xB7 new-password \xB7 expired) AND the sign-in MFA challenge panel (OTP \xB7 recovery-code \xB7 passkey-failure), because both canonical desktop panels measure w=432 at 1440. Mobile gutter = 15px inline at <=30rem, so the panel is x=15, width=360 \u2014 the SAME rhythm as the canonical Login flow, chosen deliberately: the supplied 390 reference is a desktop 2x2 composite that crops horizontally and is NOT a valid source. The canonical 360px Login measure and the 24rem un-preset shell are untouched.'},{name:"--auth-shell-registration-{card-max-width,main-padding-block-start,main-padding-block-start-mobile,main-padding-inline,main-padding-inline-mobile,main-padding-block-end,main-padding-block-end-mobile,card-stack-gap,identity-slot-block-size}",category:"component",tier:"component",role:'AuthShell `preset="registration"` measure \u2014 the canonical SCR-002 sign-up flow: a 22.5rem/360px form measure with a 15px inline gutter at 390 (card x=15, width=360 \u2014 the same page rhythm as the Login flow, so sign-in \u2192 sign-up never jumps on a phone). START-aligned (a tall sign-up card must scroll, not clip its own top above the scroll origin), with the block-start offset DERIVED from the canonical artboard (card y=284 at 1440x900, y=274 at 390x844: padding-block-start + 112px identity slot + 20px stack gap), and a footer-clearance knob of its own (block-end 3rem / 2rem mobile) so the legal/consent footer never sits flush against the submit button after a long scroll. The fixed 112px identity track absorbs absent / one-line / wrapped two-line identity copy without moving the card anchor.'},{name:"--otp-slot-size",category:"component",tier:"component",role:"InputOTP slot box. Default `var(--control-height)` \u2014 byte-identical to before and still density-aware \u2014 but its OWN knob, so an auth panel can widen the 6-slot challenge row to fill a wide surface WITHOUT re-scoping --control-height (which would resize the primary button and every other input in the same card). Set it to a NAMED tier (`var(--control-height-lg)`), never an ad-hoc calc offset."},{name:"--otp-slot-{inline-size,block-size}",category:"component",tier:"component",role:'Per-AXIS InputOTP slot box. --otp-slot-size stays the SQUARE shorthand; these two win over it and fall back to it, so a code field that is taller than it is wide \u2014 the canonical device-grant slot, 27.5x52 \u2014 is finally expressible from a token. Declared `initial` so the whole chain (axis \u2192 square \u2192 --control-height) resolves at the CALL SITE and a shell that re-scopes --control-height still reaches a field that sets neither axis. `AuthShell preset="device-authorization"` sets both for you.'},{name:"--otp-container-align",category:"component",tier:"component",role:'Main-axis alignment of the whole InputOTP row. Default `flex-start` = the historical layout, so nothing moves. It exists because a centred code challenge is the common auth case and the container element belongs to `input-otp` \u2014 the only thing a consumer could reach was a wrapper div, and every one of them wrote it. Per instance use `align="center"` on InputOTP, which outranks this token.'},{name:"--tabs-indicator-{background,size,offset}",category:"component",tier:"component",role:"Tabs `line` ACTIVE indicator \u2014 the only selected-state decoration the line variant paints; it never gets a surrounding ring/card border, so the `:focus-visible` keyboard ring stays visible and distinct (WCAG 2.4.7). --tabs-indicator-background is declared `initial` so its hsl(var(--primary)) default re-resolves at the CALL SITE under a scoped [data-tenant]/.dark theme. Defaults = primary \xB7 2px \xB7 offset 0 (flush with the trigger edge); raise the offset to park the bar on a thicker strip hairline. The selected ring on the default/card variants is unchanged."},{name:"--avatar-square-{radius,size,background,foreground}",category:"component",tier:"component",role:'Avatar `shape="square"` entity-header mark \u2014 the compact rounded square an organization/service header uses instead of the round person avatar. --avatar-square-background / --avatar-square-foreground are declared `initial` so the hsl(var(--primary)) / hsl(var(--primary-foreground)) defaults re-resolve at the CALL SITE under a scoped theme. Defaults = --radius-lg corners \xB7 --control-height box (swapping shape never reflows the header) \xB7 primary fill at 4.65:1 contrast. `shape="circle"` (the default) is inert and reads --avatar-background / --avatar-tint as before.'},{name:"--avatar-tinted-{background,foreground,glyph-size}",category:"component",tier:"component",role:'Avatar `appearance="tinted"` \u2014 the CAPABILITY MEDALLION: the tinted plate a feature/capability glyph sits on, as opposed to the solid entity mark above. Orthogonal to `shape`, so `shape="square" appearance="tinted"` is the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are declared `initial` so the hsl(var(--primary) / 0.1) / hsl(var(--primary)) defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme \u2014 which is the whole point: consumers were writing that 0.1 alpha literal into page CSS, or giving up and rendering a bare glyph, because the medallion is a composition (Avatar + a Lucide glyph) whose tint had no token. Defaults = 10% primary wash \xB7 primary glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-presence-{size,min-size,inset,ring-width,ring-color,stroke-width,bar-inline-size,bar-block-size,online-color,away-color,busy-color,offline-color}",category:"component",tier:"component",role:'Avatar `presence` \u2014 the realtime reachability dot on the mark. Every constant is a knob because the workaround it replaces baked four of them plus a raw palette colour into consumer page CSS (`-end-0.5 -bottom-0.5 size-2.5 ring-2 bg-green-500`). --avatar-presence-size is a PROPORTION of the mark (default 30%, floored by --avatar-presence-min-size = --space-2), NOT a px step, so one value tracks every avatar the system paints \u2014 the --control-height box and its xs/sm/lg steps, --avatar-square-size, --org-switcher-avatar-size, --upload-avatar-size, a call site\'s own `size-12`, the 36px ListRow density="compact" mark \u2014 instead of freezing a diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs declared `initial`, with the role default at the CALL SITE (`var(--knob, var(--role))`), so a scoped [data-tenant]/.dark override of --background / --success / --warning / --destructive / --muted-foreground reaches them \u2014 bound at :root the ring would freeze light-mode-white on a dark avatar. --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the ACCESSIBLE encoding, not decoration: they draw the per-state silhouette (filled / half-filled / barred / hollow) that keeps presence off colour-alone (WCAG 1.4.1) in greyscale, for a deuteranope and under forced colors. Defaults = 30% diameter (floor 0.5rem) \xB7 flush with the corner (inset 0) \xB7 a 2px --background separator ring \xB7 a 1.5px state stroke \xB7 a 56%-wide 1.5px dnd bar.'},{name:"--separator-{rule-size,rule-color,label-gap,label-inset,label-font-size,label-line-height,label-font-weight,label-color,tone-<tone>-rule-color,tone-<tone>-label-color}",category:"component",tier:"component",role:"Separator, including the LABELLED rule a `label` interrupts \u2014 a message stream's day divider, a \"new messages\" unread watermark, an auth conjunction. Before #308 the only labelled rule in the library was the auth-scoped AuthDivider, which baked `height: 1px` into shell-layout.css and read the `--auth-shell-divider-*` micro-scale, so a service retuning its login divider silently retuned every day divider in its chat. Defaults are the QUIETEST state: a 1px hairline, a muted --font-size-xs label at --font-weight-medium, no surrounding padding. --separator-label-gap and --separator-label-inset (the SHORT rule half under labelAlign start/end \u2014 a grid track on the inline axis, so it flips under RTL) are declared `initial` because --space-* is density-scaled and re-declared inside a `.ui-density-*` subtree; a :root binding would freeze them at the :root density. Every colour knob is a role-mirror and is likewise `initial`, with the role default at the CALL SITE \u2014 --separator-rule-color \u2192 --border, --separator-label-color \u2192 --muted-foreground, and --separator-tone-{muted,primary,success,warning,destructive,info}-{rule,label}-color \u2192 the matching semantic role. `tone` re-points the rule AND the label, never the rule alone, so an attention rule is not colour-only (WCAG 1.4.1) and survives forced-colors. AuthDivider now only re-points these knobs at the --auth-shell-divider-* layer, so #263's canonical login geometry is unchanged."},{name:"--steps-inline-{gap,item-gap,font-size,separator-size,index-font-weight,index-color,separator-color}",category:"component",tier:"component",role:"Steps type=inline compact auth/device progress rhythm, typography and EMPHASIS. Consumers select the semantic appearance; services retune the row once without page CSS. The step number's emphasis is two knobs \u2014 --steps-inline-index-font-weight (default bold) and --steps-inline-index-color (role-mirror `initial`, default the inherited step colour) \u2014 because the canonical hosted-identity row marks the step with an accent TINT at normal weight while the library default marks it with bold. --steps-inline-separator-color (role-mirror `initial`, default --muted-foreground) tints the glyph the `separator` prop picks."},{name:"--auth-account-summary-{min-height,gap,padding,identity-min-width,avatar-size,avatar-glyph-size,email-font-size}",category:"component",tier:"component",role:"AuthAccountSummary compact signed-in identity row: wrapping threshold, avatar, email and action rhythm. The default touch-safe row is package-owned and remains themeable without consumer selectors."},{name:"--auth-shell-{main-align,card-stack-gap}",category:"component",tier:"component",role:"AuthShell column knobs shared by every preset. --auth-shell-main-align is the block alignment of the auth column (default `center`, the vertically-centred card; set `start` for a tall intro+card+footnote stack). --auth-shell-card-stack-gap is the gap between the card slot's direct sections \u2014 default `0px` (quiet, rule #44) so an existing single-card consumer is byte-for-byte unchanged; a preset opts in."},{name:"--card-{accent-rail-width,accent-perimeter-width,accent-perimeter-ring-width,featured-border-color,featured-ring-width}",category:"component",tier:"component",role:'Card ACCENT geometry, one set per placement. `accentPlacement="edge"` (the default) draws the leading rail at --card-accent-rail-width (6px). `accentPlacement="perimeter"` draws the FULL attention border, measured by --card-accent-perimeter-width + --card-accent-perimeter-ring-width (1px + 1px, the optical weight of `variant="featured"`) in the card\'s own semantic accent tone. `variant="featured"` is the brand-toned member of the same family and no longer hard-codes --primary: its edge is the --card-featured-border-color role-mirror knob (`initial`, so the --primary default resolves at the CALL SITE and a scoped [data-tenant]/.dark override reaches it) at --card-featured-ring-width. The accent COLOUR itself is not a knob \u2014 it is resolved from `data-accent` on the card, so retint the ROLE (--attention, --success \u2026).'},{name:"--card-space-shell-y",category:"component",tier:"component",role:"Card BLOCK-axis shell padding \u2014 a plain header's top, a `solo` body's top and the terminal slot's bottom. Split off --card-space-inset, which is now inline-only, so a theme can make a card SHORTER without narrowing its column. Declared `initial`: the default resolves at the CALL SITE to --card-space-inset, so a card that overrides neither (and `density=\"tight|cozy\"`, which re-declares it `initial` to re-arm the per-instance inset) renders exactly as before."},{name:"--auth-shell-{compact-card-inset,card-padding-block-compact,card-body-gap-compact,card-gap-compact}",category:"component",tier:"component",role:"The compact AuthShell card's four independent knobs \u2014 one per axis, so the canonical Login card is tunable WITHOUT a consumer selector on the card-content slot: `compact-card-inset` = the inline column (\u2192 --card-space-inset); `card-padding-block-compact` = the card's block/top-bottom padding (\u2192 --card-space-shell-y, `initial` so its default mirrors the live inline inset and canonical output is unchanged); `card-body-gap-compact` = the header\u2194body gap (\u2192 --card-space-body-y, 12px, the role the block knob used to be mis-wired to); `card-gap-compact` = the in-slot title\u2195description stack gap (\u2192 --card-space-gap)."},{name:"--app-setting-picker-compact-{control-height,padding-x,gap,font-size}",category:"component",tier:"component",role:'AppSettingPicker `compact` trigger \u2014 the small, content-hugging LABELLED switcher for an auth/legal footer (`kind="locale" appearance="labeled" compact`). The box height reads the official --control-height-sm tier (never a literal or an ad-hoc calc), and padding/gap/type are separate knobs so a service theme retunes the footer switcher without forking CSS.'},{name:"--centered-shell-column-offset-block",category:"component",tier:"component",role:'CenteredShell column block offset \u2014 default `0` (the quiet state, rule #44: the top-aligned flowing/scrolling page). `<CenteredShell align="center">` flips it to `auto`, which centres the column in the 100dvh shell: the package-owned geometry for a SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice) at 1440/1024/390, so a consumer never writes `min-h-dvh` + flex-centring CSS or a className. Auto block offsets collapse to 0 when the content is taller than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--error-surface-*",category:"component",tier:"component",role:'ErrorSurface knobs \u2014 the whole 403/404/500/503 exception surface is themeable without a single consumer class or media query, in BOTH `mode="application"` and `mode="system"`. Geometry: `--error-surface-max-width` (32rem measure of the surface block, so a long JA/VI headline wraps readably even inside a wide PageContainer), `--error-surface-gap` (rhythm between status code \xB7 body \xB7 metadata \xB7 progress), `--error-surface-padding-block` (--space-10, the 1440/1024 steps) and `--error-surface-padding-block-compact` (--space-6, below the narrow step, i.e. the 390 case), `--error-surface-brand-gap` (system-mode Logo slot) and `--error-surface-progress-max-width` (18rem, so the maintenance meter reads as metadata, not as the page\'s primary content). Metadata list: `--error-surface-meta-gap` (row rhythm) \xB7 `--error-surface-meta-row-gap` (label\u2194value gap inside one row) \xB7 `--error-surface-meta-padding-block`. Chrome is QUIET by default (rule #44): `--error-surface-meta-border` is `none`; a service opts in with `1px solid hsl(var(--border))`. The narrow step is a CONTAINER query at 30rem on the SURFACE\'s own width, not a viewport media query, so it compacts correctly whether the squeeze came from a 390px phone or from an application shell with an expanded sidebar; below it, metadata rows stack label-over-value. System-mode viewport centring is delegated to `--centered-shell-column-offset-block` (via CenteredShell align="center"), and the description measure to `--empty-state-description-max-width`.'},{name:"--centered-shell-landing-*",category:"component",tier:"component",role:'CenteredShell public-landing knobs \u2014 the SHELL geometry of a public marketing/product landing, so the landing itself stays a COMPOSITION (Hero/Navbar/Footer fail the Framework-Component Test) with ZERO page-local CSS. Read only under `preset="public-landing"`; the default shell emits no `data-preset`, so none of them can match. Measure: `--centered-shell-landing-max-width` (67.5rem, shared by the header bar, the centred column AND the footer \u2014 the bar/footer inline padding is `max(gutter, (100% - measure) / 2)`, so header content starts on exactly the column edge and no consumer max-width wrapper exists), `--centered-shell-landing-inset-inline` (+ `-compact`, the 390 gutter), `--centered-shell-landing-main-padding-block` (+ `-compact`), `--centered-shell-landing-section-gap` (+ `-compact`, the rhythm BETWEEN plain `<section>` elements \u2014 the consumer never sets a gap) and `--centered-shell-landing-footer-padding-block`. Chrome is QUIET by default (rule #44): `--centered-shell-landing-card-shadow` is `none`, which flattens every Card in the subtree from ONE knob instead of a consumer `.landing .card { box-shadow: none }`. Type: `--centered-shell-landing-heading-size` (+ `-compact`) re-points `--heading-h1`, so a hero title is a real `Heading level={1}` and never a page font-size. `--centered-shell-landing-background` is a ROLE-MIRROR declared `initial` with the role default at the call site (`var(--centered-shell-landing-background, var(--background))`), so a scoped [data-tenant]/.dark override of `--background` still reaches the landing canvas. The compact step is a 40rem media query owned by the package; region visibility (anchor nav below the tablet step, wordmark and the secondary action at mobile) is `Flex hideBelow` / `hideFrom`, never a consumer @media.'},{name:"--table-action-collection-*",category:"component",tier:"component",role:'Table action-collection knobs \u2014 the canonical dense approval/action queue at 1440 \xB7 1024 \xB7 390. Read only under `preset="action-collection"`. Column PRIORITY measures replace the desktop intrinsic widths (a `white-space: nowrap` free-text column is what makes a five-column queue wider than its card and forces the horizontal scroll): `--table-action-collection-primary-width` (18%), `-secondary-width` (22%), `-meta-width` (12%) and `-actions-width` (3.5rem \u2014 an absolute measure because the row-action affordance must never be squeezed below its touch target); an unmarked column takes the remaining space. Below the collapse step the same four re-point to their `-compact` tier (24% / 22% / 20% / 2.75rem) together with `--table-action-collection-font-size-compact`, `-row-height-compact`, `-cell-space-x-compact` and `-cell-padding-y-compact`. The step is a CONTAINER query on the table\'s own width (sm 40rem \xB7 md 48rem \xB7 lg 64rem \xB7 xl 80rem via `collapseBelow`), not a viewport media query, so a table inside a master rail collapses before the page does. Percentages so the ratio holds at any card width; a service retunes the ratio once instead of authoring column widths per screen (rule #45). ONE family serves BOTH entry points \u2014 the `Table` primitive (`priority` on `TableHead`/`TableCell`) and the TanStack `DataTable` (`priority` on the `ColumnDef`, stamped onto both cells for you). There is deliberately no parallel `--data-table-action-collection-*` family: retune these once and every queue follows.'},{name:"--legal-document-*",category:"component",tier:"component",role:"LegalDocumentShell knobs \u2014 the whole legal/policy document surface is themeable without a single consumer `.legal-*` class. Geometry: `--legal-document-measure-max-width` (46rem readable measure, caps header/body/footer), `--legal-document-toc-width` \xB7 `--legal-document-column-gap` \xB7 `--legal-document-toc-inset-block-start` (sticky offset) \xB7 `--legal-document-toc-max-height`, `--legal-document-section-gap` \xB7 `--legal-document-section-title-gap` \xB7 `--legal-document-header-gap` \xB7 `--legal-document-meta-gap` \xB7 `--legal-document-nav-gap` \xB7 `--legal-document-footer-gap`, `--legal-document-meta-font-size`, `--legal-document-body-line-height` (1.8 long-form leading) and `--legal-document-scroll-offset` (the `scroll-margin-block-start` a hash jump leaves above a heading \u2014 this is why no app needs JS offset arithmetic). Contents entries: `--legal-document-toc-font-size` \xB7 `--legal-document-toc-title-font-size` \xB7 `--legal-document-toc-item-padding` \xB7 `--legal-document-toc-item-radius` \xB7 `--legal-document-toc-marker-width`. Chrome is QUIET by default (rule #44): `--legal-document-toc-border` / `--legal-document-header-border` / `--legal-document-footer-border` are all `none`; a service opts in with `1px solid hsl(var(--border))`. The six colour knobs are ROLE-MIRRORS declared `initial` with the role default at the call site \u2014 `--legal-document-toc-foreground` (\u2192 --muted-foreground, also paints the contents caption), `--legal-document-toc-active-foreground` (\u2192 --foreground), `--legal-document-toc-active-background` (\u2192 --accent), `--legal-document-toc-marker-color` (\u2192 --primary), `--legal-document-meta-foreground` (\u2192 --muted-foreground) and `--legal-document-summary-foreground` (\u2192 --muted-foreground) \u2014 so a scoped [data-tenant]/.dark override of the role still reaches them. The one-column \u21C4 two-column split is a CONTAINER query at 56rem on the shell's own width, not a viewport media query."},{name:"--empty-state-description-max-width",category:"component",tier:"component",role:"EmptyState description MEASURE \u2014 default 28rem (~65 Latin characters per line). A service or locale that needs a shorter/longer measure for its own copy (JA/VI error-page descriptions, a narrow system surface) retunes this one knob instead of forking `.ui-empty-state-description` (rule #45)."},{name:"--sidebar-badge-{background,foreground} / --sidebar-badge-destructive-{background,foreground}",category:"component",tier:"component",role:'Sidebar nav COUNT PILL colour, split into a resting pair and an emphasis pair. Before these knobs `.sb-badge` had a font-size token and no colour token at all, so a rail that had to tell "unread" from "mentions you" could only nest a `<Badge>` INSIDE `SidebarItemProp.badge` \u2014 which renders a pill inside a pill (measured: a 37.11x19.14 `.sb-badge` wrapping a 25.11x19.14 `<Badge>` with its own border). The resting pair is what every badge has always looked like (hsl(var(--secondary)) fill, hsl(var(--muted-foreground)) text); the `-destructive-` pair is read ONLY by rows that pass `badgeTone="destructive"`, which is the only thing that emits `data-tone` on the pill (rule #44), so a rail that never sets the prop renders exactly the node it always did. All four are role-mirror knobs declared `initial` with the role default resolved at the call site, so a scoped [data-tenant]/.dark override reaches them. Colour ONLY \u2014 the pill\'s min-width, radius, inline pad and font size are shared by both tones, which is what keeps a mention row and an unread row aligned in the same column.'},{name:"--sidebar-nav-item-{foreground,hover-foreground,disabled-foreground} / --sidebar-nav-icon-{foreground,hover-foreground,active-foreground,disabled-foreground}",category:"component",tier:"component",role:"Sidebar navigation row + icon COLOUR, split so they can differ. Previously `.sb-nav-item` set one `color` for the whole row, so the Lucide SVG inherited `--muted-foreground` along with the label and the icons read as 'missing' against a canonical shell that wants darker 16px icons. `--sidebar-nav-item-*` drives the row/label (incl. sub rows), `--sidebar-nav-icon-*` drives `.sb-icon`. All are role-mirror knobs declared `initial` at `:root` with the role default at the call site, so a scoped `[data-tenant]`/`.dark` override reaches them; icon defaults resolve to `currentColor`, i.e. rendering is unchanged until a service opts in (`--sidebar-nav-icon-foreground: hsl(var(--foreground))` is the canonical darker-icon setting). Colour ONLY \u2014 the 16px icon, 32px row height and 10px gap are untouched. The active row keeps `--sidebar-item-active-{background,foreground}`."},{name:"--list-row-compact-{padding-y,padding-x,gap,body-min-width}",category:"component",tier:"component",role:'ListRow `density="compact"` inline-actions geometry \u2014 the compact invitation/history row: a 36px leading Avatar, a shrinkable title+description and one or two small trailing Buttons on ONE line inside the canonical 358px card (326px content). `--list-row-compact-body-min-width` (6rem) is the compact body threshold \u2014 the default 12rem `--list-row-body-min-width` is what forced the actions (and a history Badge + date) onto a second line at 390px; it is consumed as `min(<token>, 100%)` exactly like the default knob, so the #224 wrap contract is intact and a cluster that genuinely cannot fit still wraps instead of widening the page root. `--list-row-compact-{padding-y,padding-x,gap}` are declared `initial` with the density-scaled default at the CALL SITE (`var(--space-2)` block, `var(--list-row-padding-x)` inline, `var(--space-2)` gap) \u2014 a `:root` binding would freeze compact rows at the `:root` density. Measured at 390px: invitation row 62px (was 126px, actions wrapped), history row 41px (was 114px), documentElement scrollWidth === clientWidth.'},{name:"--list-row-{body-min-width,trailing-gap,indicator-size,read-background,unread-background,indicator-color}",category:"component",tier:"component",role:"ListRow shrink + unread state. `--list-row-body-min-width` (12rem) is consumed as `min(<token>, 100%)` \u2014 the `min()` is load-bearing, a bare length re-inflates the row's intrinsic width and reintroduces page-root overflow. `--list-row-trailing-gap` spaces wrapped trailing actions. The unread trio are role-mirror knobs declared `initial` with the role at the call site: `--list-row-unread-background` defaults to `hsl(var(--muted))` NOT `--accent` \u2014 accent measured 4.23:1 against the xs muted-foreground description line, below WCAG AA; muted is 4.63:1 light / 5.47:1 dark. `--list-row-indicator-color` (\u2192 `--primary`) and `--list-row-indicator-size` drive the unread dot, whose meaning is carried by localized sr-only text, never by colour alone."},{name:"--filter-bar-scroll-padding-y",category:"component",tier:"component",role:'Scrollbar gutter for `<FilterBar overflow="scroll">`, default `var(--space-1)`. Only applies at \u2265640px, where a wide filter set becomes a single nowrap strip with a sticky inline-end clear-all; below that the bar still stacks, so the gutter would be dead space. Pair with the existing `--filter-bar-sticky-background` (already an `initial` role-mirror knob) when theming the strip.'},{name:"--filter-bar-{search-width,filter-width,chip-gap,section-gap}",category:"component",tier:"component",role:"FilterBar typed-model geometry \u2014 every list page gets the SAME search width, filter width, chip rhythm and section stacking without page-local CSS (rule #45: theme sets once, className overrides per instance). `--filter-bar-search-width` (20rem) sizes the model search slot; `--filter-bar-filter-width` (\u2192 `--filter-picker-width-md`) is the min inline size of each typed filter group; both apply from 640px up, consumed as `min(100%, <token>)` so a 390px viewport never overflows with long JA/EN/VI labels (below 640px everything stacks full-width). `--filter-bar-chip-gap` (`--space-2`) spaces the applied-chips row; `--filter-bar-section-gap` (`--space-2`) stacks strip \u2192 chips \u2192 result-count/error line."},{name:"--app-shell-{sidebar-width,rail-width}",category:"component",tier:"component",role:"AppShell docked navigation rail widths \u2014 `--app-shell-sidebar-width` (default 16rem) is the expanded rail, `--app-shell-rail-width` (default 4rem) the icon-only rail used at `<AppShell sidebarCollapsed>`. These were hard-coded `grid-template-columns` literals; a service on a different design grid (e.g. a 255px rail) now sets ONE token in its theme instead of forking `.app-root`. Below the 900px shell breakpoint both collapse to a single full-width column and the sidebar moves into AppShell's drawer."},{name:"--app-shell-bar-{inset,inset-compact,gap}",category:"component",tier:"component",role:"AppShell top-bar chrome \u2014 `--app-shell-bar-inset` is the bar's inline padding, `--app-shell-bar-inset-compact` the tighter value below the compact step, `--app-shell-bar-gap` the gap between the bar's direct children (default --space-3). ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS: the bar sits in the SAME grid track as `.app-main`, directly above the page, so its first control and the page title are on one vertical edge. The two insets used to name their own values (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and nothing reconciled them \u2014 measured in Chromium, the bar's content started at x=80 and the page header's at x=88 (1512px), and because the two sides stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even constant (8px at 1512, 12px between 720 and 900, 4px below 720), which a consumer cannot correct by hand. The page gutter now OWNS the axis and these knobs read it: `--app-shell-bar-inset: var(--space-page-x)` (24px), `--app-shell-bar-inset-compact: var(--space-page-compact-x)` (16px), stepping at the PAGE's `(max-width: 720px)`, not the shell's 900px restructure. GEOMETRY MOVED \u2014 the bar's inline padding went 16px \u2192 24px (12px \u2192 16px compact, and 12px \u2192 24px between 720 and 900); the page did not move. The NAMES are unchanged, so a theme that already sets either knob keeps working, and a bar that genuinely wants to sit tighter than its page still has its own. Height stays `--app-shell-bar-height` (3rem) at EVERY width, and it is also the owner of the chrome BAND-HEIGHT axis \u2014 see `--page-header-min-block-size-chrome`."},{name:"--topbar-{height,inset,gap}",category:"component",tier:"component",role:"Standalone `<Topbar>` box knobs. Topbar is a pure slot bar, so the defaults are the QUIET ones and rendering is unchanged from before they existed: `--topbar-height: auto` and `--topbar-inset: 0px` (inside AppShell the `.app-topbar` grid row owns height and inset), `--topbar-gap: var(--space-2)`. A service that mounts Topbar DIRECTLY on a page sizes it from the theme (`--topbar-height: 3.5rem; --topbar-inset: var(--space-4)`) instead of an app-local class. `--topbar-gap` is both the gap BETWEEN the start/center/end clusters and the gap INSIDE each, so one knob re-rhythms the whole bar. None of them touch the shrink contract (start clips, center yields first, end stays anchored inline-end)."},{name:"--{dialog,sheet}-overlay-background / --app-shell-mobile-nav-background (+ -alpha)",category:"component",tier:"component",role:"Overlay scrims. All three are ROLE-MIRROR knobs declared `initial` at `:root`, with the default resolved at the CALL SITE as a SHARE of the one semantic `--overlay-background` \u2014 `color-mix(in srgb, var(--overlay-background) var(--<x>-overlay-alpha), transparent)`. Previously each carried a private literal, so setting `--overlay-background` had NO effect anywhere. The share knobs (`--dialog-overlay-alpha` 60%, `--sheet-overlay-alpha` 40%, `--app-shell-mobile-nav-alpha` 40%) keep every default byte-identical (0.3 / 0.2 / 0.2 black) while letting one `--overlay-background` override retint the whole system, including scoped `[data-tenant]` / `.dark` themes. Setting a `*-overlay-background` knob directly still wins outright. NOTE: overlays render in a portal, so a multi-tenant scope must also sit on the portal container."}];function I(t){return C.filter(a=>a.category===t)}var q=[{name:"--activity-mark-size",value:"0.25em",description:"Mark geometry. `em`-based so the whole mark tracks `--activity-font-size-*` (and therefore * `--font-size-base` and the density axis) instead of pinning a px diameter per size step."},{name:"--activity-mark-offset",value:"0.25em",description:"Travel of one `dots` step \u2014 the loop counterpart to `--reveal-distance`, named to match. * It is ALSO the height the mark row reserves, so the indicator appearing under a composer * never shifts the row."},{name:"--activity-mark-rest-alpha",value:"0.35",description:"Resting opacity of a dot at the bottom of its cycle. Never 0: a dot that vanishes reads as a * rendering bug at the trough, and the reduced-motion resting state must stay fully legible."},{name:"--activity-pulse-mark-size",value:"0.5em",description:"The single `pulse` mark \u2014 a breathing dot (live / recording), bigger than one `dots` dot * because it carries the whole affordance alone."},{name:"--activity-gap",value:"var(--space-1)",description:"Mark-to-mark and mark-to-label gap."},{name:"--activity-font-size-xs",value:"var(--font-size-2xs)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-font-size-sm",value:"var(--font-size-xs)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-font-size-md",value:"var(--font-size-sm)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-font-size-lg",value:"var(--font-size-lg)",description:"Root size ladder \u2014 the mark is `em`-based, so this ONE value scales the whole indicator. * Steps mirror the `Text` size the label is rendered at, so mark and label stay optically * paired (`--font-size-sm` IS `--font-size-base`; the ladder skips the alias)."},{name:"--activity-bar-width",value:"4rem",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-radius",value:"var(--radius-pill)",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-segment-width",value:"40%",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-track-alpha",value:"0.2",description:"Indeterminate `bar` (syncing). Width is an inline measure, not a fill: the bar sits beside a * label in a row, so `100%` would eat the label's space."},{name:"--activity-bar-height",value:"0.25em",description:"scale-exempt: an em thickness that must track the label beside it, not a fixed band step"},{name:"--activity-color",value:"initial",description:'Mark colour. ROLE-MIRROR KNOB \u21D2 declared `initial` here with the role default at the CALL SITE * (`hsl(var(--activity-color, var(--muted-foreground)))` in styles/motion.css). Written as * `--activity-color: var(--muted-foreground)` it would freeze at the `:root` role value and a * scoped `[data-tenant]` / `.dark` override of `--muted-foreground` would never reach it. * @see docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`". * Documented default: --activity-color = var(--muted-foreground) (tone="muted").'},{name:"--badge-space-gap",value:"var(--space-inline-xs)",description:"Badge component tokens."},{name:"--badge-space-x",value:"var(--space-2)",description:"Badge component tokens."},{name:"--badge-space-y",value:"var(--space-1)",description:"Badge component tokens."},{name:"--badge-font-size",value:"var(--font-size-xs)",description:"Small-by-design (badge/pill/counter). A knob (rule #45) so a service can * re-tune badge text without touching the global --font-size-xs step."},{name:"--badge-line-height",value:"calc(1 / 0.75)",description:"Companion to --badge-font-size (gh#260). The cva's old `text-xs` utility also set * line-height via Tailwind's default `--text-xs--line-height: calc(1 / 0.75)` (the theme * remaps --text-xs but never that companion). Same unitless ratio here keeps the default * badge pixel-identical now that badge-layout.css owns the type metrics."},{name:"--badge-icon-size",value:"var(--icon-size-xs)",description:"Leading/trailing glyph inside the pill. It was a bare `0.75rem` in badge-layout.css with no * token at all, so a service could only resize it with `!important` or a forked stylesheet \u2014 * the two routes the icon axis's tier 2 exists to abolish (gh#326). Same step, so nothing * moves. Matches --badge-font-size's step by design: the glyph reads as a character in the * label's run, so the two retune together. Deliberately NOT --scaling-multiplied \u2014 the literal * it replaces did not track density."},{name:"--banner-radius",value:"0",description:"Square corners \u2014 a strip spans its container edge-to-edge, so it carries no radius."},{name:"--banner-border-width",value:"1px",description:"The strip keeps a single hairline block-end rule (tone-coloured via the alert * border rules); inline/block-start edges are open so it reads as part of the frame."},{name:"--banner-space-inset-block",value:"var(--space-3)",description:"Strip inset. Block is denser than the inline Alert card; inline defaults to the * page gutter so banner text aligns with page content under AppShell/PageContainer."},{name:"--banner-space-inset-inline",value:"var(--space-page-active-x, var(--space-6))",description:"Strip inset. Block is denser than the inline Alert card; inline defaults to the * page gutter so banner text aligns with page content under AppShell/PageContainer."},{name:"--card-space-inset",value:"var(--space-section-active)",description:"Horizontal inset of every slot (header / content / footer) + the resting top/bottom * shell padding. This is the column the title, body and footer all align to."},{name:"--card-space-shell-y",value:"initial",description:'BLOCK-axis (top/bottom) shell padding of the slotted card rhythm \u2014 a plain header\'s top, a * `solo` body\'s top, and the terminal slot\'s bottom (gh#232). Split off --card-space-inset so a * shell (AuthShell compact) can retune the card\'s height WITHOUT moving the inline column. * Declared `initial` \u2014 NOT `var(--card-space-inset)` \u2014 so the default resolves at the CALL SITE: * a :root binding would freeze at the :root inset and `[data-density="tight"|"cozy"]` (which * override --card-space-inset on the card element) would stop reaching it. See docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`" \u2014 the same call-site rule applies to derived knobs.'},{name:"--card-space-header-y",value:"var(--space-stack-sm)",description:"Vertical padding of a BANDED header band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-body-y",value:"var(--space-section-active)",description:"Gap between the header and the body, and the body's own top padding \u2014 the breathing * room under a title before content begins."},{name:"--card-space-solo-y",value:"initial",description:'Symmetric block padding for a SOLO content slot, so a composed shell can tune block and * inline spacing independently without targeting CardContent. * Declared `initial` for the SAME reason as --card-space-shell-y above: written as * `var(--card-space-inset)` here it would freeze at the :root inset, and a card carrying * [data-density="tight"|"cozy"] (which override --card-space-inset ON THE CARD) would keep the * :root value. The default now resolves at the call site through the chain * solo-y \u2192 shell-y \u2192 inset, so a solo body follows the general block knob when only that is * set, and an explicit solo-y still wins.'},{name:"--card-space-footer-y",value:"var(--space-stack-sm)",description:"Vertical padding of a SEPARATED footer band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-divided-y",value:"var(--card-space-header-y)",description:"DIVIDED-section vertical padding (rule #44/#45). A header/footer that carries a divider * border (banded header, separated footer) reads as its own band, so it pads SYMMETRICALLY * top+bottom \u2014 distinct from a plain header that flows into the body (top inset, no bottom). * One themeable knob keeps the header- and footer-band rhythm in sync; a service theme tunes * the band density here instead of forking per-slot CSS."},{name:"--card-space-gap",value:"var(--space-stack-xs)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-size",value:"var(--font-size-base)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-line-height",value:"var(--line-height-tight)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-weight",value:"var(--font-weight-semibold)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-font-size",value:"var(--font-size-sm)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-line-height",value:"var(--line-height-normal)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-background",value:"initial",description:'Card fill + edge \u2014 opt-in knobs that DEFAULT to the live --card / --border roles. Declared * `initial` (not `var(--card)`) so the default re-resolves at the call site under a scoped theme: * a :root binding to a role var freezes at the :root value and a scoped `[data-tenant]` override of * the role never reaches it (see docs/STANDARDS-vocabulary-tokens.md \xB7 "role-mirror knobs"). A * service still overrides the knob directly (--card-background: \u2026) to win over the role default.'},{name:"--card-border",value:"initial",description:"default = hsl(var(--card))"},{name:"--card-header-background",value:"initial",description:"Banded-header fill \u2014 role-tintable (rule #45): a service points this at any role, * e.g. --card-header-background: var(--primary), and tunes --card-header-background-alpha for * the wash strength. Default = the live --muted role (resolved at the call site)."},{name:"--card-header-background-alpha",value:"0.55",description:"default = hsl(var(--muted))"},{name:"--card-header-border-bottom",value:"initial",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-radius",value:"var(--radius-xl)",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-shadow",value:"var(--shadow-sm)",description:"The DXS hi-fi baseline uses a quiet 10px data surface with one shadow-sm * elevation layer. Consumers can still flatten or lift cards through this knob."},{name:"--card-glow",value:"0 0 0 0 transparent",description:"Brand glow layer \u2014 invisible no-op at rest (rule #44). Paired AFTER --card-shadow in the * surface box-shadow so a service can wash every card with the global glow, e.g. * --card-glow: var(--shadow-glow), with no markup change."},{name:"--card-tint",value:"transparent",description:"Fill tint \u2014 subtle role wash over the card background (default transparent = invisible). * Painted as an overlay so a service sets --card-tint: hsl(var(--primary) / 0.04) once."},{name:"--card-accent-rail-width",value:"var(--stroke-2xl)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--card-accent-perimeter-width",value:"var(--stroke-hairline)",description:'Accent placement `perimeter` (gh#12) \u2014 the FULL attention border. Two knobs so a service can * tune the edge weight and the outer ring independently; the defaults reproduce the optical * weight of `variant="featured"` (1px border + 1px ring) in the card\'s own semantic accent tone * instead of --primary. Structural 1px literals, like the base card hairline.'},{name:"--card-accent-perimeter-ring-width",value:"var(--stroke-hairline)",description:'Accent placement `perimeter` (gh#12) \u2014 the FULL attention border. Two knobs so a service can * tune the edge weight and the outer ring independently; the defaults reproduce the optical * weight of `variant="featured"` (1px border + 1px ring) in the card\'s own semantic accent tone * instead of --primary. Structural 1px literals, like the base card hairline.'},{name:"--card-featured-border-color",value:"initial",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--card-featured-ring-width",value:"var(--stroke-hairline)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-label-font-size",value:"var(--font-size-xs)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-label-font-weight",value:"var(--font-weight-medium)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-label-letter-spacing",value:"0.04em",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-value-font-size",value:"var(--font-size-2xl)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-value-line-height",value:"1.1",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-value-font-weight",value:"var(--font-weight-semibold)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-hint-font-size",value:"var(--font-size-xs)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-gap",value:"var(--space-stack-xs)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-size",value:"var(--icon-size-2xl)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-glyph-size",value:"var(--icon-size-lg)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-radius",value:"var(--radius-md)",description:'`variant="featured"` edge \u2014 role-mirror knob (docs/TOKENS.md). `initial` so the --primary * default resolves at the CALL SITE and a scoped [data-tenant]/.dark override of --primary * reaches it; a service points it anywhere (e.g. var(--attention)) to retint every featured card * at once. Default = hsl(var(--primary)).'},{name:"--stat-card-icon-background",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-icon-foreground",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-delta-font-size",value:"var(--font-size-xs)",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-space-gap",value:"var(--space-stack-sm)",description:"\u2500\u2500 ServiceLauncherCard (gh#219) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Internal rhythm of a launcher tile: one gap knob drives the vertical stack AND the * icon\u2194title\u2194status row, so a service theme retunes the whole tile density once."},{name:"--card-service-launcher-icon-size",value:"calc(var(--icon-size-2xl) * var(--scaling))",description:"Semantic icon surface \u2014 36px, the `--icon-size-2xl` step, multiplied by --scaling so the * medallion still breathes with its sibling controls (the gh#328 rule: an icon that wants * density says so itself). It read `var(--control-height-lg)` until gh#324: a CONTROL tier is * the wrong axis for an icon box, and the bug was visible \u2014 `@media (pointer: coarse)` lifts * the control ladder to the 44px tap floor, so on every touch device the medallion silently * inflated to 48px while the glyph inside it stayed 20px."},{name:"--card-service-launcher-icon-glyph-size",value:"calc(var(--icon-size-lg) * var(--scaling))",description:"Semantic icon surface \u2014 36px, the `--icon-size-2xl` step, multiplied by --scaling so the * medallion still breathes with its sibling controls (the gh#328 rule: an icon that wants * density says so itself). It read `var(--control-height-lg)` until gh#324: a CONTROL tier is * the wrong axis for an icon box, and the bug was visible \u2014 `@media (pointer: coarse)` lifts * the control ladder to the 44px tap floor, so on every touch device the medallion silently * inflated to 48px while the glyph inside it stayed 20px."},{name:"--card-service-launcher-icon-radius",value:"var(--radius-md)",description:"Semantic icon surface \u2014 36px, the `--icon-size-2xl` step, multiplied by --scaling so the * medallion still breathes with its sibling controls (the gh#328 rule: an icon that wants * density says so itself). It read `var(--control-height-lg)` until gh#324: a CONTROL tier is * the wrong axis for an icon box, and the bug was visible \u2014 `@media (pointer: coarse)` lifts * the control ladder to the 44px tap floor, so on every touch device the medallion silently * inflated to 48px while the glyph inside it stayed 20px."},{name:"--card-service-launcher-icon-background",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-icon-foreground",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-unavailable-icon-background",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-unavailable-icon-foreground",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-description-font-size",value:"var(--font-size-xs)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-description-line-height",value:"var(--line-height-body)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-metadata-font-size",value:"var(--font-size-xs)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-metadata-line-height",value:"var(--line-height-normal)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-cta-min-height",value:"calc(var(--control-height-lg) * 4)",description:"Dashed catalog/add companion tile: kept tall enough to sit level with a populated launcher * tile in the same grid row. Four control tiers \u2248 medallion + title + description + action."},{name:"--card-service-launcher-skeleton-title-width",value:"var(--space-12)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--card-service-launcher-skeleton-status-width",value:"var(--space-10)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--chart-trend-plot-height-xs",value:"3.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-sm",value:"5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-md",value:"7.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-lg",value:"10rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height",value:"var(--chart-trend-plot-height-xs)",description:"Active tier \u2014 chart-layout.css re-points this per `data-size`; overriding it directly * pins one height for every trend regardless of `size`."},{name:"--chart-trend-bar-gap",value:"var(--space-2)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-radius",value:"var(--radius-sm)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-max-width",value:"1.5rem",description:"Ceiling on a single mark so a 3-point trend does not render slabs."},{name:"--chart-trend-bar-min-height",value:"var(--stroke-md)",description:"Floor so a zero/absent value still reads as a plotted category rather than a hole."},{name:"--chart-trend-bar-background",value:"initial",description:'Muted mark fill \u2014 role-mirror knob, declared `initial` so the role default re-resolves * at the CALL SITE under `.dark` / `[data-tenant]` (docs/TOKENS.md \xB7 "Role-mirror knobs * MUST be `initial`"). Default = hsl(var(--muted-foreground) / 0.75), which clears the * 3:1 non-text contrast floor (WCAG 1.4.11) against the card surface.'},{name:"--chart-trend-bar-background-alpha",value:"0.75",description:"Opacity applied to the muted-fill role default."},{name:"--chart-trend-bar-emphasis-background",value:"initial",description:'Emphasized ("current") mark fill \u2014 role-mirror knob. Default = hsl(var(--primary)).'},{name:"--chart-trend-baseline-border",value:"initial",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-gap",value:"var(--space-1)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-font-size",value:"var(--font-size-xs)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-footer-gap",value:"var(--space-2)",description:"Gap between the plot block and the optional activity footer slot."},{name:"--control-height-compact",value:"var(--band-height-sm)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-default",value:"var(--band-height-md)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-comfortable",value:"var(--band-height-xl)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-compact",value:"var(--space-2)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-default",value:"var(--space-3)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-comfortable",value:"var(--space-4)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height",value:"calc(var(--control-height-default) * var(--scaling))",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-sm",value:"calc(var(--control-height) - calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-lg",value:"calc(var(--control-height) + calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-xs",value:"calc(var(--control-height) - calc(0.5rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-padding-x",value:"var(--control-padding-x-default)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap",value:"var(--space-inline-sm)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap-sm",value:"var(--space-inline-xs)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-radius",value:"var(--radius)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--button-radius",value:"var(--radius-md)",description:"Button corner radius \u2014 defaults to the button's historical `rounded-md` so nothing * changes by default, but is its OWN knob so a service theme can retune the button * radius INDEPENDENTLY of input/control radius (issue #124)."},{name:"--control-font-size",value:"var(--font-size-base)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-border-width",value:"var(--stroke-hairline)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-shadow",value:"var(--shadow-xs)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size",value:"calc(var(--icon-size-md) * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size-sm",value:"calc(var(--icon-size-sm) * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--toggle-focus-ring-width",value:"var(--stroke-lg)",description:"Ring knobs for the two controls that need a softer, heavier mark than the * global default \u2014 both are filled surfaces where a hard 2px ring reads as a * second border. Values preserve the look these controls always had; they are * knobs now instead of hand-written box-shadows (styles/focus-ring.css)."},{name:"--toggle-focus-ring-alpha",value:"0.35",description:"Ring knobs for the two controls that need a softer, heavier mark than the * global default \u2014 both are filled surfaces where a hard 2px ring reads as a * second border. Values preserve the look these controls always had; they are * knobs now instead of hand-written box-shadows (styles/focus-ring.css)."},{name:"--rating-focus-ring-offset",value:"2px",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--checkbox-size",value:"calc(1rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--checkbox-size-compact",value:"0.875rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--checkbox-size-comfortable",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-gap",value:"var(--space-inline-sm)",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-group-gap-x",value:"var(--space-6)",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-group-gap-y",value:"var(--space-3)",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-description-gap",value:"0.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--choice-control-offset",value:"0.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-width",value:"calc(2.25rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-width-compact",value:"2rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-width-comfortable",value:"2.5rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-height",value:"calc(1.25rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-height-compact",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-height-comfortable",value:"1.375rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-size",value:"calc(1rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-size-compact",value:"0.875rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-size-comfortable",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-translate",value:"calc(1rem * var(--scaling))",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-translate-compact",value:"0.875rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--switch-thumb-translate-comfortable",value:"1.125rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--slider-track-height",value:"0.375rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--slider-thumb-size",value:"1rem",description:"Outline-form gaps \u2014 marks where a radius-hugging ring would touch the glyph."},{name:"--otp-slot-size",value:"initial",description:'InputOTP slot box (gh#233). Its own knob so an auth surface can widen the 6-slot challenge row * to fill a wide panel WITHOUT re-scoping --control-height (which would resize every other * control in the same card). Declared `initial` \u2014 the tier-mirror form of the role-mirror rule in * docs/TOKENS.md: the default must resolve at the CALL SITE (`var(--otp-slot-size, * var(--control-height))`), because `--otp-slot-size: var(--control-height)` here would FREEZE at * the :root tier (32px) and an OTP row inside `.ui-auth-shell[data-variant="canonical"]`, which * re-scopes --control-height to 36px, would silently shrink. Verified in Chromium: 36px before * and after. A service opts in with a NAMED tier (`var(--control-height-lg)`), never a calc.'},{name:"--otp-slot-inline-size",value:"initial",description:'Per-AXIS overrides of the slot box (gh#12). --otp-slot-size stays the square shorthand; these * two win over it when set, so a code field can be WIDER-than-tall or TALLER-than-wide without * abandoning the token. A canonical device-grant code field is 27.5x52 per slot (a 4-slot * `appearance="grouped"` box of 112x54 with its 1px group border) \u2014 not expressible with one * square knob, which is why grouped OTP measured 146x38 against that artboard. `initial` for the * same tier-mirror reason as --otp-slot-size: the whole fallback chain * (axis \u2192 square \u2192 --control-height) must resolve at the CALL SITE so a shell that re-scopes * --control-height still reaches an OTP row that sets neither axis.'},{name:"--otp-slot-block-size",value:"initial",description:"default = var(--otp-slot-size, var(--control-height))"},{name:"--otp-container-align",value:"flex-start",description:'Main-axis alignment of the whole OTP row (rule #44/#45 \u2014 chrome is a token, default quiet). * `flex-start` is the historical behaviour, so an existing field is byte-identical; a centred * code field is `align="center"` on InputOTP (which wins over this knob) or this token set once * in a service theme. It exists because EVERY consumer was wrapping .ui-otp-container in a * flex-centring div to get a centred challenge.'},{name:"--checkbox-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-on-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-track-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-range-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-unchecked-background",value:"initial",description:'Switch OFF track fill (gh#315) \u2014 `initial`, default hsl(var(--input)). Its own knob so a * service can quieten the off-track without dragging the --input control-boundary role back * below the WCAG SC 1.4.11 floor (rule #45); whatever you set still owes 3:1 against the page * and against the thumb (--background), or "off" stops being a visible state.'},{name:"--color-picker-input-width",value:"6.5rem",description:"ColorPicker \u2014 width of the hex text field beside the swatch."},{name:"--command-list-max-height",value:"min(300px, 50vh)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-input-padding-x",value:"var(--space-3)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-group-padding",value:"var(--space-1)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-item-padding-y",value:"var(--space-2)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-item-padding-x",value:"var(--space-2)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-width",value:"35rem",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-inset-top",value:"calc(var(--space-6) * 4)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-viewport-inset",value:"var(--space-3)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-hint-padding-y",value:"var(--space-2)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-hint-padding-x",value:"var(--space-4)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--command-palette-hint-gap",value:"var(--space-4)",description:"Command / CommandPalette \u2014 list height, inner paddings and the palette's own box."},{name:"--search-input-edge-inset",value:"var(--space-3)",description:"SearchInput \u2014 the inset the leading/trailing icons sit at, and the field padding derived * from it so the text never runs under an icon."},{name:"--search-input-start-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:"SearchInput \u2014 the inset the leading/trailing icons sit at, and the field padding derived * from it so the text never runs under an icon."},{name:"--search-input-end-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:"SearchInput \u2014 the inset the leading/trailing icons sit at, and the field padding derived * from it so the text never runs under an icon."},{name:"--choice-description-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--color-picker-hex-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--command-group-heading-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--search-input-label-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--tag-input-chip-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--toggle-sm-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--button-sm-font-size",value:"var(--font-size-xs)",description:"The --font-size-xs tier, spelled once per control that reads it, so a service can retune a * single one of them without moving the whole scale."},{name:"--control-affix-inset-inline-end",value:"var(--space-2)",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-action-size",value:"1.5rem",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-action-radius",value:"var(--radius-sm)",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-icon-size",value:"var(--icon-size-md)",description:"TRAILING AFFIX \u2014 the clear \u2715 / chevron overlay a select-family trigger parks at its inline * end. Select, SearchSelect and TagInput each hard-coded the same `end-2 size-6 rounded-sm * opacity-50` stack independently (#319), so a service retuning affix weight had to chase the * literal through several components. One shared set of knobs instead \u2014 this is a CONTROL-level * concern, not a per-component one."},{name:"--control-affix-rest-alpha",value:"0.5",description:"Resting weight of an affix glyph \u2014 it must read as secondary until hovered/focused."},{name:"--control-trigger-space-inline-end",value:"2.25rem",description:"2.25rem \u2014 the room a single trailing affix needs. Written as a raw rem because the * literal it replaced (`pe-9`) is a flat Tailwind step, not a density-scaled one."},{name:"--control-inline-affix-size",value:"1.25rem",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-icon-size",value:"var(--control-affix-icon-size)",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-space-gap",value:"var(--space-1)",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-inset-inline",value:"var(--space-2)",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-rest-alpha",value:"0.7",description:"INSIDE-FIELD AFFIX \u2014 the leading/trailing controls Input and TimePicker render WITHIN the * field box, as opposed to the overlay affix (--control-affix-*) that Select and SearchSelect * park on top of a trigger. Both carried the identical literal stack (#319). Because these sit * on the field's own surface rather than over it, they rest a touch heavier than the overlay * pair \u2014 that difference is the reason the two sets stay separate."},{name:"--control-inline-affix-space-inline-end",value:"2.25rem",description:"Inline room the field reserves so its text never runs under a single affix."},{name:"--textarea-clear-inset-block-start",value:"var(--space-2)",description:"Input centres its affix on the field's single line; a textarea has no single line to centre * on, so its clear control parks at the top-end corner instead."},{name:"--control-multiline-padding-block",value:"var(--space-2)",description:"MULTILINE BOX \u2014 the block padding of `.ui-control-multiline` (Textarea). It was a bare * `--space-2` inside the padding shorthand, which made it unreachable for a service that runs a * denser composer, AND it is the constant the auto-grow row maths has to add back, so the two * had to read the same knob or drift (#45)."},{name:"--textarea-autogrow-line-height",value:"initial",description:'The unit a "row" is measured in. `initial` on purpose: the call site reads * `var(--textarea-autogrow-line-height, var(--line-height-normal))`, so a service that retunes * the multiline line-height for CJK legibility retunes the ceiling with it (#46). Unitless \u2014 * it is multiplied by `--control-font-size`.'},{name:"--textarea-autogrow-min-height-rows",value:"1",description:"Floor of the grown box, in text rows. The `minRows` prop overrides per instance \u2014 the same * theme-global / prop-local priority as `--form-label-width` vs `labelWidth`. The floor is * additionally clamped up to `--control-height` so a resting one-row composer still lines up * with the Input / Button beside it instead of undercutting the control tier."},{name:"--textarea-autogrow-max-height-rows",value:"8",description:"Ceiling of the grown box, in text rows; past it the control scrolls internally instead of * pushing the page. `maxRows={0}` sets this to `infinity` for an unbounded box."},{name:"--textarea-autogrow-box-inset",value:"calc( (var(--control-multiline-padding-block) + var(--control-border-width)) * 2 )",description:"The non-text part of the box the row maths must add back: block padding + border, both * edges. `.ui-textarea-autogrow--ghost` re-declares it without the border, because the ghost * variant drops its own chrome."},{name:"--control-inline-affix-pair-space-inline-end",value:"3.5rem",description:"A field parking TWO inline affixes (TimePicker's clear+clock, DatePicker's clear+calendar) * reserves a wider end than the single-affix case."},{name:"--control-composite-field-space-gap",value:"var(--space-2)",description:"COMPOSITE FIELD \u2014 the bordered box that wraps two inputs plus a separator (date/month * range pickers) or one input plus affixes. Shared so the four range/picker fields cannot * drift into four slightly different boxes."},{name:"--input-file-button-height",value:"var(--band-height-sm)",description:"The <input type=file> button is a browser-owned box we restyle; it sits one tier below the * field so it reads as a control INSIDE the control."},{name:"--input-file-button-font-size",value:"var(--font-size-sm)",description:"The <input type=file> button is a browser-owned box we restyle; it sits one tier below the * field so it reads as a control INSIDE the control."},{name:"--search-select-panel-max-width",value:"32rem",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-panel-viewport-inset",value:"var(--space-6)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-list-space-inset",value:"var(--space-1)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-footer-space-inset",value:"var(--space-1)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-option-space-gap",value:"var(--space-2)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-option-font-size",value:"var(--font-size-sm)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-option-sublabel-font-size",value:"var(--font-size-xs)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-status-space-inline",value:"var(--space-2)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-status-space-block",value:"var(--space-3)",description:"SEARCH-SELECT \u2014 the Popover+Command combobox. Its panel geometry was baked onto the * component as arbitrary values (`max-w-[min(32rem,calc(100vw-1.5rem))]`), so a service could * not widen the panel or change the viewport gutter without forking."},{name:"--search-select-placeholder-space-block",value:"var(--space-6)",description:"Empty/error states sit alone in the panel, so they get a taller, centred block."},{name:"--search-select-status-font-size",value:"var(--font-size-sm)",description:"Empty/error states sit alone in the panel, so they get a taller, centred block."},{name:"--search-select-spinner-size",value:"1rem",description:"Empty/error states sit alone in the panel, so they get a taller, centred block."},{name:"--cascader-column-min-width",value:"9rem",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-columns-max-height",value:"min(280px, 50vh)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-list-max-height",value:"min(300px, 50vh)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-panel-space-inset",value:"var(--space-1)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-space-inline",value:"var(--space-3)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-space-block",value:"var(--space-2)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-space-gap",value:"var(--space-1)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-font-size",value:"var(--font-size-sm)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-option-icon-size",value:"var(--icon-size-md)",description:"CASCADER \u2014 column browser + flat search list. Its option rows, column widths and panel * paddings were literals on the component (#319), so a service could not widen a column to fit * longer JA labels or tighten the row rhythm without forking."},{name:"--cascader-result-space-inline",value:"var(--space-2)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-result-space-block",value:"0.375rem",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-result-radius",value:"var(--radius-sm)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-result-icon-space-inline-end",value:"var(--space-2)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--cascader-empty-space-block",value:"var(--space-6)",description:"The flat search result row is denser than a column row \u2014 it has no chevron to balance."},{name:"--select-content-max-height",value:"24rem",description:"SELECT \u2014 the Radix listbox surface. Its popup shares the menu row rhythm (--menu-item-*), * so only what is genuinely its own lives here (#319)."},{name:"--select-scroll-button-space-block",value:"var(--space-1)",description:"SELECT \u2014 the Radix listbox surface. Its popup shares the menu row rhythm (--menu-item-*), * so only what is genuinely its own lives here (#319)."},{name:"--select-item-space-inline",value:"0.625rem",description:"Select rows sit slightly wider than a menu row: a listbox has no leading icon column to * absorb the optical inset, so 2.5 reads level with a menu's 2 + icon."},{name:"--time-picker-column-height",value:"13rem",description:"TIME PICKER \u2014 scroll columns of hour/minute/second plus an inline affix pair. All of its * geometry was literal on the component (#319): a service could not shorten the column, widen * the panel for a 12-hour layout, or retune the row rhythm without forking."},{name:"--time-picker-panel-width",value:"9rem",description:"TIME PICKER \u2014 scroll columns of hour/minute/second plus an inline affix pair. All of its * geometry was literal on the component (#319): a service could not shorten the column, widen * the panel for a 12-hour layout, or retune the row rhythm without forking."},{name:"--time-picker-panel-width-12h",value:"13rem",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-heading-space-inline",value:"var(--space-1)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-heading-space-block",value:"0.375rem",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-heading-font-size",value:"var(--font-size-xs)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-column-space-inset",value:"var(--space-1)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-option-space-block",value:"0.375rem",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-option-radius",value:"var(--radius-md)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-option-font-size",value:"var(--font-size-sm)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-footer-space-inset",value:"var(--space-2)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--time-picker-affix-space-gap",value:"var(--control-inline-affix-space-gap)",description:"12-hour mode adds an AM/PM column, so the panel is wider."},{name:"--tree-select-list-max-height",value:"min(300px, 50vh)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-space-block",value:"0.375rem",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-space-inline-end",value:"var(--space-2)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-radius",value:"var(--radius-sm)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-row-font-size",value:"var(--font-size-sm)",description:"TREE SELECT \u2014 a Select whose panel holds a tree. Its rows, indent and toggle were literal * on the component, and the depth indent was a magic expression in JSX * (`depth * 1.25 + 0.5` rem) that no theme could reach at all (#319)."},{name:"--tree-select-depth-space-base",value:"var(--space-2)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-depth-space-step",value:"1.25rem",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-toggle-size",value:"1.25rem",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-toggle-space-inline-end",value:"var(--space-1)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-label-space-gap",value:"var(--space-2)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--tree-select-empty-space-block",value:"var(--space-6)",description:"Indent = base + depth \xD7 step. A denser service dials the step down without touching JSX."},{name:"--calendar-space-inset",value:"var(--space-3)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-month-space-gap",value:"var(--space-4)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-caption-space-inline",value:"var(--space-8)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-caption-font-size",value:"var(--font-size-sm)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-nav-space-block-start",value:"var(--space-3)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-nav-space-inline",value:"var(--space-1)",description:"CALENDAR \u2014 the react-day-picker grid. Day/weekday cells already size from --control-height * (the system control tier), but the frame around them \u2014 root inset, month gaps, nav offset, * grid rhythm \u2014 was literal on the component (#319), so a service could not tighten the * calendar to its own density without forking the classNames map."},{name:"--calendar-nav-rest-alpha",value:"0.7",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-grid-space-block-start",value:"var(--space-4)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-week-space-block-start",value:"var(--space-2)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-weekday-font-size",value:"var(--font-size-xs)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-weekday-radius",value:"var(--radius-md)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-day-font-size",value:"var(--font-size-sm)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-day-radius",value:"var(--radius-md)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--calendar-chevron-size",value:"var(--icon-size-md)",description:"Nav chevrons read as secondary until hovered \u2014 they frame the month, they are not the point."},{name:"--transfer-pane-min-height",value:"14rem",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-pane-radius",value:"var(--radius-md)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-panes-space-gap",value:"var(--space-3)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-space-inline",value:"var(--space-3)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-space-block",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-font-size",value:"var(--font-size-sm)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-header-space-gap",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-search-space-inset",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-list-space-inset",value:"var(--space-1)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-space-inline",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-space-block",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-space-gap",value:"var(--space-2)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-radius",value:"var(--radius-sm)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-font-size",value:"var(--font-size-sm)",description:"TRANSFER \u2014 the two-pane list mover. Pane height, header rhythm and row density were literal * on the component (#319), so a service could not fit the panes to its own page grid or * tighten the row for a dense admin table without forking."},{name:"--transfer-row-check-space-block-start",value:"0.125rem",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-row-description-font-size",value:"var(--font-size-xs)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-empty-space-block",value:"var(--space-8)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-actions-space-gap",value:"var(--space-2)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--transfer-action-icon-size",value:"var(--icon-size-md)",description:"The checkbox is top-aligned against a two-line row, so it needs a hair of optical offset to * sit level with the first line's cap height rather than its box."},{name:"--month-picker-panel-space-inset",value:"var(--space-3)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-grid-space-block-start",value:"var(--space-3)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-grid-space-gap",value:"var(--space-1)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-cell-space-inline",value:"var(--space-4)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-nav-font-size",value:"var(--font-size-sm)",description:"MONTH PICKER \u2014 shared by MonthPicker and MonthRangePicker, which render the same year-nav + * 3-column month grid. Both carried an identical copy of every literal (#319); one set of knobs * now drives both, so they cannot drift apart the way Select and DropdownMenu once did."},{name:"--month-picker-nav-rest-alpha",value:"var(--calendar-nav-rest-alpha)",description:"Year-nav chevrons read as secondary until hovered \u2014 same treatment as the Calendar nav, so * the two panels stay visually consistent when either is retuned."},{name:"--month-picker-icon-size",value:"var(--icon-size-md)",description:"Year-nav chevrons read as secondary until hovered \u2014 same treatment as the Calendar nav, so * the two panels stay visually consistent when either is retuned."},{name:"--month-picker-separator-icon-size",value:"var(--icon-size-sm)",description:"Year-nav chevrons read as secondary until hovered \u2014 same treatment as the Calendar nav, so * the two panels stay visually consistent when either is retuned."},{name:"--button-xs-height",value:"var(--control-height-xs)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-space-inline",value:"var(--space-2)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-space-gap",value:"var(--space-1)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-font-size",value:"var(--font-size-xs)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-xs-icon-size",value:"var(--icon-size-xs)",description:'BUTTON size-xs box + the count pill (#319, reconciled in #316). * * `size="xs"` now reads the tier, like every other control in the library. It used to be * `calc(var(--control-height) - 0.75rem)` = 1.25rem, which sat 4px BELOW `--control-height-xs` * (1.5rem) that `size="icon-xs"` already used \u2014 the same tier name, two heights, so an xs * Button never lined up with the xs control beside it. It was also the exact shape this repo\'s * own rule forbids: an ad-hoc `calc(var(--control-height) \xB1 length)` silently re-derives a tier * and drifts from its siblings (the bug that made Pagination\'s size-changer taller than its * page buttons), and being a raw length rather than a `--scaling`-multiplied step, it did not * move with density while the tier did. * * What settled it was not consistency but MEASUREMENT: an xs Button rendered 20px tall, under * the 24x24 CSS px that WCAG 2.2 SC 2.5.8 (Target Size, Minimum) requires, while icon-xs beside * it rendered 24px and passed. So this is an accessibility fix that happens to also remove an * inconsistency, not a visual preference. It is a real visual change on the most-used component * in the library (20px -> 24px), and it stays a knob: a service that wants the old box sets * --button-xs-height back.'},{name:"--button-icon-space-inline-md",value:"var(--space-3)",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-icon-space-inline-sm",value:"0.625rem",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-icon-space-inline-lg",value:"var(--space-4)",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-icon-space-inline-xs",value:"0.375rem",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--button-space-block",value:"var(--space-2)",description:"Icon-adjacent padding: a button whose only child is an icon needs less inline room than one * carrying a label, or the glyph floats in a too-wide box. One knob per size tier."},{name:"--otp-caret-block-size",value:"1rem",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--otp-caret-inline-size",value:"var(--stroke-hairline)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--otp-separator-icon-size",value:"var(--icon-size-md)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-min-width",value:"var(--space-4)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-space-inline",value:"var(--space-1)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-font-size",value:"var(--font-size-xs)",description:`The OTP blinking caret. The ratchet catches it because the selector says "caret", but it is a * text CURSOR, not a chevron: a 1px bar as tall as the digit's line box. Its width belongs to * the stroke scale; its height belongs to neither the icon nor the band scale, so it is declared * rather than forced onto one. * scale-exempt: text-caret height, tracks the OTP digit's line box, not an icon or a band`},{name:"--button-count-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-default-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-default-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-destructive-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-destructive-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-secondary-background",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--button-count-secondary-color",value:"initial",description:"Count pill \xB7 COLOUR (gh#320). The pill used to tint itself translucently over whatever the * button's surface happened to be (`bg-primary-foreground/15`, `bg-foreground/8`), which means * its contrast was a FUNCTION of that surface \u2014 and five of those combinations measured below * the 4.5:1 that WCAG 2.2 SC 1.4.3 requires of small text: * * default (light) 3.88 \xB7 destructive (dark) 4.29 * outline family (light, at rest) 4.32 \xB7 outline family (light, hover) 3.64 * outline family (dark, hover) 3.68 * * The hover rows are the reason a static sweep never caught this: `--accent` only appears under * the cursor, so no screenshot of a resting page contains the failing state. Same shape as * gh#299 and gh#315 \u2014 a value that passes on the surface someone happened to check. * * The fix is the one gh#312 already validated on Toggle: OPAQUE role fills, which make the pill's * contrast independent of the button's variant AND of hover. Each filled variant uses its own * label pair SWAPPED, so the pill is exactly as legible as the label beside it and cannot be less * legible under any theme that keeps that button readable at all: * * default --primary on --primary-foreground 5.04 light \xB7 7.07 dark * destructive --destructive on --destructive-foreground 6.10 light \xB7 5.53 dark * secondary --secondary on --secondary-foreground 14.25 light \xB7 12.40 dark * outline family --foreground on --muted 14.25 light \xB7 12.40 dark * * The outline family's `--muted` fill is 1.09:1 against the button's own ground, so at rest the * counter still reads as quiet text rather than a badge (#44) \u2014 it is legibility of the DIGITS * that went up, not loudness of the pill. * * All eight are role-mirror knobs: `initial` at :root with the role default at the CALL SITE, so * a scoped [data-tenant]/.dark override of the role still reaches them (docs/TOKENS.md \xB7 * \"Role-mirror knobs MUST be `initial`\")."},{name:"--control-label-font-size",value:"var(--font-size-sm)",description:"LABEL \u2014 the Label primitive's own box and type (#319). Every one of these was a Tailwind * literal baked into label.tsx (`text-sm leading-none flex items-center gap-2`), so a service * whose form grid wanted a smaller/denser label had to hand-write font-size on every call site * (which is exactly why --form-label-font-size had to be invented as a per-instance escape). * They live under the `control` prefix because Label is a control-family primitive and * src/tokens/components/ has no `label` file of its own. * * Defaults reproduce today's rendering exactly: * font-size \u2190 `text-sm` \u2192 --text-sm \u2192 var(--font-size-sm) * line-height \u2190 `leading-none` \u2192 1 (leading-none beats text-sm's companion line-height)"},{name:"--control-label-line-height",value:"1",description:"LABEL \u2014 the Label primitive's own box and type (#319). Every one of these was a Tailwind * literal baked into label.tsx (`text-sm leading-none flex items-center gap-2`), so a service * whose form grid wanted a smaller/denser label had to hand-write font-size on every call site * (which is exactly why --form-label-font-size had to be invented as a per-instance escape). * They live under the `control` prefix because Label is a control-family primitive and * src/tokens/components/ has no `label` file of its own. * * Defaults reproduce today's rendering exactly: * font-size \u2190 `text-sm` \u2192 --text-sm \u2192 var(--font-size-sm) * line-height \u2190 `leading-none` \u2192 1 (leading-none beats text-sm's companion line-height)"},{name:"--control-label-space-gap",value:"0.5rem",description:"Label \u2192 addon/required-marker gap. A RAW rem, deliberately NOT var(--space-2): the `gap-2` it * replaces reads Tailwind's own --spacing grid, which this repo does NOT density-scale, so a * --space-* default would silently resize every label row under a compact/comfortable density. * A service that WANTS it to follow density points the knob at var(--space-2) itself."},{name:"--control-label-disabled-alpha",value:"0.7",description:"Weight of a label whose `peer` control is disabled. Deliberately its OWN knob rather than the * global --disabled-opacity (0.5): a label must stay READABLE next to a disabled control \u2014 it is * still the field's accessible name \u2014 so it rests one step heavier than the control it names. * Default = the historical `peer-disabled:opacity-70`."},{name:"--control-height-compact",value:"var(--band-height-xl)",description:"Weight of a label whose `peer` control is disabled. Deliberately its OWN knob rather than the * global --disabled-opacity (0.5): a label must stay READABLE next to a disabled control \u2014 it is * still the field's accessible name \u2014 so it rests one step heavier than the control it names. * Default = the historical `peer-disabled:opacity-70`."},{name:"--control-height-default",value:"var(--band-height-xl)",description:"Weight of a label whose `peer` control is disabled. Deliberately its OWN knob rather than the * global --disabled-opacity (0.5): a label must stay READABLE next to a disabled control \u2014 it is * still the field's accessible name \u2014 so it rests one step heavier than the control it names. * Default = the historical `peer-disabled:opacity-70`."},{name:"--accordion-chevron-size",value:"var(--icon-size-md)",description:'Accordion chevron and Carousel arrow. Both were bare 1rem literals in * data-display-layout.css \u2014 glyphs with no tier-2 route, invisible to the icon ratchet because * neither selector contains the word "icon".'},{name:"--carousel-arrow-icon-size",value:"var(--icon-size-md)",description:'Accordion chevron and Carousel arrow. Both were bare 1rem literals in * data-display-layout.css \u2014 glyphs with no tier-2 route, invisible to the icon ratchet because * neither selector contains the word "icon".'},{name:"--accordion-focus-ring-offset",value:"2px",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--carousel-dot-focus-ring-offset",value:"2px",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--progress-label-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--tree-item-title-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--tree-item-description-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--timeline-note-font-size",value:"var(--font-size-xs)",description:"Outline-form ring gaps \u2014 a hugging ring would touch these marks * (styles/focus-ring.css)."},{name:"--avatar-background",value:"initial",description:"Avatar surface \u2014 `initial` so the --muted default re-resolves at the call site under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override never reaches it). A service re-tints the placeholder fill once (e.g. --avatar-background: hsl(var(--accent))). Default = hsl(var(--muted))."},{name:"--avatar-tint",value:"transparent",description:"Optional role wash over the avatar (default transparent = invisible, rule #44). Painted as an overlay so a service sets --avatar-tint: hsl(var(--primary) / 0.08)."},{name:"--avatar-square-radius",value:"var(--radius-lg)",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-size",value:"var(--control-height)",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-background",value:"initial",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-square-foreground",value:"initial",description:'Entity-header mark \u2014 `<Avatar shape="square">` (gh#249): the compact rounded SQUARE an organization/service header uses, as opposed to the round person avatar. Every value is a knob (rule #45) so a service matches the mark to its own grid without a className override. --avatar-square-background / --avatar-square-foreground are role-mirror knobs: `initial` so the --primary / --primary-foreground defaults re-resolve at the CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding to a role var would freeze at the :root value). Defaults = --radius-lg corners \xB7 --control-height box (same as the circle avatar, so swapping shape never reflows a header) \xB7 hsl(var(--primary)) fill \xB7 hsl(var(--primary-foreground)) glyph.'},{name:"--avatar-tinted-background",value:"initial",description:'Capability medallion \u2014 `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability / feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square" for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override of --primary reaches them \u2014 the exact reason this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page CSS. Defaults = hsl(var(--primary) / 0.1) wash \xB7 hsl(var(--primary)) glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-tinted-foreground",value:"initial",description:'Capability medallion \u2014 `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability / feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square" for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override of --primary reaches them \u2014 the exact reason this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page CSS. Defaults = hsl(var(--primary) / 0.1) wash \xB7 hsl(var(--primary)) glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-tinted-glyph-size",value:"var(--control-icon-size)",description:'Capability medallion \u2014 `<Avatar appearance="tinted">` (gh#12): the tinted plate a capability / feature glyph sits on, as opposed to the SOLID entity mark above. Pair it with shape="square" for the canonical rounded square. --avatar-tinted-background / --avatar-tinted-foreground are role-mirror knobs (`initial`, docs/TOKENS.md) so the --primary defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override of --primary reaches them \u2014 the exact reason this is a token and not the `hsl(var(--primary) / 0.1)` literal consumers were writing in page CSS. Defaults = hsl(var(--primary) / 0.1) wash \xB7 hsl(var(--primary)) glyph \xB7 --control-icon-size glyph box.'},{name:"--avatar-presence-size",value:"30%",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-min-size",value:"var(--space-2)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-inset",value:"0",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-ring-width",value:"var(--stroke-md)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-stroke-width",value:"var(--stroke-sm)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-bar-inline-size",value:"56%",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-bar-block-size",value:"var(--stroke-sm)",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-ring-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-online-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-away-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-busy-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--avatar-presence-offline-color",value:"initial",description:'PRESENCE INDICATOR \u2014 `<Avatar presence="online|away|busy|offline">` (gh#309): the realtime reachability dot pinned to the block-end/inline-end corner of the mark. Every constant below is a knob (rule #45) and the separator ring is chrome that reads a token (rule #44), because the hand-rolled workaround this replaces \u2014 `bg-green-500 ring-2 ring-background -end-0.5 -bottom-0.5` on a wrapper span \u2014 bakes four service-tunable constants and a raw palette colour into a consumer page. --avatar-presence-size is a PROPORTION of the mark, not a px step. One value therefore tracks EVERY avatar the system paints \u2014 the --control-height default box, a `size-12` call site, the --avatar-square-size entity mark, --org-switcher-avatar-size (1.75rem), --upload-avatar-size (6rem), the 36px ListRow density="compact" leading mark \u2014 instead of freezing one diameter that is a boulder on a 24px mark and a speck on a 96px one. --avatar-presence-min-size is the legibility floor for the smallest marks; --avatar-presence-inset lets a service pull the dot further into a round mark than into a square one. --avatar-presence-ring-color and the four state colours are ROLE-MIRROR knobs: declared `initial` here so the --background / --success / --warning / --destructive / --muted-foreground defaults resolve at the CALL SITE and a scoped [data-tenant] / .dark override of the ROLE still reaches them (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). Binding them to the role here would freeze the ring light-mode-white on a dark avatar. Presence is never colour-alone (WCAG 1.4.1): the colour rides with a SHAPE \u2014 filled \xB7 half filled \xB7 barred \xB7 hollow \u2014 and a localized sr-only string, so --avatar-presence-stroke-width and --avatar-presence-bar-* are part of the accessible encoding, not decoration. Defaults = 30% of the mark, floor 0.5rem \xB7 flush with the mark\'s corner \xB7 2px separator ring in --background \xB7 1.5px state stroke \xB7 a 56%-wide, 1.5px dnd bar.'},{name:"--progress-track-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--progress-fill-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--timeline-dot-done-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-dot-current-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-line-completed-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--tree-item-active-border",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--tree-item-active-background",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--qr-code-foreground",value:"0 0% 0%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-background",value:"0 0% 100%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-xs",value:"6rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-sm",value:"8rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-md",value:"10rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-lg",value:"12.5rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--scroll-area-bar-size",value:"0.625rem",description:"SCROLL AREA \u2014 the custom scrollbar rail. `.ui-scroll-area-*` did not exist at all: the whole * rail shipped as Tailwind literals on the component (`w-2.5 p-px` / `h-2.5 p-px`, * `rounded-full`), so a service could not widen the bar for a touch/coarse-pointer console or * square the thumb to match a sharp-cornered theme without forking ScrollArea (rule #45). * --scroll-area-bar-size is a RAW rem on purpose: it replaces a FLAT Tailwind step (2.5 = 10px), * not a --space-* step, so binding it to the density-scaled scale would silently change today's * rendering under a non-default --scaling. Defaults reproduce the previous rail exactly."},{name:"--scroll-area-bar-padding",value:"1px",description:"Hairline inset that keeps the thumb off the rail edge \u2014 a service running a thicker bar * usually wants a proportionally larger gutter here."},{name:"--scroll-area-thumb-radius",value:"var(--radius-pill)",description:"Hairline inset that keeps the thumb off the rail edge \u2014 a service running a thicker bar * usually wants a proportionally larger gutter here."},{name:"--permission-matrix-label-width",value:"16rem",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-role-space-gap",value:"var(--space-1)",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-name-space-gap",value:"0.375rem",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-cell-icon-size",value:"var(--control-icon-size)",description:"PERMISSION MATRIX \u2014 the sticky-first-column grid. Nothing here was themeable: the class * `.ui-permission-matrix` existed as a bare hook with no CSS rule at all, so every constant * lived on the component (#319). A JA/VI service whose role names run longer than the English * ones could not widen the label column without forking."},{name:"--permission-matrix-min-width",value:"48rem",description:"Floor for the scrolling body \u2014 below this the columns collapse into unreadable slivers, so * the table scrolls instead. Mirrors --table-surface-min-inline-size on DataTable."},{name:"--password-strength-score-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--password-strength-checklist-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--branch-scope-picker-gap",value:"0.75rem",description:"Stack rhythm between the mode radios, the subset box and the error line (was `gap-3`)."},{name:"--branch-scope-picker-badges-gap",value:"0.375rem",description:"Read-only summary: the wrapped run of branch Badges (was `gap-1.5`)."},{name:"--branch-scope-picker-subset-gap",value:"0.5rem",description:'The `mode="selected"` subset box \u2014 its inner stack rhythm and the inset that clears its * indent rule (was `gap-2` / `ps-4`).'},{name:"--branch-scope-picker-subset-padding-inline",value:"1rem",description:'The `mode="selected"` subset box \u2014 its inner stack rhythm and the inset that clears its * indent rule (was `gap-2` / `ps-4`).'},{name:"--branch-scope-picker-subset-border-width",value:"var(--stroke-md)",description:"The indent rule itself is CHROME (#44), so it is a knob: a service that wants the flat * treatment sets it to 0 rather than forking the component. Default = today's `border-s-2`."},{name:"--branch-scope-picker-list-max-height",value:"16rem",description:"Bounded height of the scrollable branch list (was `max-h-64`). Flat by design: the box is a * keyboard-reachable scroll region whose job is to cap the control's measure, so it must not * grow with density and push the surrounding form off a 390px frame."},{name:"--descriptions-label-width",value:"8rem",description:'Width of the label column when <Descriptions layout="horizontal">. Labels align to this * shared column so the values line up (the horizontal-detail look, mirroring <Form layout>). * A rem value gives a fixed aligned column; set `max-content` to size each label to its text. * (rule #44/#45 \u2014 a service theme tunes it here instead of forking CSS.)'},{name:"--descriptions-row-gap",value:"var(--space-3)",description:"Row-to-row gap (gh#294). Default = the historical hardcoded `gap-y-3`, so nothing changes * visually by default. A consumer composing Descriptions beside a Form/FormField (a read-only * block above an editable field on the same card) retunes this to `var(--space-4)` to match * Form's own field-to-field rhythm instead of the two blocks reading as visually unrelated."},{name:"--descriptions-column-gap",value:"1.5rem",description:"Column-to-column gap of the <dl> grid (#319). Was a hardcoded `gap-x-6`, the one axis of the * grid rhythm a service could NOT reach while --descriptions-row-gap was already a knob \u2014 so a * 2/3-column detail block could be retuned vertically but never horizontally. * A RAW rem, deliberately NOT var(--space-6): `gap-x-6` reads Tailwind's own --spacing grid, * which this repo does NOT density-scale, so a --space-* default would silently rescale the * column gap under a compact/comfortable density. Point it at var(--space-6) to opt in."},{name:"--descriptions-label-gap",value:"0.75rem",description:'Label \u2192 value gap inside ONE item when layout="horizontal" (was `gap-x-3`). Pairs with * --descriptions-label-width: a service that narrows the label column usually wants to close * this gap in the same step. Raw rem for the same --spacing reason as above.'},{name:"--descriptions-value-font-size",value:"var(--font-size-sm)",description:"VALUE TYPOGRAPHY (gh#294 + #319). `FormField staticText` renders its read-only value with the * SAME typography as a <dd> so a static Form row and a Descriptions value are indistinguishable * when mixed on one card. That contract used to be two copies of the literal `text-sm` plus a * comment asking future editors to keep them in sync; both call sites now read these tokens, so * the mirror is mechanical and a service retunes BOTH from one place."},{name:"--descriptions-value-line-height",value:"calc(1.25 / 0.875)",description:"Companion to --descriptions-value-font-size (the gh#260 bug). The `text-sm` utility this * replaces ALSO set a line-height, via Tailwind's default `--text-sm--line-height` * (= calc(1.25 / 0.875)); the theme remaps --text-sm but never that companion. Without this the * value would silently inherit ambient leading instead of its own 20px line box."},{name:"--email-shell-width",value:"480px",description:"\u2500\u2500 Shell \u2014 the 480px transactional-email geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-shell-padding",value:"32px",description:"canonical transactional card width"},{name:"--email-shell-page-padding",value:"24px",description:"card inset \u2014 mirrors --space-8 (2rem)"},{name:"--email-shell-border-width",value:"1px",description:"gutter between the client viewport and the card (--space-6)"},{name:"--email-card-radius",value:"10px",description:"card edge \u2014 mirrors the 1px --border hairline"},{name:"--email-card-reference-height",value:"407px",description:"mirrors --card-radius (--radius-xl = 6px \xD7 \u03C6 \u2248 10px)"},{name:"--email-stack-gap",value:"24px",description:"canonical invitation reference measures 480\xD7407"},{name:"--email-stack-gap-sm",value:"12px",description:"block rhythm \u2014 mirrors --space-stack-lg (1.5rem)"},{name:"--email-mark-width",value:"22px",description:'The mark ARTWORK is the same 32\xD732 capsule + glyph `<Logo mark="godx" />` paints; only the * RENDERED BOX differs. The canonical SCR-302 header sets it at 22px next to a 13px/700 "GoDX" * wordmark, which is smaller than the web --logo-godx-size (2rem) \u2014 an email header carries the * identity, not the navigation, so the lockup sits quieter (gh#250).'},{name:"--email-mark-height",value:"22px",description:"canonical SCR-302 header lockup mark box"},{name:"--email-mark-gap",value:"8px",description:"canonical SCR-302 header lockup mark box"},{name:"--email-wordmark-font-size",value:"13px",description:"mark \u2194 wordmark \u2014 mirrors --logo-wordmark-gap (--space-2)"},{name:"--email-wordmark-font-weight",value:"700",description:"canonical wordmark size (\u2248 --font-size-xs 12.5px)"},{name:"--email-font-family-sans",value:'"Noto Sans JP", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic Medium", YuGothic, "M PLUS 2", Meiryo, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, Arial, sans-serif',description:'The DXS canonical face is Noto Sans JP (see src/styles/fonts.css; product override, direct * instruction). Email clients honour NO @font-face, so the stack ships the face NAME first and * degrades through the platform JP faces to a generic \u2014 a client with Noto Sans JP installed (or * a webmail that already loaded it, which is common \u2014 it is a widely preinstalled/CDN-cached * Google face, more likely to actually render than the niche M PLUS 2 ever was) renders the * canonical face, everything else lands on Hiragino (macOS/iOS) \u2192 Yu Gothic (Windows) \u2192 * M PLUS 2 \u2192 Meiryo \u2192 the system UI face \u2192 Arial \u2192 sans-serif. Family names are SINGLE-quoted * on export (the generator normalises) because an inline `style="\u2026"` attribute is double-quoted; * only ASCII aliases are listed, since a non-ASCII family name does not survive every transfer * encoding.'},{name:"--email-font-family-mono",value:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",description:"Tabular/reference strings \u2014 invoice ids, masked card numbers, ISO dates, amounts \u2014 are set in * the mono face in the canonical card. Mirrors --font-family-mono."},{name:"--email-body-font-size",value:"14px",description:"Tabular/reference strings \u2014 invoice ids, masked card numbers, ISO dates, amounts \u2014 are set in * the mono face in the canonical card. Mirrors --font-family-mono."},{name:"--email-body-line-height",value:"1.9",description:"1.9, NOT the web --line-height-body (1.7) \u2014 the canonical email card runs its JP body copy * looser than a screen, because a mail client gives the reader no density control."},{name:"--email-body-font-weight",value:"400",description:"1.9, NOT the web --line-height-body (1.7) \u2014 the canonical email card runs its JP body copy * looser than a screen, because a mail client gives the reader no density control."},{name:"--email-heading-font-size",value:"17px",description:"mirrors --font-weight-normal"},{name:"--email-heading-line-height",value:"1.7",description:"canonical card title (\u2248 --heading-h2, base \xD7 \u03C6^\xBC\xB2 \u2248 17.6px)"},{name:"--email-heading-font-weight",value:"500",description:"the title shares the card's body leading, not a tight ramp"},{name:"--email-cta-height",value:"44px",description:`TOUCH TARGET, deliberately DECOUPLED from --control-height-lg (36px). Do not "restore" the * mirror: email is a mobile-first, touch-only medium. There is no hover state, no precise * pointer, and mail clients do not reliably offer zoom or a focus affordance \u2014 so the web's AA * floor (SC 2.5.8, 24x24, which 36px clears) is the wrong bar here. 44px is the AAA target size * (SC 2.5.5, 44x44) and the Apple HIG 44pt / Material 48dp convention. A service that must keep * the old box sets --email-cta-height + --email-cta-line-height in its own theme.`},{name:"--email-cta-line-height",value:"44px",description:`TOUCH TARGET, deliberately DECOUPLED from --control-height-lg (36px). Do not "restore" the * mirror: email is a mobile-first, touch-only medium. There is no hover state, no precise * pointer, and mail clients do not reliably offer zoom or a focus affordance \u2014 so the web's AA * floor (SC 2.5.8, 24x24, which 36px clears) is the wrong bar here. 44px is the AAA target size * (SC 2.5.5, 44x44) and the Apple HIG 44pt / Material 48dp convention. A service that must keep * the old box sets --email-cta-height + --email-cta-line-height in its own theme.`},{name:"--email-cta-padding-x",value:"16px",description:"equal to the height: bulletproof vertical centring in Outlook"},{name:"--email-cta-radius",value:"6px",description:"mirrors --space-4"},{name:"--email-cta-font-size",value:"14px",description:"mirrors the --radius base (0.375rem)"},{name:"--email-cta-font-weight",value:"500",description:"mirrors --font-size-base"},{name:"--email-footer-font-size",value:"11px",description:"\u2500\u2500 Legal footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-footer-line-height",value:"1.8",description:"mirrors --font-size-2xs (base \xF7 \u03C6^\xBC\xB2 \u2248 11.1px)"},{name:"--email-footer-link-gap",value:"12px",description:"the legal band runs looser than the web --line-height-normal"},{name:"--email-footer-padding-top",value:"20px",description:"horizontal separation between footer links (--space-3)"},{name:"--email-footer-border-width",value:"1px",description:"space above the legal band"},{name:"--email-focus-border-width",value:"2px",description:"\u2500\u2500 Focus affordance (webmail preview panes that DO honour :focus) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-max-width",value:"520px",description:"\u2500\u2500 Mobile reflow \u2014 applied under the narrow media query and by inline fallbacks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-width",value:"100%",description:"the reflow breakpoint (card width + both gutters)"},{name:"--email-mobile-padding",value:"20px",description:"the card goes fluid below the breakpoint"},{name:"--email-mobile-page-padding",value:"12px",description:"reduced card inset \u2014 mirrors --space-5"},{name:"--email-mobile-heading-font-size",value:"16px",description:"One step below the 17px desktop title. The canonical 390px reference raster is unusable (it is * 435px wide and already clips the card \u2014 dxs-platform/platform#496), so this is the library's * documented contract rather than a measured canonical value."},{name:"--email-mobile-cta-width",value:"100%",description:"One step below the 17px desktop title. The canonical 390px reference raster is unusable (it is * 435px wide and already clips the card \u2014 dxs-platform/platform#496), so this is the library's * documented contract rather than a measured canonical value."},{name:"--error-surface-max-width",value:"32rem",description:"Measure of the surface block. Caps the status code + body + metadata column so a long ja/vi * headline wraps at a readable width in BOTH shells, including the application shell where * PageContainer is much wider than the copy wants to be."},{name:"--error-surface-gap",value:"var(--space-3)",description:"Rhythm between the status code, the EmptyState body, the metadata list and the progress meter."},{name:"--error-surface-padding-block",value:"var(--space-10)",description:"Block padding of the surface inside the shell it is placed in. Desktop (1440/1024) default; the * 390 step steps it down so a phone loses no vertical space (see styles/shell-layout.css)."},{name:"--error-surface-padding-block-compact",value:"var(--space-6)",description:"Block padding of the surface inside the shell it is placed in. Desktop (1440/1024) default; the * 390 step steps it down so a phone loses no vertical space (see styles/shell-layout.css)."},{name:"--error-surface-brand-gap",value:"var(--space-2)",description:"Brand slot (a Logo) above the status code, system mode only."},{name:"--error-surface-meta-gap",value:"var(--space-1)",description:"Metadata description list: row rhythm and the label\u2194value gap inside one row."},{name:"--error-surface-meta-row-gap",value:"var(--space-2)",description:"Metadata description list: row rhythm and the label\u2194value gap inside one row."},{name:"--error-surface-meta-border",value:"none",description:"Chrome, default quiet (#44). A service opts in with * `--error-surface-meta-border: 1px solid hsl(var(--border));`."},{name:"--error-surface-meta-padding-block",value:"0",description:"Chrome, default quiet (#44). A service opts in with * `--error-surface-meta-border: 1px solid hsl(var(--border));`."},{name:"--error-surface-progress-max-width",value:"18rem",description:"Width of the maintenance progress meter \u2014 narrower than the measure, so it reads as metadata * rather than as the primary content of the page."},{name:"--sheet-width-default",value:"22.5rem",description:"Feedback primitive tokens: dialog, alert, empty state."},{name:"--sheet-overlay-background",value:"initial",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--sheet-overlay-alpha",value:"40%",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--sheet-shadow",value:"var(--shadow-lg)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-header-space-gap",value:"0.375rem",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-title-block-space-gap",value:"var(--space-1)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-title-row-space-gap",value:"var(--space-3)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-extra-space-gap",value:"var(--space-2)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-footer-space-gap",value:"var(--space-2)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-title-font-size",value:"var(--font-size-lg)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-description-font-size",value:"var(--font-size-sm)",description:"Sheet chrome rhythm (#319). --sheet-pad-x/-y already governed the panel inset, but the gaps * INSIDE the chrome \u2014 header stack, title/subtitle pair, the extra-slot row \u2014 were literal, so * a service could retune the sheet's outer padding and still be stuck with the inner rhythm."},{name:"--sheet-body-space-block",value:"var(--space-1)",description:"The scroll region keeps a sliver of padding so a focused control's ring is never clipped at * the scroll edges \u2014 retune together with --focus-ring-width."},{name:"--sheet-close-offset",value:"var(--space-4)",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--sheet-close-rest-alpha",value:"0.7",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--sheet-header-close-space-inline-end",value:"var(--space-8)",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-width-default",value:"32rem",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-viewport-inset",value:"1.5rem",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-overlay-background",value:"initial",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-overlay-alpha",value:"60%",description:"Close button: offset from the panel corner, and the inline room the header reserves so a * title never runs under it."},{name:"--dialog-space-x",value:"var(--space-chrome-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-y",value:"var(--space-chrome-y)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-inset",value:"var(--dialog-space-y) var(--dialog-space-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-gap",value:"var(--space-stack-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-close-space-offset",value:"var(--space-4)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-header-space-gap",value:"var(--space-stack-xs)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-title-row-space-gap",value:"var(--space-3)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-title-block-space-gap",value:"var(--space-1)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-extra-space-gap",value:"var(--space-2)",description:"Dialog chrome rhythm (#319) \u2014 the exact mirror of the --sheet-* set above, because Dialog's * header markup is byte-identical to Sheet's. --dialog-space-x/-y already governed the panel * inset, but every gap INSIDE the chrome (header stack, title/subtitle pair, the extra-slot row) * was a literal on the component, so a service could retune the outer padding and still be stuck * with the inner rhythm. Defaults are the values the literals resolved to."},{name:"--dialog-header-close-space-inline-end",value:"var(--space-8)",description:"Inline room the header reserves so a long title or the extra slot never runs under the * absolutely-positioned close button; retune together with --dialog-close-space-offset. * Mirrors --sheet-header-close-space-inline-end."},{name:"--dialog-close-rest-alpha",value:"0.7",description:"Close button rest opacity \u2014 quiet at rest, full on hover (rule #44). Was a hard 0.7 baked into * dialog-layout.css with no way for a theme to make the \u2715 louder. Mirrors * --sheet-close-rest-alpha so the two overlay siblings retune together."},{name:"--dialog-step-up-error-font-size",value:"var(--font-size-sm)",description:"Type-to-confirm challenge: the step-up failure line under the input. Its size + ink were * `text-sm text-destructive` on the component, i.e. unreachable from a theme."},{name:"--alert-radius",value:"var(--card-radius)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-space-inset",value:"var(--space-section-active)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-space-gap",value:"var(--space-inline-md)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-inner-space-gap",value:"var(--space-stack-sm)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-dismiss-space-offset",value:"var(--space-3)",description:"Alert corner radius (gh#268 \u2014 rule #45): a full-width Alert often sits in the * same page column as a Card \u2014 so the DEFAULT IS the Card radius (gh#282); a service re-tunes by * overriding this once. Default keeps the historical --radius-md."},{name:"--alert-icon-size",value:"var(--icon-size-lg)",description:"Leading tone glyph (the \u26A0/\u2713/\u24D8 at the start of the strip). It was a bare `1.25rem` in * alert-layout.css with no token at all, so the ONLY ways to resize it were `!important` or a * forked stylesheet \u2014 the two things the icon axis's tier 2 exists to abolish (gh#326). Same * step, so nothing moves; a service now scopes `--alert-icon-size` to one Alert instead. * Deliberately NOT --scaling-multiplied: the literal it replaces did not track density."},{name:"--alert-dismiss-rest-alpha",value:"0.7",description:"Dismiss \u2715 \u2014 rest opacity (quiet at rest, full on hover, rule #44) and glyph size. The rest * alpha was a hard 0.7 in alert-layout.css whose `:hover` companion lived on the COMPONENT as * `hover:opacity-100`, so the two halves of one affordance sat in two files and neither was * themeable. Mirrors --sheet-close-rest-alpha. The icon size carries a raw rem, not * var(--space-N): it replaces a flat Tailwind `size-4` step and must not start tracking the * density axis."},{name:"--alert-dismiss-icon-size",value:"var(--icon-size-md)",description:"Dismiss \u2715 \u2014 rest opacity (quiet at rest, full on hover, rule #44) and glyph size. The rest * alpha was a hard 0.7 in alert-layout.css whose `:hover` companion lived on the COMPONENT as * `hover:opacity-100`, so the two halves of one affordance sat in two files and neither was * themeable. Mirrors --sheet-close-rest-alpha. The icon size carries a raw rem, not * var(--space-N): it replaces a flat Tailwind `size-4` step and must not start tracking the * density axis."},{name:"--alert-bg-alpha",value:"0.05",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--alert-border-alpha",value:"0.3",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--banner-border-block-end-width",value:"1px",description:"BANNER \u2014 the page-level Alert treatment (gh#255). Every constant a service would want to * match to its page grid is a knob (rule #45). * * --banner-radius and --banner-border-width are NOT declared here (gh#327). They used to be, * and both copies were dead: components/banner.css declares the same two names at the same * `:root`, base.css imports banner AFTER feedback, so banner won every time. Radius agreed at * `0`, but border-width did not \u2014 this file said `0` and banner.css says `1px`, and `1px` is * what alert-layout.css actually paints as the strip's block-end hairline. So anyone who read * THIS file for the default was told the strip has no rule when it has one, and the MCP token * catalog carried both entries with the two conflicting values. banner.css owns the strip * geometry; these two live there and only there. * * The inline inset tracks the live page gutter, so a banner mounted above a PageContainer lines * its text up with the page title instead of sitting at a second, unrelated margin."},{name:"--banner-space-block",value:"var(--space-3)",description:"BANNER \u2014 the page-level Alert treatment (gh#255). Every constant a service would want to * match to its page grid is a knob (rule #45). * * --banner-radius and --banner-border-width are NOT declared here (gh#327). They used to be, * and both copies were dead: components/banner.css declares the same two names at the same * `:root`, base.css imports banner AFTER feedback, so banner won every time. Radius agreed at * `0`, but border-width did not \u2014 this file said `0` and banner.css says `1px`, and `1px` is * what alert-layout.css actually paints as the strip's block-end hairline. So anyone who read * THIS file for the default was told the strip has no rule when it has one, and the MCP token * catalog carried both entries with the two conflicting values. banner.css owns the strip * geometry; these two live there and only there. * * The inline inset tracks the live page gutter, so a banner mounted above a PageContainer lines * its text up with the page title instead of sitting at a second, unrelated margin."},{name:"--banner-space-inline",value:"var(--space-page-active-x)",description:"Inline inset tracks the page gutter so a banner's text lines up with the page title. * `--space-page-active-x` steps down to the compact gutter on `.ui-page-container` only, and * custom properties inherit \u2014 so a banner rendered INSIDE the container picks the compact value * up for free, while the normal case (a banner mounted ABOVE the container, or in AppShell) would * keep the desktop gutter and sit 8px out at 390px. The compact step below is what actually keeps * the two aligned; retune it rather than hard-coding a mobile inset at the call site."},{name:"--banner-space-inline-compact",value:"var(--space-page-compact-x)",description:"Inline inset tracks the page gutter so a banner's text lines up with the page title. * `--space-page-active-x` steps down to the compact gutter on `.ui-page-container` only, and * custom properties inherit \u2014 so a banner rendered INSIDE the container picks the compact value * up for free, while the normal case (a banner mounted ABOVE the container, or in AppShell) would * keep the desktop gutter and sit 8px out at 390px. The compact step below is what actually keeps * the two aligned; retune it rather than hard-coding a mobile inset at the call site."},{name:"--banner-dismiss-space-offset",value:"var(--space-2)",description:"Dismiss offset is measured from the banner's own (shorter) block inset, so the \u2715 stays * optically centred on a one-line strip instead of floating at the inline-alert offset."},{name:"--dialog-content-glow",value:"0 0 0 0 transparent",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-y",value:"var(--space-10)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-x",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-description-max-width",value:"28rem",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-y",value:"var(--space-6)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-x",value:"var(--space-4)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-y",value:"var(--space-3)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-x",value:"var(--space-2)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-icon-foreground",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-tint",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-size",value:"var(--icon-size-4xl)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--empty-state-icon-glyph-size",value:"var(--icon-size-xl)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-row-gap",value:"var(--space-stack-sm)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-cell-gap",value:"var(--space-inline-lg)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-card-inset",value:"var(--space-section-active)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-radius",value:"var(--radius)",description:"Medallion box + the glyph inside it. Both were literal (`width: 3rem` in the stylesheet, a * `size-6` utility on the icon), so a service could not scale the empty-state mark to its own * page rhythm \u2014 and the two must move TOGETHER or the glyph stops sitting centred in its * circle, which is exactly the kind of pair rule #45 exists to keep tunable as one."},{name:"--skeleton-background",value:"initial",description:"Skeleton placeholder fill \u2014 `initial` so the --muted default re-resolves at the call site under * a scoped theme (a :root binding to a role var freezes at :root). A service tints the shimmer to * its surface (rule #44) without forking the keyframes. Default = hsl(var(--muted))."},{name:"--skeleton-block-height",value:"1rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-caption-height",value:"0.75rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-title-height",value:"1.75rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-label-width",value:"6rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-detail-value-max-width",value:"28rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-stat-value-width",value:"8rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--skeleton-stat-caption-width",value:"5rem",description:"Skeleton bar heights + widths (#319). These carry RAW rem, not var(--space-N): the literals * they replace were plain Tailwind steps, which are not multiplied by --scaling. Routing them * through the spacing scale would silently make the loading state track the density axis while * the loaded content it stands in for does not. Names say what each bar STANDS IN FOR, so a * service retuning its type scale knows which one to move."},{name:"--query-load-more-space-block-start",value:"var(--space-stack-md)",description:'QUERY LIFECYCLE FOOTERS \u2014 the two rows `<InfiniteQueryState>` appends under a paginated feed: * the "load more" button row and the "loading more\u2026" caption. Both gaps were Tailwind literals * on the component (`pt-4` / `pt-2`), so a service running a denser (or airier) feed could not * align them to its own vertical rhythm without forking the component (rule #45). They live in * the feedback tier because these ARE query feedback surfaces and there is no query stylesheet. * Defaults = var(--space-stack-md) (16px) and var(--space-stack-sm) (8px) \u2014 the exact values the * Tailwind steps resolved to, so adopting this changes nothing until a theme opts in.'},{name:"--query-loading-more-space-block-start",value:"var(--space-stack-sm)",description:'QUERY LIFECYCLE FOOTERS \u2014 the two rows `<InfiniteQueryState>` appends under a paginated feed: * the "load more" button row and the "loading more\u2026" caption. Both gaps were Tailwind literals * on the component (`pt-4` / `pt-2`), so a service running a denser (or airier) feed could not * align them to its own vertical rhythm without forking the component (rule #45). They live in * the feedback tier because these ARE query feedback surfaces and there is no query stylesheet. * Defaults = var(--space-stack-md) (16px) and var(--space-stack-sm) (8px) \u2014 the exact values the * Tailwind steps resolved to, so adopting this changes nothing until a theme opts in.'},{name:"--tooltip-max-width",value:"20rem",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-space-inline",value:"var(--space-2)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-space-block",value:"var(--space-1)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-radius",value:"var(--radius)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-font-size",value:"var(--font-size-xs)",description:"TOOLTIP \u2014 the transient label surface. Every constant here was a Tailwind literal baked into * the component (`max-w-xs px-2 py-1 rounded-md text-xs shadow-md`), so a service could not * retune tooltip density or measure without forking the component (rule #45). Defaults reproduce * the previous look exactly, so adopting this changes nothing until a theme opts in."},{name:"--tooltip-shadow",value:"var(--shadow-md)",description:"Raised-surface depth \u2014 role-mirror knob, so a flat service theme sets `none` once."},{name:"--tooltip-background",value:"initial",description:'Surface colours \u2014 `initial` so the popover roles re-resolve at the CALL SITE under a scoped * [data-tenant]/.dark theme (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). * Defaults = hsl(var(--popover)) fill \xB7 hsl(var(--popover-foreground)) text.'},{name:"--tooltip-foreground",value:"initial",description:'Surface colours \u2014 `initial` so the popover roles re-resolve at the CALL SITE under a scoped * [data-tenant]/.dark theme (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). * Defaults = hsl(var(--popover)) fill \xB7 hsl(var(--popover-foreground)) text.'},{name:"--tooltip-border-color",value:"initial",description:'Surface colours \u2014 `initial` so the popover roles re-resolve at the CALL SITE under a scoped * [data-tenant]/.dark theme (docs/TOKENS.md \xB7 "Role-mirror knobs MUST be `initial`"). * Defaults = hsl(var(--popover)) fill \xB7 hsl(var(--popover-foreground)) text.'},{name:"--popover-width",value:"18rem",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-space-inset",value:"var(--space-4)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-radius",value:"var(--radius)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-shadow",value:"var(--shadow-md)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-header-space-gap",value:"var(--space-1)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-header-font-size",value:"var(--font-size-sm)",description:"POPOVER \u2014 the interactive overlay panel. Same story as Tooltip: `w-72 rounded-md p-4 shadow-md` * were literals, so a service could not align the panel to its own grid or width scale."},{name:"--popover-surface-background",value:"initial",description:"Role-mirror knobs \u2014 `initial`, defaults resolve at the call site (see Tooltip above)."},{name:"--popover-surface-foreground",value:"initial",description:"Role-mirror knobs \u2014 `initial`, defaults resolve at the call site (see Tooltip above)."},{name:"--popover-surface-border-color",value:"initial",description:"Role-mirror knobs \u2014 `initial`, defaults resolve at the call site (see Tooltip above)."},{name:"--toast-icon-size",value:"var(--icon-size-md)",description:"TOAST (Sonner) \u2014 the status glyph in the toast's leading slot. * * WHY A KNOB AND NOT A UTILITY: sonner renders the toast body itself and takes the five status * glyphs through ONE `icons={{ success, info, warning, error, loading }}` config prop. That prop * is all-or-nothing \u2014 a consumer who wants a different glyph size must re-declare all five icons, * re-importing lucide and re-deriving the aria wiring. Routing the size through a token makes it * a one-line theme override instead (rule #45). * * Raw rem, not var(--space-N): it replaces a flat Tailwind `size-4` step, and the 16px box sonner * gives `[data-icon]` is itself fixed \u2014 scaling the glyph with density alone would overflow it. * * NAMESPACE NOTE: sonner publishes its own `--toast-*` custom properties (--toast-icon-margin-*, * --toast-svg-margin-*, --toast-button-margin-*, --toast-close-button-*). Neither name below * collides with those; keep it that way when adding to this group."},{name:"--toast-mobile-offset",value:"16px",description:"Viewport gutter of the mobile toast stack. Sonner passes `mobileOffset` straight into inline * CSS, so a var() string resolves normally. Flat 16px on purpose: this is a fixed inset from the * device edge (thumb reach / safe area), not a density-scaled gap inside a surface."},{name:"--form-label-width",value:"8rem",description:"Fixed aligned label column by default (gh#284) \u2014 `max-content` sized each field's label * column to its own label, so horizontal forms (especially columns={2} grids) had controls * starting at ragged x positions. 8rem mirrors --descriptions-label-width so edit forms and * show pages share the same optical grid; the Form/FormField `labelWidth` prop overrides."},{name:"--form-label-gap",value:"var(--space-4)",description:"Column gap between the label and its control in horizontal/inline layout."},{name:"--form-label-font-size",value:"var(--text-sm)",description:"Type in the label column. Inherits the body size by default, so nothing moves unless a * service opts in. It is a knob because the label column already is one: --form-label-width * lets a service align forms to its grid, and a service whose grid was drawn around a * smaller label had no way to say so \u2014 leaving it to hand-write font-size per label, which * is exactly what the width token exists to prevent."},{name:"--form-block-gap",value:"var(--space-4)",description:"Row rhythm between a Form's own top-level blocks (gh#295) \u2014 e.g. the field group's * CardContent and its CardFooter action row, or two FormFields that happen to be Form's OWN * direct children (no Card framing). Unchanged from the historical value."},{name:"--form-field-row-gap",value:"var(--space-3)",description:"Field-to-field row rhythm (gh#295) \u2014 mirrors --descriptions-row-gap so a read-only value * mixed in via `FormField.staticText` (gh#294) and a real Descriptions block share ONE canonical * rhythm. Applied via margin-based sibling spacing on FormField itself (not Form's flex `gap`, * which only reaches DIRECT children \u2014 dead in the common `Form > CardContent > FormField*` * composition every real Save-button form needs), so it holds at any DOM depth relative to Form."},{name:"--form-grid-row-gap",value:"var(--form-field-row-gap)",description:"Row rhythm between the ROWS of a `<Form columns={n}>` grid (gh#304). The grid path cannot use * the margin above \u2014 a per-item margin inside a grid double-counts the track gap AND leaves the * first item of row 1 unshifted while its row-mates drop, so row 1's columns end up misaligned. * The rhythm therefore rides the grid's own `row-gap`, and it DEFAULTS TO THE SAME * --form-field-row-gap the stacked path uses so `columns={1}` \u2014 and every `columns={n}` form once * it collapses to one column on a narrow container \u2014 is pixel-identical to a Form with no * `columns` at all. Retune this alone only to give multi-column forms a looser row rhythm than * stacked ones; retune --form-field-row-gap to move both together."},{name:"--form-grid-column-gap",value:"var(--space-4)",description:"Gutter between the COLUMNS of a `<Form columns={n}>` grid (gh#304). Was ResponsiveGrid's * generic 16px stack gap with no way to say otherwise; it is a form's inter-field gutter and a * service aligning forms to its design grid needs it as a knob (rule #45). The default keeps the * historical 16px, so nothing moves unless a theme opts in."},{name:"--legal-document-section-focus-ring-offset",value:"var(--space-1)",description:"Section anchor ring needs a gap so the mark does not touch the heading * (outline form \u2014 styles/focus-ring.css)."},{name:"--legal-document-measure-max-width",value:"46rem",description:"Section anchor ring needs a gap so the mark does not touch the heading * (outline form \u2014 styles/focus-ring.css)."},{name:"--legal-document-column-gap",value:"var(--space-10)",description:"Rail \u2194 document column gutter (two-column mode only)."},{name:"--legal-document-toc-width",value:"15rem",description:"Sticky table-of-contents rail width (two-column mode only)."},{name:"--legal-document-toc-inset-block-start",value:"var(--space-6)",description:"Distance the pinned rail keeps from the top of the scrollport."},{name:"--legal-document-toc-max-height",value:"calc(100dvh - var(--space-12))",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-gap",value:"var(--space-1)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-padding",value:"var(--space-1) var(--space-2)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-radius",value:"var(--radius)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-title-font-size",value:"var(--font-size-2xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-font-size",value:"var(--font-size-xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-marker-width",value:"var(--stroke-md)",description:"Leading marker on the active contents entry \u2014 a non-colour affordance so the * active state is never colour-only (WCAG 1.4.1)."},{name:"--legal-document-nav-gap",value:"var(--space-4)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-header-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-font-size",value:"var(--font-size-xs)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-gap",value:"var(--space-10)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-title-gap",value:"var(--space-3)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-body-line-height",value:"1.8",description:"Long-form leading \u2014 legal prose reads looser than UI text."},{name:"--legal-document-scroll-offset",value:"var(--space-6)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-footer-gap",value:"var(--space-4)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-toc-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-header-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-footer-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-meta-foreground",value:"initial",description:"\u2500\u2500 Role-mirror knobs \u2014 `initial` here, role default at the call site \u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-summary-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-background",value:"initial",description:"default = hsl(var(--foreground))"},{name:"--legal-document-toc-marker-color",value:"initial",description:"default = hsl(var(--accent))"},{name:"--list-row-padding-y",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-padding-x",value:"var(--space-4)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-gap",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-border",value:"initial",description:"Row divider \u2014 `initial` so the --border default re-resolves at the call site under a scoped * theme (a :root binding to a role var freezes at :root). Default = 1px solid hsl(var(--border))."},{name:"--list-row-body-min-width",value:"12rem",description:"Inline size the content column keeps before the trailing actions wrap onto their own line * (#45 \u2014 a service retunes the stack threshold to its grid). Clamped with min(\u2026, 100%) at the * call site, so a container narrower than the knob can never force page-root overflow (#224)."},{name:"--list-row-trailing-gap",value:"var(--space-2)",description:"Gap BETWEEN trailing actions \u2014 they also wrap among themselves at narrow widths (#224)."},{name:"--list-row-compact-padding-y",value:"initial",description:'\u2500\u2500 Compact inline-actions geometry (`density="compact"`, #246) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The canonical invitation / history row: a 36px leading avatar, a shrinkable title + * description body and one or two small trailing Buttons, all on ONE line inside the canonical * 358px card (326px content after the row\'s own inline inset) at a 390px viewport. These knobs * only LOWER the geometry \u2014 the #224 wrap contract (row + trailing cluster wrap, body clamped * with min(\u2026, 100%)) is untouched, so a cluster that genuinely cannot fit still wraps instead of * widening the page root. * Declared `initial` so the spacing defaults re-resolve at the CALL SITE \u2014 --space-2 is * density-scaled and re-declared inside a `.ui-density-*` subtree, so a :root binding would * freeze compact rows at the :root density (docs/TOKENS.md \u2014 the :root freeze rule). * Defaults = var(--space-2) block padding \xB7 var(--list-row-padding-x) inline padding \xB7 * var(--space-2) gap.'},{name:"--list-row-compact-padding-x",value:"initial",description:"Inline inset defaults to the default row axis so compact and default rows in the same Card keep * one optical axis; a service retunes it to its own grid (#45)."},{name:"--list-row-compact-gap",value:"initial",description:"Inline inset defaults to the default row axis so compact and default rows in the same Card keep * one optical axis; a service retunes it to its own grid (#45)."},{name:"--list-row-compact-body-min-width",value:"6rem",description:"Compact body threshold \u2014 small enough that avatar + body + two compact Buttons fit the * canonical column (36 + 8 + 96 + 8 + 146 = 294 \u2264 326), and still a readable measure."},{name:"--list-row-read-background",value:"initial",description:"Read (default) row surface \u2014 `initial`, documented default = transparent, i.e. the Card * surface shows through untouched (#44 \u2014 chrome defaults to the quietest state)."},{name:"--list-row-unread-background",value:"initial",description:"Unread emphasis surface (`unread` rows) \u2014 `initial` so the --muted default re-resolves at the * call site under a scoped theme. Documented default = hsl(var(--muted)), the same quiet * emphasis role the table header uses; it keeps the xs muted description line at WCAG AA * (4.63:1 light / 5.47:1 dark), which hsl(var(--accent)) would not (4.23:1 light)."},{name:"--list-row-indicator-color",value:"initial",description:"Unread indicator dot fill \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme. Documented default = hsl(var(--primary))."},{name:"--list-row-indicator-size",value:"var(--space-2)",description:"Diameter of the read/unread indicator dot; the gutter is reserved on read rows too so the * titles of a mixed list stay on one optical axis."},{name:"--logo-radius",value:"var(--radius)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-xs",value:"1.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-sm",value:"1.5rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-md",value:"1.75rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-lg",value:"2.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-xs",value:"var(--font-size-2xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-sm",value:"var(--font-size-xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-md",value:"var(--font-size-sm)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-lg",value:"var(--font-size-base)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-success-background",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-success-foreground",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-godx-color",value:"initial",description:'Semantic identity fill reads the --brand IDENTITY role: independent of --primary (a re-themed * action colour never recolours the mark) AND of --success (the \u82E5\u7AF9 STATUS green stays on * badges/progress/"saved" text \u2014 the mark used to borrow it and rendered \u0394E76 \u2248 17.5 off the * canonical emerald, gh#250). Services retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --brand actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--brand)) * --logo-success-foreground = hsl(var(--logo-identity-foreground)) * --logo-godx-color = hsl(var(--brand))'},{name:"--logo-identity-foreground",value:"48 9% 9%",description:'Identity-fill INK \u2014 the ink `mark="glyph"` sets its (caller-supplied) TEXT in when it sits on * the --brand identity fill. This is deliberately NOT --brand-foreground. * * --brand-foreground is the identity artwork\'s KNOCKOUT colour, not an ink: it tracks * --background in both themes (light 60 33% 99% = --background; dark 48 9% 9% = --background) * because `<Logo mark="godx">` punches its inner bar as an evenodd HOLE and the email mark has to * paint that hole as a solid fill to match. Negative space only has to clear WCAG 2.2 SC 1.4.11 * (3:1, non-text) \u2014 and at 3.67:1 on the emerald it does. * * The boxed glyph is a different thing: it renders real TEXT, so SC 1.4.3 applies at 4.5:1 (14px * bold is NOT "large text" \u2014 that needs 18.66px bold / 24px), and the knockout white measured * 3.67:1 \u2192 a genuine AA failure. This ink is 48 9% 9% (#191815), the same near-black spine the * DARK theme already inked the glyph with, so it is theme-INVARIANT: dark renders byte-identically * (6.89:1, unchanged) and light rises 3.67 \u2192 4.74:1. A service that re-themes --brand to a dark * fill overrides --logo-success-foreground (the public knob) to re-invert the ink. * * NOT a role-mirror knob: its default is a real value, not a role token, so there is no role to * freeze at :root and the `initial` + call-site rule (docs/TOKENS.md) does not apply to it.'},{name:"--logo-godx-size-xs",value:"1.5rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-sm",value:"1.75rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-md",value:"2rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size-lg",value:"2.5rem",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-godx-size",value:"initial",description:'\u2500\u2500 Identity-mark box (`<Logo mark="godx" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The `size` prop drives the identity mark exactly like it drives the boxed glyph \u2014 a public * prop that renders identically at every tier is a silent no-op, and the godx LOCKUP already * scaled its wordmark per tier, so a fixed mark broke the mark\u2194wordmark proportion at * `size="lg"` (gh#163 follow-up). The godx artwork is a horizontal capsule inside a square * viewBox, so it carries less optical weight than the fully-filled glyph box: each tier sits * +0.25rem above its --logo-size-* sibling. `md` is 2rem \u2014 the historical fixed value \u2014 so * every existing default-size identity surface renders byte-identically. * * --logo-godx-size is the PIN: declared `initial` (guaranteed-invalid) here with the per-tier * default at the CALL SITE (logo-layout.css), so it is inert by default and a service that * sets it once freezes the mark at that box on EVERY tier \u2014 the same call-site pattern the * role-mirror colour knobs use (docs/TOKENS.md). Retune a single tier via --logo-godx-size-*. * Documented default: --logo-godx-size = unset (tiers apply).'},{name:"--logo-wordmark-gap",value:"var(--space-2)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-xs",value:"var(--font-size-xs)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-sm",value:"var(--font-size-sm)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-md",value:"var(--font-size-base)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-lg",value:"var(--font-size-lg)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-weight",value:"700",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-letter-spacing",value:"-0.01em",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-family",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-color",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role --brand (never --primary, never the * --success status green): the GoDX/`success` lockup is canonical emerald out of the box, and * every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--brand)) on the * godx / tone="success" lockup.'},{name:"--pagination-gap",value:"var(--space-inline-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-item-gap",value:"var(--space-inline-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-size-width",value:"5.5rem",description:"Size-changer trigger MIN width (gh#286) - the trigger sizes to its content (w-max) so * localized labels (ja 100/\u30DA\u30FC\u30B8) never clip; this token only keeps short labels from * collapsing below the control rhythm."},{name:"--pagination-page-padding-x",value:"var(--space-1)",description:"Inline padding on a page button. The button keeps min-width: var(--control-height), so this * only takes effect once a number outgrows that box. Deliberately --space-1 rather than * --space-2: at the wider value a two-digit label (14.8px of text + 2 * 8px + 2px border) came * to 32.8px and pushed the common 1\u20132 digit buttons off square by 0.8px."},{name:"--pagination-total-font-size",value:"var(--font-size-sm)",description:"Inline padding on a page button. The button keeps min-width: var(--control-height), so this * only takes effect once a number outgrows that box. Deliberately --space-1 rather than * --space-2: at the wider value a two-digit label (14.8px of text + 2 * 8px + 2px border) came * to 32.8px and pushed the common 1\u20132 digit buttons off square by 0.8px."},{name:"--pagination-icon-size",value:"var(--control-icon-size)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-bar-gap",value:"var(--space-3)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-bar-padding-y",value:"var(--space-2)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-label-font-size",value:"var(--font-size-xs)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-picker-width-sm",value:"11rem",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--filter-picker-width-md",value:"14rem",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-gap",value:"var(--space-2)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-item-gap",value:"var(--space-1)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-font-size",value:"var(--font-size-xs)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-separator-size",value:"var(--control-icon-size-sm)",description:'Chevron glyph inside the `simple` form\'s Prev/Next Buttons (#319). Those are size="sm" * Buttons, so `.ui-button--sm svg` would draw them at --control-icon-size-sm; the component * pinned them larger with a literal `size-4` class instead, because in simple mode the chevron * IS the control \u2014 there is no label beside it to carry the affordance. The knob keeps that * deliberate override themeable (rule #45) and tracks the default control-icon tier, so it now * follows density like every other control glyph instead of staying a flat 1rem.'},{name:"--steps-inline-index-font-weight",value:"var(--font-weight-bold)",description:"Inline-step emphasis (gh#12). The step number's weight and tint are separate knobs so a * service can express progression the canonical way \u2014 an accent TINT at normal weight \u2014 instead * of the original bold. --steps-inline-index-color / --steps-inline-separator-color are * role-mirror knobs (docs/TOKENS.md): `initial` so their defaults (the inherited step colour and * the --muted-foreground role) resolve at the CALL SITE and a scoped [data-tenant]/.dark * override still reaches them. Defaults reproduce today's row byte for byte."},{name:"--steps-inline-index-color",value:"initial",description:"Inline-step emphasis (gh#12). The step number's weight and tint are separate knobs so a * service can express progression the canonical way \u2014 an accent TINT at normal weight \u2014 instead * of the original bold. --steps-inline-index-color / --steps-inline-separator-color are * role-mirror knobs (docs/TOKENS.md): `initial` so their defaults (the inherited step colour and * the --muted-foreground role) resolve at the CALL SITE and a scoped [data-tenant]/.dark * override still reaches them. Defaults reproduce today's row byte for byte."},{name:"--steps-inline-separator-color",value:"initial",description:"default = currentColor at the call site"},{name:"--app-setting-picker-compact-control-height",value:"var(--control-height-sm)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-padding-x",value:"var(--space-2)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-gap",value:"var(--space-1)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--breadcrumb-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--menubar-shortcut-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-max-inline-size",value:"100%",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-overflow",value:"auto",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-indicator-background",value:"initial",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-indicator-size",value:"2px",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-indicator-offset",value:"0px",description:"Tabs `line` active indicator (gh#248) \u2014 the ONLY selected-state decoration the line variant * paints. The surrounding active ring belongs to the default/card lists, so `:focus-visible` * keeps its own distinct keyboard ring here (WCAG 2.4.7). `--tabs-indicator-background` is a * role-mirror knob: `initial` so hsl(var(--primary)) re-resolves at the CALL SITE under a scoped * [data-tenant]/.dark theme (a :root binding to a role var would freeze at :root). * Default = hsl(var(--primary)) \xB7 2px thick \xB7 flush with the trigger edge (offset 0 = quietest, * rule #44); a service raises the offset to park the bar on a thicker strip hairline."},{name:"--tabs-root-gap",value:"0.5rem",description:"Gap between the tab strip and the panel below it."},{name:"--tabs-list-line-space-inset",value:"0px",description:"Inset of the `line` strip. The default/pill strip keeps its own padding box; the line strip is * a flat underlined rail, so its default is the quietest value \u2014 none (rule #44). Raise it to * give the rail breathing room above its hairline."},{name:"--tabs-trigger-line-radius",value:"0px",description:"`line` trigger box. Square corners because the selected state is the token-owned ::after bar, * never a pill; the hit box is wider/taller than the pill trigger so the underline spans a real * column of the rail."},{name:"--tabs-trigger-line-padding-x",value:"1rem",description:"`line` trigger box. Square corners because the selected state is the token-owned ::after bar, * never a pill; the hit box is wider/taller than the pill trigger so the underline spans a real * column of the rail."},{name:"--tabs-trigger-line-padding-y",value:"0.5rem",description:"`line` trigger box. Square corners because the selected state is the token-owned ::after bar, * never a pill; the hit box is wider/taller than the pill trigger so the underline spans a real * column of the rail."},{name:"--menubar-item-hover-background",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--menubar-item-hover-foreground",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--filter-bar-sticky-offset",value:"0px",description:"Sticky offset for the pinned filter strip (Toolbar `sticky`, #197). Default 0 (chrome, * rule #44); raise it so the strip parks below a fixed topbar."},{name:"--filter-bar-sticky-background",value:"initial",description:"Fill painted only when the filter strip is pinned. Role-mirror knob (rule #45): `initial` * so --background re-resolves at the call site under a scoped [data-tenant]/.dark theme."},{name:"--filter-bar-scroll-padding-y",value:"var(--space-1)",description:'Scrollbar gutter reserved under a `overflow="scroll"` filter strip (#216) so the inline * scrollbar never overlaps the controls. Rule #45 \u2014 a service theme retunes it to its grid * (0 on an overlay-scrollbar platform).'},{name:"--filter-bar-search-width",value:"20rem",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--filter-bar-filter-width",value:"var(--filter-picker-width-md)",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--filter-bar-chip-gap",value:"var(--space-2)",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--filter-bar-section-gap",value:"var(--space-2)",description:"FilterBar typed model (gh#258) \u2014 token-owned geometry so every list page gets the SAME * search width, filter width, chip rhythm and section stacking without page-local CSS. * All are rule-#45 knobs: a service theme retunes them once, props/className override per * instance. Below 640px the strip stacks (search/filters full-width); the widths apply from * the 640px breakpoint up, always clamped by min(100%, \u2026) so a narrow container never * overflows at 390px with long JA/EN/VI labels."},{name:"--menu-item-height",value:"var(--band-height-md)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-radius",value:"var(--radius-sm)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-space-inline",value:"var(--space-2)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-space-gap",value:"var(--space-2)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-font-size",value:"var(--font-size-base)",description:"MENU SURFACES \u2014 one row rhythm shared by ContextMenu, Menubar and DropdownMenu. All three are * the same Radix popup surface; the row height was a literal `2rem` in the CSS and DropdownMenu * had not been converted at all, so it carried the whole box as Tailwind literals on the * component (#319). A service tunes the menu rhythm once here instead of three times."},{name:"--menu-item-inset-space-inline-start",value:"var(--space-8)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-indicator-size",value:"0.875rem",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-indicator-inset-inline-start",value:"var(--space-2)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-icon-size",value:"var(--icon-size-md)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-content-space-inset",value:"var(--space-1)",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--menu-content-min-width",value:"10rem",description:"Inset rows (a checkbox/radio row without its own indicator) reserve the indicator column so * labels stay aligned with their checked siblings."},{name:"--dropdown-content-min-width",value:"8rem",description:"DropdownMenu is anchored to a small trigger, so it opens narrower than a context menu."},{name:"--menu-separator-space-block",value:"var(--space-1)",description:"DropdownMenu is anchored to a small trigger, so it opens narrower than a context menu."},{name:"--menu-separator-space-inline",value:"calc(var(--space-1) * -1)",description:"DropdownMenu is anchored to a small trigger, so it opens narrower than a context menu."},{name:"--navigation-menu-trigger-icon-size",value:"var(--icon-size-sm)",description:"NAVIGATION MENU \u2014 the disclosure chevron on a top-level trigger. It was a bare `0.9rem` in * navigation-layout.css: no token, so unreachable from an app (gh#326), and 14.4px, so off the * icon scale AND off the pixel grid. A stroked chevron drawn into a 14.4px box lands its path * on half pixels at 1x, which is a rendering defect rather than a rounding preference, so this * SNAPS to the nearest step \u2014 --icon-size-sm, 14px, \u22120.4px. The nearest step upward is 16px * (+1.6px) and would also fight the `opacity: 0.7` that makes this chevron deliberately quiet. * `sm` rather than `md` is the same call the other quiet chevrons in the system already made * (--month-picker-separator-icon-size, --steps-inline-separator-size). NOT * --scaling-multiplied: the literal it replaces did not track density."},{name:"--steps-dot-size",value:"0.625rem",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-dot-process-ring-width",value:"var(--stroke-xl)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-marker-border-width",value:"var(--stroke-md)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-marker-font-size",value:"var(--font-size-sm)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-marker-icon-size",value:"var(--icon-size-md)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-wait-icon-size",value:"var(--icon-size-xs)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-title-font-size",value:"var(--font-size-sm)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-title-font-size-compact",value:"var(--font-size-xs)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-subtitle-font-size",value:"var(--font-size-xs)",description:"STEPS \u2014 the full (non-inline) variant. `--steps-inline-*` already covers the compact inline * form; the marker, connector and text rhythm of the main variant were still literal on the * component (#319), so a service could not resize the dot, retighten the vertical run, or move * the horizontal connector to match its own grid."},{name:"--steps-vertical-space-gap",value:"var(--space-3)",description:"Vertical run: gap between marker and text, and the drop to the next step."},{name:"--steps-vertical-space-block-end",value:"var(--space-8)",description:"Vertical run: gap between marker and text, and the drop to the next step."},{name:"--steps-vertical-text-space-block-start",value:"var(--space-1)",description:"Vertical run: gap between marker and text, and the drop to the next step."},{name:"--steps-horizontal-text-space-block-start",value:"var(--space-2)",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--steps-horizontal-text-space-inline",value:"var(--space-2)",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--steps-connector-inset",value:"1.25rem",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--steps-connector-space-block-start",value:"var(--space-4)",description:"Horizontal run: the text sits under the marker, and the connector spans the gap between * two markers \u2014 inset by the marker radius on each side so it never runs under a marker."},{name:"--app-setting-picker-locale-width",value:"10rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-timezone-width",value:"14rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-date-format-width",value:"11rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-time-format-width",value:"11rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-theme-width",value:"9rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-brand-width",value:"11rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-density-width",value:"10rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-font-size-width",value:"9rem",description:"APP SETTING PICKER \u2014 per-kind trigger widths (#319). Each picker is sized to the longest * value it can show: a timezone name is far wider than a theme name. These were literal * `sm:w-*` steps in a lookup table, so a service whose locale renders longer labels (a German * timezone list, a Japanese density label) could not widen just the one that overflows."},{name:"--app-setting-picker-width-breakpoint",value:"40rem",description:"Below this the trigger hugs its content instead of taking the per-kind width, so a picker * dropped into a narrow topbar never stretches the bar (gh#165)."},{name:"--app-setting-picker-icon-size",value:"var(--icon-size-md)",description:"Below this the trigger hugs its content instead of taking the per-kind width, so a picker * dropped into a narrow topbar never stretches the bar (gh#165)."},{name:"--app-setting-picker-icon-rest-alpha",value:"0.7",description:"Below this the trigger hugs its content instead of taking the per-kind width, so a picker * dropped into a narrow topbar never stretches the bar (gh#165)."},{name:"--scroll-area-anchor-offset",value:"3rem",description:'How close to the bottom edge the reader still counts as "following the stream" for * `<ScrollArea anchor="bottom">`. Inside this band new content keeps the viewport pinned to the * newest item; one pixel beyond it the reader is reading history and anchoring NEVER moves them * again until they come back (WCAG 3.2.5 \u2014 no change of context on request of the machine). * * It is a rule #45 knob because the right distance is a function of the row height a service * renders: one line of a dense audit log is ~20px, a chat bubble with an avatar is ~64px, and * "one row from the bottom" is what the reader actually means. Expressed in rem on purpose \u2014 it * then tracks the user\'s font size, so the band is still one row at 200% zoom (WCAG 1.4.4) * instead of collapsing to a third of a row. The `anchorOffset` prop overrides it per instance; * px/rem/em are all accepted.'},{name:"--separator-rule-size",value:"var(--stroke-hairline)",description:"Rule weight, both orientations and both halves of a labelled rule."},{name:"--separator-rule-color",value:"initial",description:"Rule colour. Role-mirror knob \u2192 declared `initial` so the --border default re-resolves at the * CALL SITE under a scoped [data-tenant] / .dark theme (docs/TOKENS.md \u2014 the :root freeze rule). * Documented default = hsl(var(--border))."},{name:"--separator-label-gap",value:"initial",description:"Gap between the label and each rule half. `initial` so the default re-resolves at the call * site: --space-* is density-scaled and re-declared inside a `.ui-density-*` subtree, so a * :root binding would freeze the gap at the :root density. Documented default = var(--space-3)."},{name:"--separator-label-inset",value:"initial",description:'Length of the SHORT rule half when labelAlign is `start` / `end` \u2014 the label\'s inset from the * inline edge (the Slack/Mattermost stream convention). It is a grid track on the INLINE axis, * so it flips with the writing direction under dir="rtl" with no extra rule. `initial` for the * same density reason as the gap. Documented default = var(--space-6).'},{name:"--separator-label-font-size",value:"var(--font-size-xs)",description:"Label type ramp (#46) \u2014 steps of the modular scale, never a literal. `xs` is the quiet * stream/divider voice; --separator-label-line-height keeps ja/vi descenders and tone marks * intact (cf. #254); --separator-label-font-weight lets a service raise an unread watermark * without touching the day divider."},{name:"--separator-label-line-height",value:"var(--line-height-normal)",description:"Label type ramp (#46) \u2014 steps of the modular scale, never a literal. `xs` is the quiet * stream/divider voice; --separator-label-line-height keeps ja/vi descenders and tone marks * intact (cf. #254); --separator-label-font-weight lets a service raise an unread watermark * without touching the day divider."},{name:"--separator-label-font-weight",value:"var(--font-weight-medium)",description:"Label type ramp (#46) \u2014 steps of the modular scale, never a literal. `xs` is the quiet * stream/divider voice; --separator-label-line-height keeps ja/vi descenders and tone marks * intact (cf. #254); --separator-label-font-weight lets a service raise an unread watermark * without touching the day divider."},{name:"--separator-label-color",value:"initial",description:"Label colour. Role-mirror \u2192 `initial`; documented default = hsl(var(--muted-foreground))."},{name:"--separator-tone-muted-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-muted-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-primary-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-primary-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-success-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-success-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-warning-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-warning-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-destructive-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-destructive-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-info-rule-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--separator-tone-info-label-color",value:"initial",description:"Per-tone rule + label colours. `tone` re-points BOTH the rule and the label \u2014 never the rule * alone \u2014 so an attention rule such as an unread watermark is not colour-only (WCAG 1.4.1) and * survives forced-colors. All role-mirror \u2192 `initial`; documented defaults are the matching * semantic role. `muted` deliberately mirrors the quiet default so a service can still split the * two voices later without a breaking change."},{name:"--sheet-responsive-breakpoint-width",value:"48rem",description:'Viewport width at and below which `SheetContent responsive="auto"` renders the mobile bottom * sheet instead of the desktop side panel. Read at runtime by useSheetResponsiveMode() (a CSS * @media cannot resolve a custom property), so a service moves the drawer breakpoint from ONE * knob for every overlay that opts into the responsive contract \u2014 Sheet and OrgSwitcher alike. * 48rem = 768px mirrors the library\'s canonical mobile line (useIsMobile). Accepts px/rem/em.'},{name:"--sheet-bottom-max-height",value:"85dvh",description:'Block size cap for the responsive BOTTOM presentation only (never for a plain * `side="bottom"` sheet, which stays content-sized). Keeps the sheet below the viewport so the * SheetBody scrolls and the page behind it stays visible/dismissable.'},{name:"--sidebar-section-label-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-product-tenant-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-background",value:"initial",description:'Sidebar nav count pill colours \u2014 the pill had a font-size knob and NO colour knob, so a rail * that needed to tell "unread" from "mentions you" had to nest a <Badge> inside `badge` and got * two pills, one inside the other. `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--secondary)) fill \xB7 hsl(var(--muted-foreground)) text.'},{name:"--sidebar-badge-foreground",value:"initial",description:'Sidebar nav count pill colours \u2014 the pill had a font-size knob and NO colour knob, so a rail * that needed to tell "unread" from "mentions you" had to nest a <Badge> inside `badge` and got * two pills, one inside the other. `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--secondary)) fill \xB7 hsl(var(--muted-foreground)) text.'},{name:"--sidebar-badge-destructive-background",value:"initial",description:'The EMPHASIS pair, read only by `SidebarItemProp.badgeTone="destructive"` (rule #44 \u2014 a * neutral row never resolves them). Defaults = hsl(var(--destructive)) fill \xB7 * hsl(var(--destructive-foreground)) text, the canonical AA-checked pair.'},{name:"--sidebar-badge-destructive-foreground",value:"initial",description:'The EMPHASIS pair, read only by `SidebarItemProp.badgeTone="destructive"` (rule #44 \u2014 a * neutral row never resolves them). Defaults = hsl(var(--destructive)) fill \xB7 * hsl(var(--destructive-foreground)) text, the canonical AA-checked pair.'},{name:"--sidebar-user-focus-ring-alpha",value:"0.45",description:"Softened focus ring on the tinted shell grounds: at full alpha the ring * reads as a SELECTED item rather than a focused one (styles/focus-ring.css)."},{name:"--topbar-chip-icon-size",value:"1.125rem",description:"Topbar glyphs. Three separate sizes, all previously baked as literals in shell-layout.css. * * --topbar-chip-icon-size is off the SCALE but on the pixel GRID, and those are different * findings. Read the rule it styles: `display: grid; place-items: center; font-weight: 700` \u2014 * it is a letter MEDALLION, not a glyph, so the half-pixel-stroke argument that made the * NavigationMenu chevron snap from 14.4px to 14px does not apply here. 18px is a whole pixel * sitting between --icon-size-md (16) and -lg (20); snapping it would be a visible 2px box * change bought for nothing. Declared, not silently tolerated. * scale-exempt: 18px letter medallion, a whole pixel between two icon steps, not a glyph"},{name:"--sidebar-product-caret-icon-size",value:"var(--icon-size-sm)",description:"Sidebar product-switcher caret \u2014 a real glyph, previously a bare 0.875rem."},{name:"--topbar-icon-size",value:"var(--icon-size-md)",description:"Sidebar product-switcher caret \u2014 a real glyph, previously a bare 0.875rem."},{name:"--topbar-caret-icon-size",value:"var(--icon-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--topbar-icon-focus-ring-alpha",value:"0.45",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-user-role-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--org-switcher-meta-foreground",value:"var(--muted-foreground)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-nav-sub-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-flyout-title-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--topbar-chip-icon-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--kbd-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-logo-mark-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-avatar-font-size",value:"var(--font-size-2xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-user-name-font-size",value:"var(--font-size-xs)",description:"The caret is the eighth literal-sized icon rule in this file. The ratchet missed it because * its selector says `caret` while the pattern looked for `icon|glyph` \u2014 the guard has been * taught the wider vocabulary."},{name:"--sidebar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--topbar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--app-shell-bar-height",value:"var(--band-height-2xl)",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-bar-inset",value:"var(--space-page-x)",description:"Inline inset and slot gap of the AppShell top bar. * * ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS (gh#330). The bar sits in the SAME grid track as * `.app-main`, directly above the page, so its first control and the page title are on one * vertical edge \u2014 or they are supposed to be. These two knobs used to name their own values * (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and * nothing reconciled them: measured in Chromium on /isolate/layout-app-shell the topbar's * content started at x=80 while the page header's started at x=88 (1512px), and because the two * sides also stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even * constant \u2014 8px at 1512, 12px between 720 and 900, 4px below 720. A consumer cannot correct a * drift that changes with the viewport. * * The page gutter WINS the axis, for three reasons: it is read by seven regions (page header, * toolbar, body, footer, PageContainer.Inset, the DataTable full-bleed compensation, Banner) * against this knob's one element; the bar is the region that must line up with the page, not * the other way round (the rail is a different track with its own inset); and moving the bar * moves exactly one element in one composition, while moving --space-page-x would move every * page in every consumer app. * * The NAMES stay \u2014 a theme that already sets `--app-shell-bar-inset` keeps working, and a bar * that genuinely wants to sit tighter than its page still has its own knob. Only the DEFAULTS * now point at the owner. `-compact` applies below the PAGE's 720px step (not the shell's 900px * restructure), so both sides tighten on the same line \u2014 see styles/shell-layout.css."},{name:"--app-shell-bar-inset-compact",value:"var(--space-page-compact-x)",description:"Inline inset and slot gap of the AppShell top bar. * * ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS (gh#330). The bar sits in the SAME grid track as * `.app-main`, directly above the page, so its first control and the page title are on one * vertical edge \u2014 or they are supposed to be. These two knobs used to name their own values * (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and * nothing reconciled them: measured in Chromium on /isolate/layout-app-shell the topbar's * content started at x=80 while the page header's started at x=88 (1512px), and because the two * sides also stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even * constant \u2014 8px at 1512, 12px between 720 and 900, 4px below 720. A consumer cannot correct a * drift that changes with the viewport. * * The page gutter WINS the axis, for three reasons: it is read by seven regions (page header, * toolbar, body, footer, PageContainer.Inset, the DataTable full-bleed compensation, Banner) * against this knob's one element; the bar is the region that must line up with the page, not * the other way round (the rail is a different track with its own inset); and moving the bar * moves exactly one element in one composition, while moving --space-page-x would move every * page in every consumer app. * * The NAMES stay \u2014 a theme that already sets `--app-shell-bar-inset` keeps working, and a bar * that genuinely wants to sit tighter than its page still has its own knob. Only the DEFAULTS * now point at the owner. `-compact` applies below the PAGE's 720px step (not the shell's 900px * restructure), so both sides tighten on the same line \u2014 see styles/shell-layout.css."},{name:"--app-shell-bar-gap",value:"var(--space-3)",description:"Inline inset and slot gap of the AppShell top bar. * * ONE OWNER FOR THE HORIZONTAL PAGE-INSET AXIS (gh#330). The bar sits in the SAME grid track as * `.app-main`, directly above the page, so its first control and the page title are on one * vertical edge \u2014 or they are supposed to be. These two knobs used to name their own values * (--space-4 / --space-3) while the page named --space-page-x / --space-page-compact-x, and * nothing reconciled them: measured in Chromium on /isolate/layout-app-shell the topbar's * content started at x=80 while the page header's started at x=88 (1512px), and because the two * sides also stepped at DIFFERENT breakpoints (shell 900px, page 720px) the error was not even * constant \u2014 8px at 1512, 12px between 720 and 900, 4px below 720. A consumer cannot correct a * drift that changes with the viewport. * * The page gutter WINS the axis, for three reasons: it is read by seven regions (page header, * toolbar, body, footer, PageContainer.Inset, the DataTable full-bleed compensation, Banner) * against this knob's one element; the bar is the region that must line up with the page, not * the other way round (the rail is a different track with its own inset); and moving the bar * moves exactly one element in one composition, while moving --space-page-x would move every * page in every consumer app. * * The NAMES stay \u2014 a theme that already sets `--app-shell-bar-inset` keeps working, and a bar * that genuinely wants to sit tighter than its page still has its own knob. Only the DEFAULTS * now point at the owner. `-compact` applies below the PAGE's 720px step (not the shell's 900px * restructure), so both sides tighten on the same line \u2014 see styles/shell-layout.css."},{name:"--app-shell-sidebar-width",value:"16rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-rail-width",value:"4rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-page-max-width",value:"80rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-main-background",value:"hsl(var(--muted) / 0.4)",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-width",value:"22.5rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-background",value:"initial",description:"Mobile-drawer scrim \u2014 `initial` so the shared --overlay-background default re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding freezes at :root). Default = * var(--app-shell-mobile-nav-alpha) of the global scrim, i.e. rgb(0 0 0 / 0.2) out of the box."},{name:"--app-shell-mobile-nav-alpha",value:"40%",description:"Share of the global --overlay-background this drawer's scrim uses. A slide-in drawer washes the * page more lightly than a modal dialog; keeping it a RATIO means a service retints the whole * system with one --overlay-background and every overlay keeps its calibrated depth."},{name:"--app-shell-mobile-nav-inset",value:"var(--space-1)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--app-shell-mobile-nav-icon-size",value:"var(--icon-size-lg)",description:'Hamburger glyph in the drawer trigger. Bigger ON PURPOSE than the trigger Button\'s own * `size="sm"` icon step (--control-icon-size-sm, 0.875rem) \u2014 it is the ONLY navigation * affordance on a phone, so it reads at the 1.25rem step. A flat rem, not a density-scaled * alias: it must not move with the density axis, or the one tap target that opens navigation * shrinks on a compact page. Consumed as a `size-[var(...)]` utility so it outranks * `.ui-button--sm svg` (see the comment at the call site in app-shell.tsx).'},{name:"--service-role-panel-list-gap",value:"0.25rem",description:"ServiceRolePanel (gh#319). `.ui-service-role-panel` shipped as a bare hook \u2014 the class was on * the root but no rule existed anywhere, so the role rail's rhythm lived entirely as Tailwind * literals inside the component and no theme could reach it (#45). The panel is a thin * composition over MasterDetail, which still owns ALL the two-track geometry; these knobs cover * only the role LIST inside the master rail. * * Flat rems, not `--space-*`: the literals they replace read Tailwind's own `--spacing` grid, * which this package never remaps and which does not follow the density axis. Aliasing them * would start scaling the rail inside a `.ui-density-*` subtree \u2014 a visual change, not a * refactor."},{name:"--service-role-panel-row-gap",value:"0.25rem",description:"between role rows (was `gap-1`)"},{name:"--service-role-panel-item-title-gap",value:"0.375rem",description:"select button \u2194 delete button (was `gap-1`)"},{name:"--service-role-panel-item-padding-block",value:"0.5rem",description:"The role row is a MULTI-LINE button (name over a description/member-count line), so it opts * out of the control height grid (`h-auto`) and sets its own block padding \u2014 that is what the * `py-2` literal was doing. It needs the raised `.ui-service-role-panel` ancestor specificity * (0,2,0) to beat `.ui-button--md { padding-block: var(--button-space-block) }` (0,1,0): * layout.css is imported BEFORE control.css and both are `@layer components`, so an equal * (0,1,0) rule here would silently lose."},{name:"--sidebar-brand-mark-size",value:"1.375rem",description:"The role row is a MULTI-LINE button (name over a description/member-count line), so it opts * out of the control height grid (`h-auto`) and sets its own block padding \u2014 that is what the * `py-2` literal was doing. It needs the raised `.ui-service-role-panel` ancestor specificity * (0,2,0) to beat `.ui-button--md { padding-block: var(--button-space-block) }` (0,1,0): * layout.css is imported BEFORE control.css and both are `@layer components`, so an equal * (0,1,0) rule here would silently lose."},{name:"--sidebar-nav-item-height",value:"var(--band-height-md)",description:"The role row is a MULTI-LINE button (name over a description/member-count line), so it opts * out of the control height grid (`h-auto`) and sets its own block padding \u2014 that is what the * `py-2` literal was doing. It needs the raised `.ui-service-role-panel` ancestor specificity * (0,2,0) to beat `.ui-button--md { padding-block: var(--button-space-block) }` (0,1,0): * layout.css is imported BEFORE control.css and both are `@layer components`, so an equal * (0,1,0) rule here would silently lose."},{name:"--sidebar-nav-item-font-size",value:"var(--font-size-xs)",description:"ON the type scale (gh#329). This was `0.8125rem` \u2014 13px, which is not a step: the golden scale * runs \u2026/ratio\xB2 \u2248 11.1 \xB7 /ratio \u2248 12.5 \xB7 base 14\u2026, so 13 sat BETWEEN two steps and every nav row * in the rail read off the system's type rhythm. It had no mechanical anchor either \u2014 unlike the * auth field label below, which is pinned to an artboard y-coordinate and keeps its literal with * a declared `scale-exempt` \u2014 only the checked-in hi-fi source's number. * * GEOMETRY MOVES, by 0.53px: 13px \u2192 --font-size-xs (\u224812.47px), the step the sub-row * (--sidebar-nav-sub-font-size) and the flyout title already take. Nothing reflows \u2014 the row is a * fixed 2rem with `align-items: center`, and 1.5 \xD7 12.47 = 18.7px sits inside it with the same * headroom 19.5px had. What is gained is that a service overriding --font-size-base now moves the * rail's labels with the rest of its type, instead of leaving one constant behind."},{name:"--sidebar-nav-item-line-height",value:"1.5",description:"Line box of the nav LABEL, not the row (gh#254): `.sb-label` clips, so its line box is its clip * box, and the row's `line-height: 1` shears descenders and Vietnamese tone marks. Keep >= 1.2."},{name:"--auth-account-summary-email-line-height",value:"1.5",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--topbar-start-truncate-line-height",value:"1.5",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-icon-size",value:"var(--icon-size-md)",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-gap",value:"0.625rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-padding-x",value:"0.625rem",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-gap",value:"2px",description:"Same gh#254 trap, two more clipping boxes. `.ui-auth-account-email` and the truncating last * child of `.ui-topbar-start` both clip (`overflow: hidden`/`clip` + `text-overflow: ellipsis`), * so their line box IS their clip box \u2014 inheriting a tight value shears descenders and Vietnamese * tone marks exactly as the sidebar label did. Keep >= 1.2."},{name:"--sidebar-nav-item-radius",value:"calc(var(--radius) - 1px)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-nav-scroll-padding",value:"var(--space-3) var(--space-2)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-section-gap",value:"var(--space-4)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-section-label-padding-x",value:"var(--space-2)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--sidebar-section-label-padding-bottom",value:"var(--space-1)",description:"Corner radius of a nav row. Defaults to the global rail radius, which is what an inset pill * wants \u2014 but a rail that sets --sidebar-nav-scroll-padding and --sidebar-nav-gap to 0 makes its * rows full-bleed bands flush with both edges, and a band wants square corners. Every other * geometry property of `.sb-nav-item` was already a knob; this one was pinned to a constant, so * the only way to square a band was a consumer selector against a DS internal class \u2014 the exact * coupling rule #45 exists to prevent. Set `0` for full-bleed bands."},{name:"--topbar-search-max-width",value:"none",description:"Default `none` (gh#296): the search trigger fills its whole `Topbar` center slot \u2014 flush to * whatever sits in `end` (a locale picker, notification bell, account menu) \u2014 rather than * floating as a fixed ~420px box with dead space on either side at normal desktop widths. A * consumer that wants a capped, centered search box (the previous look) sets this explicitly."},{name:"--topbar-height",value:"auto",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-inset",value:"0px",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-gap",value:"var(--space-2)",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-center-compact-display",value:"none",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-trigger-height",value:"var(--band-height-xl)",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-trigger-padding-x",value:"var(--space-2)",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-avatar-size",value:"1.75rem",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-menu-width",value:"16rem",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-sheet-max-height",value:"75dvh",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--org-switcher-state-min-height",value:"8rem",description:"\u26A0 THIS KNOB DELETES CONTENT AT 1100px AND BELOW \u2014 read before you ship a center slot. * At compact desktop/tablet widths the docked sidebar leaves too little inline room for three * intrinsically-sized clusters, so the package hides the optional center slot before it can * cover the breadcrumb/title or end utilities (gh#244). The default is `none`, which means a * global search trigger placed in `center` is INVISIBLE from 1100px down \u2014 including on every * phone \u2014 unless the consumer opts back in. That default arrived in 18.6.0 and removed the slot * for consumers who never changed a line of their own code (gh#12); it stays because the * overlap it prevents is a real defect and flipping a shipped default twice is worse than * documenting it once, but it is a DECISION, not an accident: * * :root { --topbar-center-compact-display: flex; } \u2190 restore the slot at every width * * Opt back in only once the center content has a compact presentation of its own (an icon-only * search trigger, a collapsing field). A page-local media query is the anti-pattern this knob * replaces."},{name:"--sidebar-item-active-color",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-tint",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-background",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--sidebar-item-active-foreground",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--auth-shell-control-height",value:"var(--control-height-comfortable)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-heading-size",value:"var(--font-size-2xl)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-card-max-width",value:"24rem",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-bar-padding",value:"var(--space-5) var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-main-padding",value:"var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-canonical-control-height",value:"var(--band-height-lg)",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-card-max-width",value:"22.5rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-heading-size",value:"var(--font-size-base)",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding",value:"1rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding-mobile",value:"0.9375rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-compact-card-inset",value:"var(--space-6)",description:"INLINE (start/end) inset of the compact auth card \u2014 the column the title, fields and actions * align to. Independent of the block axis below (gh#232)."},{name:"--auth-shell-card-padding-block-compact",value:"initial",description:"BLOCK (top/bottom) padding of the compact auth card \u2014 the knob that tunes canonical Login card * HEIGHT without narrowing the column. Bound to the card's --card-space-shell-y, so it really * reaches CardContent's block edges (before gh#232 it was wired only to --card-space-body-y and * the rendered body kept the inset on both block edges \u2014 the documented token did nothing). * Declared `initial` so the default mirrors the LIVE --auth-shell-compact-card-inset resolved at * the CALL SITE: the canonical card renders exactly as before (24px block = 24px inline), and a * service that re-tunes the inset inside a scoped `[data-tenant]` still drags the block default * with it (a :root binding would freeze at the :root inset \u2014 docs/TOKENS.md)."},{name:"--auth-shell-card-body-gap-compact",value:"var(--space-3)",description:"Header\u2194body gap inside the compact auth card (the breathing room under the title before the * first field). Its own knob since gh#232 \u2014 it used to ride on * --auth-shell-card-padding-block-compact, which conflated the section gap with the card's block * padding and made the block knob un-tunable. Default is the pre-gh#232 12px rhythm."},{name:"--auth-shell-card-gap-compact",value:"var(--space-3)",description:"IN-SLOT stack gap of the compact auth card (title \u2195 description inside the header)."},{name:"--auth-shell-main-align",value:"center",description:"Main-axis alignment of the auth column. Default `center` = today's vertically-centred card; * a flow whose intro + card + footnote stack is tall opts into `start` via this knob."},{name:"--auth-shell-card-stack-gap",value:"0px",description:"Gap between the direct sections of the auth column (intro \xB7 card \xB7 remember row). Default 0 * (quiet, rule #44) so an existing single-card consumer is untouched; presets opt in."},{name:"--auth-shell-login-card-max-width",value:"22.5rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-flow-offset-block",value:"14.4375rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-flow-offset-block-mobile",value:"13.8125rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-inline",value:"1rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-inline-mobile",value:"0.9375rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-main-padding-block-end",value:"1rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-identity-slot-block-size",value:"7rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-card-stack-gap",value:"1.25rem",description:"Login preset (gh#237) \u2014 canonical SCR-001 anchors at 1440x900, 1024x900 and 390x844. * The 112px identity slot is deliberately taller than a two-line real requester. Identity * content aligns to the slot end, so missing/one-line/two-line requester data changes only the * empty space above it, never the card or footer position. The flow starts at y=231 desktop and * y=221 mobile: 231/221 + 112 + 20 gap = card y=363/353. Literal artboard lengths belong here, * not in a consumer selector, and do not drift with --scaling."},{name:"--auth-shell-login-card-padding-block-compact",value:"var(--auth-shell-compact-card-inset)",description:"Canonical SCR-001 block inset is a FLAT 24px \u2014 the same measure as the inline inset. The card * draws its own 1px border and a border-box rect already contains it, so the former * `+ 2 x --control-border-width` double-counted the border and pushed the passkey CTA to y=390 * instead of the canonical y=388 (gh#263)."},{name:"--auth-shell-device-card-max-width",value:"23.75rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding",value:"1rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding-mobile",value:"0.9375rem 0.3125rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-otp-slot-inline-size",value:"1.71875rem",description:'CODE FIELD measure \u2014 the preset owns it (gh#12). A device grant is the one canonical flow whose * PRIMARY control is the code field, so leaving it on the generic square --otp-slot-size made the * preset own the page but not its own subject: two 4-slot `appearance="grouped"` boxes rendered * 146x38 (4 x the canonical 36px control tier + the 1px group border) against a 112x54 artboard. * These are the per-slot boxes that ADD UP to that artboard group: * inline 27.5px x 4 + 2 x 1px group border = 112px * block 52px + 2 x 1px group border = 54px * Literal artboard lengths, like every other preset measure here \u2014 they must not drift with * --scaling. A service that groups its code differently overrides --otp-slot-inline-size / * --otp-slot-block-size on its own field; the generic square knob is untouched everywhere else.'},{name:"--auth-shell-device-otp-slot-block-size",value:"3.25rem",description:'CODE FIELD measure \u2014 the preset owns it (gh#12). A device grant is the one canonical flow whose * PRIMARY control is the code field, so leaving it on the generic square --otp-slot-size made the * preset own the page but not its own subject: two 4-slot `appearance="grouped"` boxes rendered * 146x38 (4 x the canonical 36px control tier + the 1px group border) against a 112x54 artboard. * These are the per-slot boxes that ADD UP to that artboard group: * inline 27.5px x 4 + 2 x 1px group border = 112px * block 52px + 2 x 1px group border = 54px * Literal artboard lengths, like every other preset measure here \u2014 they must not drift with * --scaling. A service that groups its code differently overrides --otp-slot-inline-size / * --otp-slot-block-size on its own field; the generic square knob is untouched everywhere else.'},{name:"--auth-shell-context-card-max-width",value:"25rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding",value:"var(--space-6)",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding-mobile",value:"var(--space-6) 0",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-card-stack-gap",value:"1rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-recovery-card-max-width",value:"27rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding",value:"1rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding-mobile",value:"0.9375rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-registration-card-max-width",value:"22.5rem",description:"Registration preset (gh#256) \u2014 the canonical sign-up measure. Two things make it structurally * different from every preset above, and both are the reason it could not be expressed by * re-using `login`: * * 1. LONG-FORM SCROLLING. A registration card is the tallest surface in the hosted-identity set * (name \xB7 email \xB7 password \xB7 confirm \xB7 strength meter \xB7 consent \xB7 submit \xB7 provider row), and * a vertically CENTRED tall card clips its own top on a short viewport \u2014 the top overflows * above the scroll origin and becomes unreachable. So the column is start-aligned and the * block-start inset becomes ordinary page padding the user can scroll past. * 2. FOOTER CLEARANCE. The legal/consent footer must never be flush against the submit button * at the end of a long scroll, so the block-end inset is a knob of its own rather than * mirroring the block-start one. * * The 360px form measure and the 15px mobile inline gutter match the canonical Login flow exactly * (22.5rem card, x=15 at 390 \u21D2 width=360), so a user moving sign-in \u2192 sign-up on a phone never * sees the surface jump."},{name:"--auth-shell-registration-main-padding-block-start",value:"9.5rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-block-start-mobile",value:"8.875rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-inline",value:"1rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-inline-mobile",value:"0.9375rem",description:"Block-start offsets are DERIVED from the canonical SCR-002 artboard, not chosen: the card * anchors at y=284 (1440x900) and y=274 (390x844), and the column is * card y = padding-block-start + identity slot + stack gap * so 284 - 112 - 20 = 152px (9.5rem) and 274 - 112 - 20 = 142px (8.875rem). Like every canonical * screen in this family the page passes NO brand bar (the mark lives INSIDE the column as * AuthIdentity), so `main` starts at the viewport top and the offset is the whole anchor."},{name:"--auth-shell-registration-main-padding-block-end",value:"3rem",description:"Footer clearance \u2014 the gap the legal footer keeps below the end of a long form."},{name:"--auth-shell-registration-main-padding-block-end-mobile",value:"2rem",description:"Footer clearance \u2014 the gap the legal footer keeps below the end of a long form."},{name:"--auth-shell-registration-card-stack-gap",value:"1.25rem",description:"Footer clearance \u2014 the gap the legal footer keeps below the end of a long form."},{name:"--auth-shell-registration-identity-slot-block-size",value:"7rem",description:"Fixed identity track, the same 112px `login` proved (gh#237). Without it the card anchor moves * with the title/requester line count, so the canonical y above would hold for exactly one copy * length and drift for every other. The slot absorbs absent / one-line / two-line identity * content instead."},{name:"--auth-shell-divider-gap",value:"0.625rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-label-font-size",value:"var(--font-size-2xs)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-label-line-height",value:"calc(19 / 11)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-rule-color",value:"var(--border)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-shell-divider-label-color",value:"var(--muted-foreground)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-identity-gap",value:"0.375rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-requester-gap",value:"0.375rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-requester-icon-size",value:"var(--icon-size-md)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-requester-glyph-size",value:"var(--icon-size-2xs)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-min-height",value:"var(--control-height-comfortable)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-gap",value:"var(--space-2)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-padding",value:"var(--space-1) var(--space-2)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-identity-min-width",value:"12rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-avatar-size",value:"var(--control-height-sm)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-avatar-glyph-size",value:"var(--control-icon-size-sm)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-account-summary-email-font-size",value:"var(--font-size-sm)",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-footer-content-gap",value:"0.375rem",description:"Labelled auth divider geometry. The canonical SCR-001 divider row is 19px tall at an 11px * label (the artboard label inherits the page's line-height instead of collapsing to a 1.0 line * box). 19/11 is that RATIO, not a pin: it holds the row proportional when the label's step * moves, which is exactly what gh#329 did to it. The previous hardcoded `line-height: 1` * rendered an 11px row, sitting the whole lower half of the Login card 8px high (gh#263). * * ON the type scale (gh#329): the label was the literal `0.6875rem` (11px) and is now * --font-size-2xs, the scale's ratio\u207B\xB2 step \u2248 11.107px \u2014 the same step the sidebar section * label, the kbd cap and the chrome page subtitle already take. GEOMETRY MOVES by 0.11px on the * label and 0.19px on the row (19 \u2192 19.19px), i.e. a fifth of a pixel, and in exchange the auth * micro-scale finally rides the service's --font-size-base like everything else. 11 was never a * step; it was 2xs rounded to the nearest whole pixel and then written down as if it were."},{name:"--auth-footer-text-font-size",value:"var(--font-size-2xs)",description:"The same 11px \u2192 2xs snap as the divider label above (gh#329); this one carried no artboard * anchor at all, only the literal. GEOMETRY MOVES by 0.11px."},{name:"--auth-stack-gap",value:"var(--space-3)",description:"The same 11px \u2192 2xs snap as the divider label above (gh#329); this one carried no artboard * anchor at all, only the literal. GEOMETRY MOVES by 0.11px."},{name:"--auth-shell-field-label-gap",value:"var(--space-1)",description:"The same 11px \u2192 2xs snap as the divider label above (gh#329); this one carried no artboard * anchor at all, only the literal. GEOMETRY MOVES by 0.11px."},{name:"--auth-shell-field-label-font-size",value:"0.75rem",description:"scale-exempt: SCR-001 pins the email input at y=489; --font-size-xs (12.47px) drifts the 18px label box to 18.7px (gh#263)"},{name:"--auth-shell-field-label-line-height",value:"1.5",description:"scale-exempt: SCR-001 pins the email input at y=489; --font-size-xs (12.47px) drifts the 18px label box to 18.7px (gh#263)"},{name:"--centered-shell-bar-height",value:"var(--app-shell-bar-height)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-bar-padding-x",value:"var(--space-page-x)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-bar-padding-x-compact",value:"var(--space-page-compact-x)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-main-padding",value:"var(--space-6)",description:"CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID \"My Page\", * account, standalone settings). The bar mirrors AppShell's `.app-topbar`: the same fixed height, * and now the same HORIZONTAL PAGE-INSET AXIS (gh#330) \u2014 bar, main and footer all read the page * gutter and all step to the compact gutter on the page's own `(max-width: 720px)` line, so the * bar's content and the column beneath it share one content edge at every width. * * The comment here used to claim the bar mirrored `.app-topbar`'s inline padding. It did not, and * had not since gh#330 moved that side onto --space-page-x: the bar was left hard-coding * --space-4 (16px) while main and footer used --space-6 (24px). Measured in Chromium on * /isolate/layout-centered-shell BEFORE this fix, the bar's content sat at x=16 and the column at * x=24 \u2014 a constant 8px misalignment at every width from 784px down to 390px. 784 is not * arbitrary: it is the md tier (46rem) plus the two 24px main gutters, the width below which the * column stops being centred and pins to the gutter, which is what makes the error visible. * AFTER: 24/24 at 784 and 760, 16/16 at 700 and 390. * * The column max-width has three tiers, all wider than the 24rem auth card. A service retunes the * inset, block padding and each width tier without forking CSS."},{name:"--centered-shell-main-padding-inline-compact",value:"var(--space-page-compact-x)",description:"Inline-only compact steps. They exist as their own knobs rather than as a redefinition of the * shorthands above, so `--centered-shell-main-padding` keeps meaning all four sides and a service * that already sets it is not broken by this fix."},{name:"--centered-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"Inline-only compact steps. They exist as their own knobs rather than as a redefinition of the * shorthands above, so `--centered-shell-main-padding` keeps meaning all four sides and a service * that already sets it is not broken by this fix."},{name:"--centered-shell-footer-padding-inline-compact",value:"var(--space-page-compact-x)",description:"Inline-only compact steps. They exist as their own knobs rather than as a redefinition of the * shorthands above, so `--centered-shell-main-padding` keeps meaning all four sides and a service * that already sets it is not broken by this fix."},{name:"--centered-shell-column-offset-block",value:"0",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-sm",value:"32rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-md",value:"46rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-lg",value:"64rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-landing-max-width",value:"67.5rem",description:'CenteredShell \xB7 public-landing preset (gh#252) \u2014 the token-owned geometry of a PUBLIC marketing * / product landing page: one content measure shared by header, main column and footer, the * section rhythm between page sections, the flat (chrome-quiet) card presentation and the hero * heading tier. Every value is a service-themeable knob (rule #45): a landing composition is a * COMPOSITION of real primitives (docs/COMPOSITION-VS-COMPONENT.md \u2014 Hero/Navbar/Footer FAIL the * Framework-Component Test), so the consumer must never own geometry or a media query. These * knobs are read ONLY under `.ui-centered-shell[data-preset="public-landing"]`, so the default * shell \u2014 which emits no `data-preset` at all \u2014 is byte-identical to before.'},{name:"--centered-shell-landing-inset-inline",value:"var(--space-6)",description:"1080px \u2014 header/main/footer share ONE measure"},{name:"--centered-shell-landing-inset-inline-compact",value:"var(--space-4)",description:"24px page gutter \u2265 40rem"},{name:"--centered-shell-landing-main-padding-block",value:"var(--space-10)",description:"16px page gutter < 40rem"},{name:"--centered-shell-landing-main-padding-block-compact",value:"var(--space-6)",description:"40px"},{name:"--centered-shell-landing-section-gap",value:"var(--space-10)",description:"24px"},{name:"--centered-shell-landing-section-gap-compact",value:"var(--space-6)",description:"rhythm BETWEEN page sections"},{name:"--centered-shell-landing-footer-padding-block",value:"var(--space-8)",description:"rhythm BETWEEN page sections"},{name:"--centered-shell-landing-card-shadow",value:"none",description:"Chrome is a token, default quiet (rule #44): a landing surface is FLAT \u2014 no card elevation \u2014 * so a service opts back IN with `--centered-shell-landing-card-shadow: var(--card-shadow)`."},{name:"--centered-shell-landing-heading-size",value:"var(--font-size-display)",description:"Hero heading tier. The preset re-points `--heading-h1` (same mechanism as * `.ui-auth-shell` \u2192 `--auth-shell-heading-size`), so a hero title is a real `Heading level={1}` * and never a consumer font-size."},{name:"--centered-shell-landing-heading-size-compact",value:"var(--font-size-3xl)",description:"Hero heading tier. The preset re-points `--heading-h1` (same mechanism as * `.ui-auth-shell` \u2192 `--auth-shell-heading-size`), so a hero title is a real `Heading level={1}` * and never a consumer font-size."},{name:"--centered-shell-landing-background",value:"initial",description:'Role-mirror knob \u2014 MUST stay `initial`, with the role default at the CALL SITE * (`var(--centered-shell-landing-background, var(--background))`), so a scoped * `[data-tenant]` / `.dark` override of `--background` still reaches it (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented default = hsl(var(--background)) \u2014 a public * page is the plain page canvas, not the app\'s `--muted` chrome.'},{name:"--app-shell-bar-height",value:"var(--band-height-3xl)",description:"Rule #24 companion (gh#291): on coarse pointers --control-height grows to 2.75rem * (44px tap floor), and the shell bar sits flush with the viewport top \u2014 a 3rem bar * leaves the control's 3px focus ring painting ABOVE y=0, off-screen, which no * overflow setting can recover. 3.5rem gives 6px of breathing per side."},{name:"--sidebar-nav-item-foreground",value:"initial",description:"Resting nav row + label (also the resting sub-row). Default = hsl(var(--muted-foreground))."},{name:"--sidebar-nav-item-hover-foreground",value:"initial",description:"Hovered nav row + label. Default = hsl(var(--foreground))."},{name:"--sidebar-nav-item-disabled-foreground",value:"initial",description:"`aria-disabled` nav row + label. Default = the resting row colour."},{name:"--sidebar-nav-icon-foreground",value:"initial",description:"Nav ICON (`.sb-icon`, incl. the collapsed rail and group triggers). Default = currentColor, * i.e. the row colour \u2014 the pre-gh#228 look. Set this alone to get canonical dark icons next to * muted labels."},{name:"--sidebar-nav-icon-hover-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-active-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-disabled-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--table-row-height-compact",value:"var(--band-height-sm)",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-default",value:"var(--band-height-md)",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-comfortable",value:"var(--band-height-xl)",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height",value:"calc(var(--table-row-height-default) * var(--scaling))",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-padding-y",value:"var(--space-2)",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-space-x",value:"var(--control-padding-x)",description:"Table component tokens: row height, cell padding."},{name:"--table-border-color",value:"initial",description:"Cell-grid rule colour for `<Table bordered>` (gh#274) \u2014 the outer frame and the vertical * rules between columns. Declared `initial` so the default re-resolves to the LIVE --border * role at the call site (a :root binding to hsl(var(--border)) freezes at the :root value and * a scoped [data-tenant] override of --border would never reach it \u2014 docs/TOKENS.md). * Default = hsl(var(--border))."},{name:"--table-pagination-padding-y",value:"initial",description:"Cell-grid rule colour for `<Table bordered>` (gh#274) \u2014 the outer frame and the vertical * rules between columns. Declared `initial` so the default re-resolves to the LIVE --border * role at the call site (a :root binding to hsl(var(--border)) freezes at the :root value and * a scoped [data-tenant] override of --border would never reach it \u2014 docs/TOKENS.md). * Default = hsl(var(--border))."},{name:"--table-pagination-padding-x",value:"initial",description:"Cell-grid rule colour for `<Table bordered>` (gh#274) \u2014 the outer frame and the vertical * rules between columns. Declared `initial` so the default re-resolves to the LIVE --border * role at the call site (a :root binding to hsl(var(--border)) freezes at the :root value and * a scoped [data-tenant] override of --border would never reach it \u2014 docs/TOKENS.md). * Default = hsl(var(--border))."},{name:"--table-surface-min-inline-size",value:"640px",description:'DataTable surface \u2014 narrow-viewport legibility FLOOR (gh#253). Below the `sm` viewport step a * multi-column admin grid whose columns are `white-space: nowrap` would otherwise be crushed, so * the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. * This shipped as a hard-coded `min-w-[640px]` utility on the surface element, which left a * service that wants a narrower (or no) floor with no route but forking the component (#45). * Default = the previous literal, in px so the floor releases at EXACTLY the same width as the * px-based `sm` media query that clears it (a rem value would drift under a non-16px root and * re-introduce the scroll between the two thresholds). Set `0` to opt out entirely \u2014 which is * what `preset="action-collection"` does, because there the priority measures own the width.'},{name:"--table-head-font-size",value:"var(--font-size-xs)",description:'DataTable surface \u2014 narrow-viewport legibility FLOOR (gh#253). Below the `sm` viewport step a * multi-column admin grid whose columns are `white-space: nowrap` would otherwise be crushed, so * the bordered surface keeps a minimum inline size and `.ui-data-table-scroll` scrolls instead. * This shipped as a hard-coded `min-w-[640px]` utility on the surface element, which left a * service that wants a narrower (or no) floor with no route but forking the component (#45). * Default = the previous literal, in px so the floor releases at EXACTLY the same width as the * px-based `sm` media query that clears it (a rem value would drift under a non-16px root and * re-introduce the scroll between the two thresholds). Set `0` to opt out entirely \u2014 which is * what `preset="action-collection"` does, because there the priority measures own the width.'},{name:"--table-header-background",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-header-foreground",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-pin-shadow",value:"-6px 0 6px -5px hsl(var(--foreground) / 0.12)",description:"Inline-end shadow that lifts a pinned (sticky) action column off the body it scrolls over."},{name:"--table-row-striped-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-hover-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-selected-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-action-collection-primary-width",value:"18%",description:'Table \xB7 action-collection preset (gh#253) \u2014 the canonical dense approval / action queue. * Column PRIORITY measures replace the desktop intrinsic widths (a nowrap free-text column is * what makes a five-column queue wider than its card and forces a horizontal scroll). Nothing * here is read unless `preset="action-collection"` is set, so every existing table is untouched. * Percentages so the ratio holds at any card width; the action affordance is an absolute measure * because it must never be squeezed below its touch target.'},{name:"--table-action-collection-secondary-width",value:"22%",description:"requester / subject"},{name:"--table-action-collection-meta-width",value:"12%",description:"target / resource"},{name:"--table-action-collection-actions-width",value:"3.5rem",description:"timestamps, ids \u2014 lowest priority"},{name:"--table-action-collection-primary-width-compact",value:"24%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-secondary-width-compact",value:"22%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-meta-width-compact",value:"20%",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-actions-width-compact",value:"2.75rem",description:"Compact tier, applied below the collapse step (the 390px acceptance frame). Percentages, * like the desktop tier: while the priority ratios FIT (the canonical queues up to the * six-column budget below), they keep the table filling its container edge-to-edge with no * horizontal scroll \u2014 the gh#253 acceptance."},{name:"--table-action-collection-primary-width-floor",value:"6rem",description:'Wide-collection FLOORS \u2014 the compact measures from SEVEN columns up (gh#262). * * Percentage measures stop working once a queue carries enough columns for the shares to sum * past 100%: `table-layout: fixed` NORMALIZES them back into the table\'s specified width, so at * ~10 columns on a 390px frame a "24%" column really renders at a tenth of the frame, every * column drops below one CJK glyph, and JA headers shred into a vertical one-character-per-line * column (the failure mode WCAG 2.2 SC 1.4.10 Reflow exists to prevent). Cell `min-inline-size` * and `max(%, length)` widths are equally inert in the fixed algorithm, and the intrinsic * (max-content) size of a fixed-layout table is degenerate \u2014 all verified in Chromium \u2014 so a * LENGTH is the only floor an engine respects: length columns are never squeezed, and when * their sum outgrows the container the table\'s used width grows past its specified 100% * (CSS 2.1 \xA717.5.2.1) into the keyboard-reachable overflow-x wrapper, which scrolls. * ONE-dimensional scrolling of a data table is explicitly permitted by SC 1.4.10; * one-character-per-line is not readable at all. * * The stylesheet switches tiers on a COLUMN BUDGET, not on width: up to six columns the * percentage ratios above still resolve to readable measures at every acceptance artboard * (320 \xB7 375 \xB7 390), so the canonical five-column approval queue keeps its documented * scroll-free 390 frame byte-for-byte; from seven columns the shares are guaranteed to * over-sum and the floors + intentional scroll take over. That budget is the preset\'s * documented column-count ceiling (gh#262 expectation 3): past six columns the preset stops * compressing and starts scrolling. * * Units are rem, not ch: `ch` tracks the "0" glyph (~half a CJK em), so a ch-denominated floor * would silently halve for the JA copy these floors exist to protect. At the compact type tier * (font-size-xs = 0.75rem, space-2 inline padding each side) the 5rem meta floor keeps a ~4rem * text measure = 5 full-width glyphs per line \u2014 a JA header wraps to two readable lines, never * a character column.'},{name:"--table-action-collection-secondary-width-floor",value:"5.5rem",description:'Wide-collection FLOORS \u2014 the compact measures from SEVEN columns up (gh#262). * * Percentage measures stop working once a queue carries enough columns for the shares to sum * past 100%: `table-layout: fixed` NORMALIZES them back into the table\'s specified width, so at * ~10 columns on a 390px frame a "24%" column really renders at a tenth of the frame, every * column drops below one CJK glyph, and JA headers shred into a vertical one-character-per-line * column (the failure mode WCAG 2.2 SC 1.4.10 Reflow exists to prevent). Cell `min-inline-size` * and `max(%, length)` widths are equally inert in the fixed algorithm, and the intrinsic * (max-content) size of a fixed-layout table is degenerate \u2014 all verified in Chromium \u2014 so a * LENGTH is the only floor an engine respects: length columns are never squeezed, and when * their sum outgrows the container the table\'s used width grows past its specified 100% * (CSS 2.1 \xA717.5.2.1) into the keyboard-reachable overflow-x wrapper, which scrolls. * ONE-dimensional scrolling of a data table is explicitly permitted by SC 1.4.10; * one-character-per-line is not readable at all. * * The stylesheet switches tiers on a COLUMN BUDGET, not on width: up to six columns the * percentage ratios above still resolve to readable measures at every acceptance artboard * (320 \xB7 375 \xB7 390), so the canonical five-column approval queue keeps its documented * scroll-free 390 frame byte-for-byte; from seven columns the shares are guaranteed to * over-sum and the floors + intentional scroll take over. That budget is the preset\'s * documented column-count ceiling (gh#262 expectation 3): past six columns the preset stops * compressing and starts scrolling. * * Units are rem, not ch: `ch` tracks the "0" glyph (~half a CJK em), so a ch-denominated floor * would silently halve for the JA copy these floors exist to protect. At the compact type tier * (font-size-xs = 0.75rem, space-2 inline padding each side) the 5rem meta floor keeps a ~4rem * text measure = 5 full-width glyphs per line \u2014 a JA header wraps to two readable lines, never * a character column.'},{name:"--table-action-collection-meta-width-floor",value:"5rem",description:'Wide-collection FLOORS \u2014 the compact measures from SEVEN columns up (gh#262). * * Percentage measures stop working once a queue carries enough columns for the shares to sum * past 100%: `table-layout: fixed` NORMALIZES them back into the table\'s specified width, so at * ~10 columns on a 390px frame a "24%" column really renders at a tenth of the frame, every * column drops below one CJK glyph, and JA headers shred into a vertical one-character-per-line * column (the failure mode WCAG 2.2 SC 1.4.10 Reflow exists to prevent). Cell `min-inline-size` * and `max(%, length)` widths are equally inert in the fixed algorithm, and the intrinsic * (max-content) size of a fixed-layout table is degenerate \u2014 all verified in Chromium \u2014 so a * LENGTH is the only floor an engine respects: length columns are never squeezed, and when * their sum outgrows the container the table\'s used width grows past its specified 100% * (CSS 2.1 \xA717.5.2.1) into the keyboard-reachable overflow-x wrapper, which scrolls. * ONE-dimensional scrolling of a data table is explicitly permitted by SC 1.4.10; * one-character-per-line is not readable at all. * * The stylesheet switches tiers on a COLUMN BUDGET, not on width: up to six columns the * percentage ratios above still resolve to readable measures at every acceptance artboard * (320 \xB7 375 \xB7 390), so the canonical five-column approval queue keeps its documented * scroll-free 390 frame byte-for-byte; from seven columns the shares are guaranteed to * over-sum and the floors + intentional scroll take over. That budget is the preset\'s * documented column-count ceiling (gh#262 expectation 3): past six columns the preset stops * compressing and starts scrolling. * * Units are rem, not ch: `ch` tracks the "0" glyph (~half a CJK em), so a ch-denominated floor * would silently halve for the JA copy these floors exist to protect. At the compact type tier * (font-size-xs = 0.75rem, space-2 inline padding each side) the 5rem meta floor keeps a ~4rem * text measure = 5 full-width glyphs per line \u2014 a JA header wraps to two readable lines, never * a character column.'},{name:"--table-action-collection-flex-width-floor",value:"5rem",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-font-size-compact",value:"var(--font-size-xs)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-cell-space-x-compact",value:"var(--space-2)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-cell-padding-y-compact",value:"var(--space-2)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-row-height-compact",value:"var(--table-row-height-compact)",description:"Floor for a column with NO `priority` (the free-text column, `auto` up to the budget). It * cannot stay `auto` past the budget: once the marked columns over-constrain the table, the * fixed algorithm hands an auto column exactly 0px (verified in Chromium) \u2014 the same * shredding, moved to the free-text column. The actions floor is simply * `--table-action-collection-actions-width-compact`, already an absolute measure."},{name:"--table-action-collection-min-inline-size-compact",value:"0",description:'Compact-tier LEGIBILITY FLOOR \u2014 the measure below which the preset stops fitting the table * to its container and lets the scroll region it already owns take over. * * The percentage budget above is sized for ONE column per priority tier plus one free-text * column. A queue that repeats a tier \u2014 two `secondary` columns, three `meta` columns, or an * unmarked column beside them \u2014 asks for more than 100%, and under `table-layout: fixed` the * surplus is taken out of the columns rather than out of the table: measured on a seven-column * Japanese admin queue at 390, every column landed at 44\u201359px and CJK labels wrapped at ONE to * TWO characters per line (dxs-platform/platform#680). That is a WCAG 2.2 SC 1.4.10 Reflow (AA) * failure, and it is the failure mode this floor removes. * * Scrolling is the correct escape, not a concession: SC 1.4.10 exempts "parts of the content * which require two-dimensional layout for usage or meaning", and its own note names data * tables as the example. A table that scrolls horizontally inside its card conforms; a table * whose cells are one character wide does not. * * Default `0` so nothing changes for any existing consumer: a queue that fits its priority * budget keeps fitting. A consumer whose queue carries more columns than the budget sets this * to the measure at which its narrowest column is still readable (roughly 5rem per column for * Japanese at the compact type tier) and the table scrolls instead of crushing.'},{name:"--table-stacked-collection-card-padding-y",value:"var(--space-stack-sm)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-card-padding-x",value:"var(--space-inline-md)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-card-gap",value:"var(--space-stack-sm)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-cell-padding-y",value:"var(--space-stack-xs)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-stacked-collection-label-font-size",value:"var(--font-size-xs)",description:"Table \xB7 stacked-record-collection preset (gh#293 restore \u2014 SCR-215) \u2014 the canonical WIDE, * heterogeneous record collection. Below the collapse step every `<tr>` becomes a bordered * key-value card; these tokens own the card's own geometry (unlike action-collection, there is no * column-priority measure to retune \u2014 the whole row becomes one block)."},{name:"--table-select-column-width",value:"2.5rem",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-sort-icon-size",value:"var(--icon-size-xs)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-toolbar-icon-size",value:"var(--icon-size-md)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-pagination-icon-size",value:"var(--icon-size-md)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-pagination-font-size",value:"var(--font-size-sm)",description:"DATA TABLE odds and ends that were still literal on the component (#319). The table's cell * rhythm, widths and action-collection tiers were already tokenized; these are the pieces * around them \u2014 the select column, sort glyphs, sticky header layer and skeleton shapes."},{name:"--table-skeleton-check-size",value:"1rem",description:"Skeleton placeholders mimic the real content they stand in for: a checkbox square and a * text line at the row's cap height. Retune with --table-cell-padding-y to keep the loading * state the same height as the loaded one."},{name:"--table-skeleton-line-block-size",value:"1rem",description:"The skeleton bar's BLOCK SIZE. `--table-skeleton-line-height` was a length on the * line-height axis, whose scale is unitless ratios (gh#324) \u2014 a mis-named height, and the * only raw value on that axis, which is what kept the axis ungated. Renamed rather than * left, with the old name kept as a DEPRECATED ALIAS: it is a published token a consumer * theme may already override, and styles/table-layout.css still reads the old name, so an * override of either spelling keeps working. Prefer the new name; the alias may be removed * in a future major."},{name:"--table-skeleton-line-height",value:"var(--table-skeleton-line-block-size)",description:"The skeleton bar's BLOCK SIZE. `--table-skeleton-line-height` was a length on the * line-height axis, whose scale is unitless ratios (gh#324) \u2014 a mis-named height, and the * only raw value on that axis, which is what kept the axis ungated. Renamed rather than * left, with the old name kept as a DEPRECATED ALIAS: it is a published token a consumer * theme may already override, and styles/table-layout.css still reads the old name, so an * override of either spelling keeps working. Prefer the new name; the alias may be removed * in a future major."},{name:"--table-skeleton-radius",value:"var(--radius-sm)",description:"The skeleton bar's BLOCK SIZE. `--table-skeleton-line-height` was a length on the * line-height axis, whose scale is unitless ratios (gh#324) \u2014 a mis-named height, and the * only raw value on that axis, which is what kept the axis ungated. Renamed rather than * left, with the old name kept as a DEPRECATED ALIAS: it is a published token a consumer * theme may already override, and styles/table-layout.css still reads the old name, so an * override of either spelling keeps working. Prefer the new name; the alias may be removed * in a future major."},{name:"--table-font-size",value:"var(--font-size-sm)",description:"The table's own base type step (gh#319). Was a `text-sm` utility on the <table>; the theme * remaps `--text-sm` to `--font-size-sm`, so this default is byte-identical."},{name:"--table-line-height",value:"calc(1.25 / 0.875)",description:"MANDATORY companion to --table-font-size \u2014 the gh#260 trap, again. `text-sm` also set * line-height from Tailwind's `--text-sm--line-height`, and styles/base.css remaps `--text-sm` * but NOT that companion. Without this the table would silently inherit ambient page leading * the moment the font-size moved into CSS. Same unitless ratio Tailwind ships."},{name:"--table-row-border-width",value:"var(--stroke-hairline)",description:'Horizontal row rule (chrome, #44) \u2014 the divider between body rows AND under the header row. * Owned here rather than as TableRow\'s `border-b` utility so the "last row draws no rule" * rule in `@layer components` can actually zero it; a utility would outrank it by layer. * Colour is deliberately NOT set: it inherits the global `* { border-color: hsl(var(--border)) }` * so a scoped [data-tenant]/.dark override of --border still reaches every row.'},{name:"--table-flush-divider-width",value:"var(--table-row-border-width)",description:"Flush divider (chrome, #44/#45) \u2014 the ONE edge a full-bleed table keeps when it sits inside * `<CardContent flush>`: the line between a plain CardHeader and the first row. The other three * edges coincide with the card's own frame and are erased (gh#305); this one has nothing behind * it, so without it the header band floats (gh#306). Scoped by construction to the case that * needs it \u2014 a BANDED header, a CardBar or a headerless card already draw their own line there * and never reach this token \u2014 so a service tunes only the plain-header divider: `0` for a * borderless full-bleed table, heavier for a stronger band. Colour and style are NOT set here: * only the width is restored, so the surface's own border declaration still owns them and a * scoped [data-tenant]/.dark override of --border reaches it. * Default = the same hairline the table's other rules draw."},{name:"--toggle-count-min-width",value:"var(--space-4)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-space-inline",value:"var(--space-1)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-font-size",value:"var(--font-size-xs)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-radius",value:"var(--radius-pill)",description:"\u2500\u2500 Counter pill \xB7 geometry. One knob per component (rule #45, and the repo's existing shape: * --button-count-*, --tag-input-chip-font-size, --toggle-sm-font-size are all independent knobs * off the same primitive scale). The VALUES are byte-identical to Button's counter pill and read * the same primitive tokens, so the two pills render the same size and a Toggle count set beside * a Button count is indistinguishable \u2014 asserted by * src/styles/__tests__/toggle-count-token-parity.test.ts so it cannot drift silently. * * (They are concrete here rather than `initial` + a call-site `var(--toggle-\u2026, var(--button-\u2026))` * fallback because check:typography requires a bare `var(--token)` for every font-size, and a * split shape across the five geometry knobs would be worse than a consistent one.)"},{name:"--toggle-count-gap",value:"0",description:"Quiet default (#44) \u2014 the toggle's own flex gap already separates label from pill; this knob * ADDS to it for a service whose design grid wants the counter pushed further out."},{name:"--toggle-count-background",value:"initial",description:"\u2500\u2500 Counter pill \xB7 unpressed colours. Defaults = hsl(var(--muted)) fill (quiet: 1.09:1 against * the chip's own surface, so at rest the count reads as text, #44) and hsl(var(--foreground)) * text (14.25:1 on that fill). Opaque on purpose \u2014 a translucent fill would take its contrast * from whatever surface the chip currently has, and lose ~0.7 on hover."},{name:"--toggle-count-color",value:"initial",description:"\u2500\u2500 Counter pill \xB7 unpressed colours. Defaults = hsl(var(--muted)) fill (quiet: 1.09:1 against * the chip's own surface, so at rest the count reads as text, #44) and hsl(var(--foreground)) * text (14.25:1 on that fill). Opaque on purpose \u2014 a translucent fill would take its contrast * from whatever surface the chip currently has, and lose ~0.7 on hover."},{name:"--toggle-pressed-count-background",value:"initial",description:"\u2500\u2500 Counter pill \xB7 PRESSED colours. Defaults = hsl(var(--primary-foreground)) fill, * hsl(var(--primary)) text \u2014 the pressed chip's OWN pair, swapped. Not `--primary` on * `--primary`: the pressed chip is already filled with --primary, so a --primary pill would be * invisible on it. The swap also flips the pill from near-invisible to solid, which is one of * the encodings that keeps the pressed state off colour alone (WCAG 1.4.1)."},{name:"--toggle-pressed-count-color",value:"initial",description:"\u2500\u2500 Counter pill \xB7 PRESSED colours. Defaults = hsl(var(--primary-foreground)) fill, * hsl(var(--primary)) text \u2014 the pressed chip's OWN pair, swapped. Not `--primary` on * `--primary`: the pressed chip is already filled with --primary, so a --primary pill would be * invisible on it. The swap also flips the pill from near-invisible to solid, which is one of * the encodings that keeps the pressed state off colour alone (WCAG 1.4.1)."},{name:"--toggle-pressed-border-color",value:"initial",description:"\u2500\u2500 Pressed chrome. Default = hsl(var(--primary)), i.e. the pressed fill itself, so out of the * box this border is quiet (#44) and the pressed state is carried by the fill inversion + the * pill inversion + the forced-colors rule below. A service whose pressed fill is a PALE tint * (where those inversions get weak) opts into a visible outline by overriding this."},{name:"--toggle-count-forced-outline-width",value:"var(--stroke-hairline)",description:"Under forced-colors every fill is flattened to a system colour, so neither inversion encodes * anything any more. This outline is the state's structural fallback there \u2014 drawn on the * PRESSED pill only, and as `outline` (not `border`) so it costs no layout."},{name:"--upload-dropzone-space-inset",value:"var(--space-10)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-radius",value:"var(--radius-lg)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-border-width",value:"var(--stroke-md)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-icon-size",value:"var(--icon-size-3xl)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-hint-space-block-start",value:"var(--space-3)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-hint-font-size",value:"var(--font-size-sm)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-meta-space-block-start",value:"var(--space-1)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-dropzone-meta-font-size",value:"var(--font-size-xs)",description:"DROPZONE \u2014 the large drag target. Its inset is deliberately generous; a dense service * dials it back with one override instead of forking the variant."},{name:"--upload-tile-size",value:"6rem",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-radius",value:"var(--radius-md)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-space-gap",value:"var(--space-3)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-icon-size",value:"var(--icon-size-xl)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-label-space-block-start",value:"var(--space-1)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-label-font-size",value:"var(--font-size-xs)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-tile-overlay-font-size",value:"var(--font-size-xs)",description:'TILE \u2014 the 96px square shared by picture-card thumbnails and the "add image" button. * One knob resizes the whole gallery; picture-card grids are the most common thing a service * re-grids to match its own column rhythm.'},{name:"--upload-overlay-background",value:"initial",description:"Busy scrim over a tile/frame \u2014 role-mirror knob, `initial` so --background re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme. Default = hsl(var(--background) / 0.7)."},{name:"--upload-overlay-alpha",value:"70%",description:"Busy scrim over a tile/frame \u2014 role-mirror knob, `initial` so --background re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme. Default = hsl(var(--background) / 0.7)."},{name:"--upload-remove-space-inset",value:"var(--space-1)",description:"REMOVE affordance \u2014 the \u2715 / trash chip pinned to a tile or avatar."},{name:"--upload-remove-offset",value:"var(--space-1)",description:"REMOVE affordance \u2014 the \u2715 / trash chip pinned to a tile or avatar."},{name:"--upload-remove-icon-size",value:"var(--icon-size-sm)",description:"REMOVE affordance \u2014 the \u2715 / trash chip pinned to a tile or avatar."},{name:"--upload-picture-max-width",value:"20rem",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-radius",value:"var(--radius-md)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-preview-max-height",value:"12rem",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-empty-height",value:"8rem",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-empty-label-space-block-start",value:"var(--space-2)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-empty-label-font-size",value:"var(--font-size-sm)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-overlay-font-size",value:"var(--font-size-sm)",description:"PICTURE \u2014 the single wide preview variant."},{name:"--upload-picture-badge-radius",value:"var(--radius-md)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-offset",value:"var(--space-2)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-space-inline",value:"var(--space-2)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-space-block",value:"0.125rem",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-picture-badge-font-size",value:"var(--font-size-xs)",description:'"Pending replace" chip. The radius previously came from a bare `rounded`, which resolves to a * flat .25rem and so ignored the radius scale entirely \u2014 the exact shape of a rule #44 miss. * It now follows the tile radius like every other chip in the component.'},{name:"--upload-avatar-size",value:"6rem",description:"AVATAR \u2014 the round single-image variant."},{name:"--upload-avatar-border-width",value:"var(--stroke-md)",description:"AVATAR \u2014 the round single-image variant."},{name:"--upload-avatar-change-space-block",value:"var(--space-1)",description:"AVATAR \u2014 the round single-image variant."},{name:"--upload-avatar-change-background",value:"rgb(0 0 0 / 0.7)",description:'The "change" strip sits ON the photo, so its scrim is intentionally a fixed dark wash rather * than a surface role \u2014 it must stay legible over an arbitrary user image in either theme.'},{name:"--upload-avatar-change-foreground",value:"#fff",description:'The "change" strip sits ON the photo, so its scrim is intentionally a fixed dark wash rather * than a surface role \u2014 it must stay legible over an arbitrary user image in either theme.'},{name:"--upload-avatar-pending-delete-alpha",value:"0.4",description:"Alpha of an image the user has marked for deletion but not yet committed."},{name:"--upload-draft-space-inline",value:"var(--space-3)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-space-block",value:"var(--space-2)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-space-gap",value:"var(--space-2)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-radius",value:"var(--radius-md)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-font-size",value:"var(--font-size-sm)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-draft-icon-size",value:"var(--icon-size-sm)",description:"DRAFT BAR \u2014 the undo strip shown after a pending remove/replace."},{name:"--upload-row-space-inline",value:"var(--space-3)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-space-block",value:"var(--space-2)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-space-gap",value:"var(--space-3)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-radius",value:"var(--radius-md)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-font-size",value:"var(--font-size-sm)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-row-icon-size",value:"var(--icon-size-md)",description:"FILE ROW \u2014 the list rendered under the dropzone/button variants."},{name:"--upload-crop-dialog-max-width",value:"24rem",description:"CROP DIALOG \u2014 the 1:1 avatar cropper (`UploadCropDialog`). The 66-literal upload pass above * never reached this file, so the whole cropper was still baked on the component: the dialog was * pinned to `max-w-sm`, the round preview to `size-48`, the zoom row to `px-2`. An avatar is one * of the most service-specific sizes there is (a 96px tile service wants a smaller crop stage * than a 200px one), and the crop stage must stay in proportion with --upload-avatar-size \u2014 * which is exactly the pair rule #45 exists to keep tunable together. * * The dialog width intentionally stays a knob of its own rather than reading * --dialog-width-default: the cropper is deliberately NARROWER than a normal dialog so the round * stage reads as the subject, not as a small element in a wide box."},{name:"--upload-crop-preview-size",value:"12rem",description:"CROP DIALOG \u2014 the 1:1 avatar cropper (`UploadCropDialog`). The 66-literal upload pass above * never reached this file, so the whole cropper was still baked on the component: the dialog was * pinned to `max-w-sm`, the round preview to `size-48`, the zoom row to `px-2`. An avatar is one * of the most service-specific sizes there is (a 96px tile service wants a smaller crop stage * than a 200px one), and the crop stage must stay in proportion with --upload-avatar-size \u2014 * which is exactly the pair rule #45 exists to keep tunable together. * * The dialog width intentionally stays a knob of its own rather than reading * --dialog-width-default: the cropper is deliberately NARROWER than a normal dialog so the round * stage reads as the subject, not as a small element in a wide box."},{name:"--upload-crop-preview-radius",value:"var(--radius-pill)",description:"CROP DIALOG \u2014 the 1:1 avatar cropper (`UploadCropDialog`). The 66-literal upload pass above * never reached this file, so the whole cropper was still baked on the component: the dialog was * pinned to `max-w-sm`, the round preview to `size-48`, the zoom row to `px-2`. An avatar is one * of the most service-specific sizes there is (a 96px tile service wants a smaller crop stage * than a 200px one), and the crop stage must stay in proportion with --upload-avatar-size \u2014 * which is exactly the pair rule #45 exists to keep tunable together. * * The dialog width intentionally stays a knob of its own rather than reading * --dialog-width-default: the cropper is deliberately NARROWER than a normal dialog so the round * stage reads as the subject, not as a small element in a wide box."},{name:"--upload-crop-zoom-space-inline",value:"var(--space-2)",description:"Zoom row inset \u2014 the slider is inset from the dialog edge so its thumb's focus ring is never * flush against the dialog padding."}];var h={issue:"https://github.com/godx-jp/godxjp-ui/issues/163",standard:"docs/FRAME-COVERAGE-STANDARD.md",ledger:"preview/frame-coverage.ledger.json",recordedAt:"2026-07-30",warning:"UNTESTED means no executed case demonstrates this dimension. It is NOT a pass. Do not infer support from a happy-path example, a similarly-named frame, or a rendered screenshot.",verdicts:{covered:"An executed case proves this dimension.",untested:"No executed case. NOT a pass \u2014 report it as UNTESTED to the user.","not-applicable":"The dimension cannot exist for this export; the ledger carries the written reason."},requiredViewports:[320,375,390,768,1024,1280,1440,1920],containerWidths:[240,320,480,640,960],totals:{exports:288,dimensionCells:4032,covered:66,untested:2211,notApplicable:1755,exportsWithoutFrame:0},dimensions:[{id:"variants",title:"Variants",axis:"visual"},{id:"tones",title:"Tones",axis:"visual"},{id:"sizes",title:"Sizes",axis:"visual"},{id:"shapes",title:"Shapes",axis:"visual"},{id:"density",title:"Spacing / density",axis:"visual"},{id:"ownership",title:"Controlled / uncontrolled / reset",axis:"ownership"},{id:"states",title:"Disabled / read-only / loading / empty / error / success",axis:"state"},{id:"async",title:"Async: loading / reject / retry / cancel / offline",axis:"async"},{id:"responsive",title:"Responsive viewport matrix",axis:"responsive"},{id:"rtl",title:"RTL / direction",axis:"international"},{id:"contentStress",title:"Long / localized content stress",axis:"contentStress"},{id:"keyboard",title:"Keyboard / focus order / focus return",axis:"accessibility"},{id:"accessibleName",title:"Accessible name / description / error association",axis:"accessibility"},{id:"reducedMotion",title:"Preferences: reduced motion / forced colors / 200% zoom / coarse touch",axis:"preferences"}],knownGaps:[{id:"button-contract-gaps",targets:["Button"],dimensions:["states","contentStress","rtl","keyboard","reducedMotion"],cases:["implicit form `type` inside a form","loading combined with asChild","count / overflowCount / showZero overflow","long Japanese and Vietnamese labels","RTL","coarse touch target"]},{id:"card-contract-gaps",targets:["Card"],dimensions:["variants","density","rtl","accessibleName"],cases:["`variant` \xD7 `accent` \xD7 `size` matrix",'`density="tight"` spacing mode',"nested heading levels","RTL accent rail"]},{id:"card-subcomponent-contract-gaps",targets:["CardBar","CardContent","CardFooter","CardHeader"],dimensions:["rtl","accessibleName","contentStress"],cases:["CardBar `extra` slot","CardContent `tight` / `flush` / `solo`","CardFooter `flush` / `separated`","CardHeader `banded`","RTL"]},{id:"empty-state-contract-gaps",targets:["EmptyState"],dimensions:["variants","contentStress","accessibleName"],cases:["`section` variant","`compact` variant","without icon / description / action","heading level (`titleAs` / `titleLevel`) and live-region semantics"]},{id:"progress-contract-gaps",targets:["Progress"],dimensions:["tones","contentStress"],cases:["destructive tone","`over={true}` (value beyond 100%)"]},{id:"carousel-contract-gaps",targets:["Carousel"],dimensions:["ownership","contentStress","keyboard","reducedMotion","rtl"],cases:["single slide","plugins / setApi","autoplay pause","arrow-key traversal alongside tabs","touch / swipe","RTL"]},{id:"data-table-contract-gaps",targets:["DataTable"],dimensions:["states","async","keyboard","accessibleName","density"],cases:["error / prerequisite / background refresh","server and manual modes (manualPagination / manualSorting / manualFiltering)","zero-page and single-page pagination","accessible table and action-column labels","invalid row ids (getRowId)","keyboard behaviour","virtualization"]},{id:"charts-contract-gaps",targets:["LineChart","BarChart","AreaChart","PieChart"],dimensions:["async","reducedMotion","keyboard","contentStress","responsive","rtl"],cases:["loading / error","reduced motion","keyboard datum navigation","invalid / null / NaN data","mobile axis collision","RTL"]},{id:"locale-stress-gaps",targets:["Timeline","TreeList","PasswordStrength"],dimensions:["contentStress","accessibleName"],cases:["long localized Japanese / Vietnamese content","hard-coded screen-reader strings that never reach `t()`"]}]},z=[{name:"Accordion",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionContent",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AccordionItem",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionTrigger",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AccountChip",group:"layout",frame:"docs/layout/account-chip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Activity",group:"general",frame:"docs/general/activity.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes","tones","variants"],notApplicable:["density","ownership","shapes","states"]},{name:"Alert",group:"feedback",frame:"docs/feedback/alert.tsx",covered:["tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"AlertActions",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertContent",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDescription",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]},{name:"AlertDialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"AlertDialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogRoot",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertMutationFeedback",group:"query",frame:"docs/query/mutation-feedback.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertQueryError",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertTitle",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AppSettingPicker",group:"navigation",frame:"docs/navigation/app-setting-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AppShell",group:"layout",frame:"docs/layout/app-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AreaChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"AspectRatio",group:"layout",frame:"docs/layout/aspect-ratio.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthAccountSummary",group:"layout",frame:"docs/layout/auth-account-summary.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AuthDivider",group:"layout",frame:"docs/layout/auth-divider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthFooter",group:"layout",frame:"docs/layout/auth-footer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthIdentity",group:"layout",frame:"docs/layout/auth-identity.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthShell",group:"layout",frame:"docs/layout/auth-shell.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"AuthStack",group:"layout",frame:"docs/layout/auth-stack.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Avatar",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:["shapes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states","tones","variants"]},{name:"AvatarFallback",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AvatarImage",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Badge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:["shapes","tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states"]},{name:"Banner",group:"feedback",frame:"docs/feedback/banner.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"BarChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"BranchScopePicker",group:"data-entry",frame:"docs/data-entry/branch-scope-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Breadcrumb",group:"layout",frame:"docs/navigation/breadcrumb.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Button",group:"general",frame:"docs/general/button/index.tsx",covered:["shapes","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","tones"]},{name:"ButtonRefetch",group:"query",frame:"docs/query/button-refetch.tsx",covered:["shapes","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","tones"]},{name:"Calendar",group:"data-entry",frame:"docs/data-entry/calendar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Card",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"CardAction",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardBar",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardContent",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardCover",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardDescription",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardFooter",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardHeader",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardTitle",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Carousel",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CarouselContent",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselDots",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselItem",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselNext",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CarouselPrevious",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Cascader",group:"data-entry",frame:"docs/data-entry/cascader.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CenteredShell",group:"layout",frame:"docs/layout/centered-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Checkbox",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CheckboxGroup",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CodeBlock",group:"data-display",frame:"docs/data-display/code-block.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Collapsible",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CollapsibleContent",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CollapsibleTrigger",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ColorPicker",group:"data-entry",frame:"docs/data-entry/color-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Command",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandEmpty",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandGroup",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandInput",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandItem",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CommandList",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandPalette",group:"data-entry",frame:"docs/data-entry/command-palette.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CompactBarTrend",group:"charts",frame:"docs/charts/compact-bar-trend.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ContextMenu",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuCheckboxItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"ContextMenuLabel",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuPortal",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioGroup",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuSeparator",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuShortcut",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSub",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ContextMenuTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"CredentialReveal",group:"data-display",frame:"docs/data-display/credential-reveal.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"DataState",group:"query",frame:"docs/query/data-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DataTable",group:"data-display",frame:"docs/data-display/data-table/index.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["shapes","sizes","tones","variants"]},{name:"DatePicker",group:"data-entry",frame:"docs/data-entry/date-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DateRangePicker",group:"data-entry",frame:"docs/data-entry/date-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Descriptions",group:"data-display",frame:"docs/data-display/descriptions.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Dialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogBody",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogClose",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"DialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenu",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuCheckboxItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"DropdownMenuLabel",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuPortal",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuSeparator",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuShortcut",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSub",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DropdownMenuTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"EmptyState",group:"data-display",frame:"docs/data-display/empty-state.tsx",covered:["tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"ErrorSurface",group:"layout",frame:"docs/layout/error-surface/index.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"Field",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FilterBar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"FilterBarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Flex",group:"layout",frame:"docs/layout/flex.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Form",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones","variants"]},{name:"FormErrors",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FormErrorsProvider",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FormField",group:"data-entry",frame:"docs/data-entry/form-field/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Heading",group:"general",frame:"docs/general/typography.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"HoverCard",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"HoverCardContent",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"HoverCardTrigger",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InfiniteQueryState",group:"query",frame:"docs/query/infinite-query-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Input",group:"data-entry",frame:"docs/data-entry/input.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"InputOTP",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"InputOTPGroup",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSeparator",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSlot",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Label",group:"data-entry",frame:"docs/data-entry/label.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LegalDocumentShell",group:"layout",frame:"docs/layout/legal-document-shell/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LineChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ListRow",group:"data-display",frame:"docs/data-display/list-row.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones","variants"]},{name:"Logo",group:"general",frame:"docs/general/logo.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"MasterDetail",group:"layout",frame:"docs/layout/master-detail.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Menubar",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarCheckboxItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"MenubarLabel",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarMenu",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioGroup",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarSeparator",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarShortcut",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSub",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarSubContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSubTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"MenubarTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MobileShell",group:"layout",frame:"docs/layout/mobile-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MonthPicker",group:"data-entry",frame:"docs/data-entry/month-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MonthRangePicker",group:"data-entry",frame:"docs/data-entry/month-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"NavigationMenu",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuContent",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuIndicator",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuItem",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuLink",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuList",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuTrigger",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuViewport",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NumberInput",group:"data-entry",frame:"docs/data-entry/number-input.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"OrgSwitcher",group:"layout",frame:"docs/layout/org-switcher.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PageContainer",group:"layout",frame:"docs/layout/page-container.tsx",covered:["density","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"Pagination",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationContent",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationEllipsis",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationItem",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PaginationLink",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationNext",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationPrevious",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PasswordInput",group:"data-entry",frame:"docs/data-entry/password-input.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"PasswordStrength",group:"data-entry",frame:"docs/data-entry/password-strength.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PermissionMatrix",group:"data-display",frame:"docs/data-display/permission-matrix.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"PieChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Popover",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PopoverAnchor",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverContent",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverDescription",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverHeader",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTitle",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTrigger",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PrefetchLink",group:"query",frame:"docs/query/prefetch-link.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Progress",group:"data-display",frame:"docs/data-display/progress.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","variants"]},{name:"Prose",group:"data-display",frame:"docs/data-display/prose.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"QrCode",group:"data-display",frame:"docs/data-display/qr-code.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Radio",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroup",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroupRoot",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioItem",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Rating",group:"data-entry",frame:"docs/data-entry/rating.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ResizableHandle",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanel",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanelGroup",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResponsiveGrid",group:"layout",frame:"docs/layout/responsive-grid.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Reveal",group:"general",frame:"docs/general/reveal.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollArea",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollBar",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SearchInput",group:"data-entry",frame:"docs/data-entry/search-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Select",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectContent",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectGroup",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectItem",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectLabel",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollDownButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollUpButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectSeparator",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectTrigger",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"SelectValue",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"Separator",group:"layout",frame:"docs/layout/separator.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"ServiceCatalogCta",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCard",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCardSkeleton",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceRolePanel",group:"layout",frame:"docs/layout/service-role-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Sheet",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SheetBody",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetClose",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SheetContent",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetDescription",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetFooter",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetHeader",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:["tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"SheetOverlay",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetPortal",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTitle",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTrigger",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Sidebar",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarHeader",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarItem",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"SidebarSection",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Skeleton",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonDetail",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonRows",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonStat",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonTable",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Slider",group:"data-entry",frame:"docs/data-entry/slider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SplitPane",group:"layout",frame:"docs/layout/split-pane.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"StatCard",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"StatusBadge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:["shapes","tones","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","sizes","states"]},{name:"Steps",group:"navigation",frame:"docs/navigation/steps.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Switch",group:"data-entry",frame:"docs/data-entry/switch.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones","variants"]},{name:"Table",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableBody",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableCell",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHead",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHeader",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableRow",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tabs",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsContent",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TabsList",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsTrigger",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TagInput",group:"data-entry",frame:"docs/data-entry/tag-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Text",group:"general",frame:"docs/general/typography.tsx",covered:["sizes","tones"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"Textarea",group:"data-entry",frame:"docs/data-entry/textarea.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]},{name:"Timeline",group:"data-display",frame:"docs/data-display/timeline.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones"]},{name:"TimelineGrid",group:"data-display",frame:"docs/data-display/timeline-grid.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TimePicker",group:"data-entry",frame:"docs/data-entry/time-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Toaster",group:"feedback",frame:"docs/feedback/toast.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Toggle",group:"data-entry",frame:"docs/data-entry/toggle.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","states","tones"]},{name:"ToggleGroup",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","tones"]},{name:"ToggleGroupItem",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:["sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones"]},{name:"Toolbar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ToolbarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tooltip",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TooltipContent",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipProvider",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipTrigger",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Topbar",group:"layout",frame:"docs/layout/topbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Transfer",group:"data-entry",frame:"docs/data-entry/transfer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TreeList",group:"data-display",frame:"docs/data-display/tree-list.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TreeSelect",group:"data-entry",frame:"docs/data-entry/tree-select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TwoFactorSetup",group:"feedback",frame:"docs/feedback/two-factor-setup.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Upload",group:"data-entry",frame:"docs/data-entry/upload.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones"]}],le=new Map(z.map(t=>[t.name.toLowerCase(),t]));function L(t){return le.get(String(t??"").trim().toLowerCase())}var u=[{number:1,title:"Storybook is mandatory",body:"Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."},{number:2,title:"Tokens, not utilities",body:"Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden."},{number:3,title:"Radix for interactive primitives",body:"Anything with keyboard / ARIA / portal wraps the relevant Radix primitive."},{number:4,title:"shadcn-style ownership",body:"Primitives are thin wrappers; consumers can fork the source in place."},{number:5,title:"One i18next singleton",body:"`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`."},{number:6,title:"WCAG 2.1 AA baseline",body:"Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."},{number:7,title:"SemVer 2.0 + Keep a Changelog 1.1",body:"Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."},{number:8,title:"Inclusive naming",body:"`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."},{number:9,title:"No marketing speak",body:'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'},{number:10,title:"English is canonical for docs",body:"Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."},{number:11,title:"Submodule discipline",body:"Never push a pin to a SHA not on the submodule remote."},{number:12,title:"Branch + PR workflow",body:"`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."},{number:13,title:"TypeScript strict",body:"Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."},{number:14,title:"Every third-party library is shadcn / Radix-recommended",body:"Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."},{number:15,title:"No `@apply` re-encoding tokens",body:"Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."},{number:16,title:"CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",body:"A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."},{number:17,title:"`src/stories/` \u2194 `src/components/` parity",body:"Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."},{number:18,title:"`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",body:"Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."},{number:19,title:"No service-specific anything",body:'`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'},{number:20,title:'No "platform-only" exports',body:"Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."},{number:21,title:"Every component honours every theme axis",body:"`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values."},{number:22,title:"100% match to the design canon",body:'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'},{number:23,title:"Concept-first prop API",body:"One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."},{number:24,title:"Mobile-first",body:"Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."},{number:25,title:"Stories are docs; UI is the primitive",body:"When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."},{number:26,title:"Library isolation",body:"`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."},{number:27,title:"Per-group folder structure",body:"Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."},{number:28,title:"`src/` folder taxonomy",body:"Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."},{number:29,title:"Stories consume framework primitives only",body:"No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."},{number:30,title:"Story `render` returns JSX directly",body:"No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."},{number:31,title:"No nested wrapper / convenience primitives",body:"One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."},{number:32,title:"No redundant props",body:"Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."},{number:33,title:"Stories / source / docs name-synchronized",body:"No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only)."},{number:34,title:"Storybook source panel = real, copy-paste-ready code",body:'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`).'},{number:35,title:"Status chips never wrap",body:"A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."},{number:36,title:"Badge tone/icon are the colour escape hatch",body:"For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."},{number:37,title:"DataTable is full-width \u2014 never inside a narrow grid column",body:"A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"},{number:38,title:"FilterBar stays OUT of CardContent flush",body:"`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."},{number:39,title:"Long text columns get an explicit width",body:"For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."},{number:40,title:"Pages are mobile-first",body:'Author and verify every page at 320\u2013390px FIRST. Page sections are spaced by `PageContainer` itself (`--page-body-gap` between its direct children). Inside a section spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction="col"`, control rows = the default `direction="row"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout (the static audit rejects them). Styles are loaded whole: `@godxjp/ui/styles` or `styles/core`, never a hand-picked set of layers.'},{number:41,title:"Drawer & dialog footer layout",body:'Sheet/Dialog/AlertDialog footers are a pinned action bar (Ant Design Drawer footer): the footer sticks to the bottom, SheetFooter draws a full-bleed top border, and actions are RIGHT-aligned with the PRIMARY button rightmost (Cancel/secondary to its left). A destructive / clear / reset action goes far-LEFT \u2014 give that button `className="mr-auto"`. NEVER stack footer buttons full-width or center them.'},{number:42,title:"Props & Tokens Before Customization",body:"Before reaching for a Tailwind class, inline `style`, or extra CSS, you MUST first check whether the component already supports the need via a PROP, a design TOKEN, or a layout/typography PRIMITIVE. godx-ui is meant to be enough on its own (Ant-Design-style): `className` is for genuine one-offs only \u2014 never to redo what an API already does. Specifically: (1) NEVER hand-roll typography \u2014 no `text-[13px]`/`text-[11px]` arbitrary px (bypasses the golden type scale), no `font-medium`/`font-semibold`/`text-muted-foreground` on a raw `<span>`; use `<Text size tone weight tabular mono>` / `<Heading level>`. (2) NEVER hand-roll a trivial flex/grid wrapper; use `<Flex>` / `<ResponsiveGrid>` / `<PageContainer>`. (3) NEVER set a control's radius/height/colour with a utility when a `shape`/`size`/`tone`/token exists."},{number:43,title:"Every form control goes through FormField",body:"Consumers MUST wrap every labelled form control (Input, Select, DatePicker, DateRangePicker, NumberInput, Radio.Group, Checkbox groups, range pairs, ...) in FormField \u2014 it owns the label (aria-labelledby, never a dangling <label for>), auto-generates/injects the control id, and wires aria-describedby/aria-errormessage/aria-invalid. Bare controls are the rare exception (e.g. a toolbar quick-filter with its own aria-label) and must carry id/name + aria-label themselves. Never hand-roll a label+control stack with Text/Label."},{number:44,title:"Chrome is a token, default quiet",body:"Any decorative chrome a component draws \u2014 dividers, separator borders, and the padding that exists only to space that chrome \u2014 MUST read a token; never hard-code it in `src/styles/*.css` (a hard-coded `border-bottom: 1px solid hsl(var(--border))` leaves consumers no off-switch short of a variant fork). The DEFAULT is the quietest state (`none` / balanced rhythm); a service theme opts IN, e.g. `--page-header-divider: 1px solid hsl(var(--border))`."},{number:45,title:"Every service-tunable constant gets a knob",body:'When component CSS encodes a geometry choice that a service plausibly re-tunes to match its design handoff \u2014 form label column width, label\u2194control gap, header insets \u2014 it MUST be a documented component token (current value as the default). The theme sets it ONCE globally; props (`labelWidth`) override per instance; Form\u2192FormField priority stays intact. The test: "would a service theme.css want to change this to match its design grid?" If yes and the only route is forking CSS, that is a library gap \u2014 fix the library, don\'t patch the app.'},{number:46,title:"Typography is tokens, default is base",body:"The DEFAULT body size is `--font-size-base`; components render body/UI text at `base`, not at the `sm` alias. Smaller-by-design text (badge, section label, caption) is a component token defaulting to a small step (`--badge-font-size: var(--font-size-xs)`), so a service re-tunes that part without moving the global scale. The `sm`/`xs` tokens stay for the explicit `<Text size>` API. Every component token is surfaced in the MCP `get_component` output (check:mcp-token-sync)."},{number:47,title:"The layer contract \u2014 cascade layers, not specificity",body:'Every rule this package ships is inside a cascade layer, and layer order beats specificity outright. Two consequences. (1) INSIDE the package: `@layer components` is EARLIER than Tailwind\'s `utilities`, so a utility a component emits on its own element (`<table class="text-sm">`) silently outranks the component rule meant to own that property \u2014 no selector can win. A responsive re-point that must beat such a utility goes in `@layer godxjp-ui-responsive`, declared after Tailwind in `styles/base.css` and therefore LAST; it is reserved for `@container`/`@media` re-points, never static rules.'}];function T(t){return u.find(a=>a.number===t)}var b=[{name:"common-fixes",tagline:"Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",tags:["fixes","migration","bug","cardstat","statusbadge","datatable","sidebar","gotcha","review"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2050
2115
|
// 0) \u2605 MOST COMMON: <Card> body has NO padding (content is flush against the edges)
|
|
2051
2116
|
// Cause: the bare <Card> has ZERO inner padding \u2014 it MUST contain <CardContent>.
|
|
2052
2117
|
// Don't hand-roll padding with className="p-4" on the Card either.
|
|
@@ -2234,7 +2299,7 @@ export function GeneralSettingsSection({
|
|
|
2234
2299
|
// use route-backed local navigation so every area is deep-linkable and back/forward works.
|
|
2235
2300
|
// \u26D4 Do NOT wrap the whole settings page in a Card, and do NOT make desktop a horizontal tab bar
|
|
2236
2301
|
// \u2014 persistent local nav scales to many sections; a tab strip does not.
|
|
2237
|
-
// \u26D4 Do NOT invent CSS class names for the shell/nav/content \u2014 compose Flex + semantic tokens.`},{name:"settings-section-rows",aliases:["settings-section","settings-row","danger-zone","billing-handoff","preferences-rows","account-identity"],tagline:"The four canonical settings BLOCKS \u2014 identity \xB7 preference rows \xB7 billing handoff \xB7 danger zone \u2014 composed from Card + ListRow + Descriptions + StatusBadge + AlertDialog. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",tags:["settings","account","billing","danger","preferences","listrow","card","admin"],code:`//
|
|
2302
|
+
// \u26D4 Do NOT invent CSS class names for the shell/nav/content \u2014 compose Flex + semantic tokens.`},{name:"settings-section-rows",aliases:["settings-section","settings-row","danger-zone","billing-handoff","preferences-rows","account-identity"],tagline:"The four canonical settings BLOCKS \u2014 identity \xB7 preference rows \xB7 billing handoff \xB7 danger zone \u2014 composed from Card + ListRow + Descriptions + StatusBadge + AlertDialog. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",tags:["settings","account","billing","danger","preferences","listrow","card","admin"],code:`// Gate 0 (docs/COMPOSITION-VS-COMPONENT.md): a "SettingsSection"/"SettingsRow"/
|
|
2238
2303
|
// "DangerZone" FAILS the Framework-Component Test \u2014 it owns no behavior (C2), is fully
|
|
2239
2304
|
// expressible from existing primitives + tokens (C3), and its API would be screen-shaped (C4).
|
|
2240
2305
|
// So compose it. Live reference screen: the "settings account sections" page in the preview
|
|
@@ -2664,7 +2729,7 @@ export function MaintenancePage() {
|
|
|
2664
2729
|
// --error-surface-max-width | -gap | -padding-block(-compact) | -meta-gap | -meta-row-gap |
|
|
2665
2730
|
// --error-surface-meta-border (default 'none', rule #44) | --error-surface-progress-max-width
|
|
2666
2731
|
//
|
|
2667
|
-
// ANTI-PATTERNS: AuthShell + a generic Card (the
|
|
2732
|
+
// ANTI-PATTERNS: AuthShell + a generic Card (the workaround; AuthShell is the
|
|
2668
2733
|
// UNAUTHENTICATED root with auth-card geometry) \xB7 rebuilding nav on the 403 page \xB7 two CTAs \xB7
|
|
2669
2734
|
// className="min-h-dvh flex \u2026" \xB7 a hand-built "18:00 - 20:00 JST" string \xB7 writing the request id
|
|
2670
2735
|
// into 'description' as prose instead of using the semantic slot.`},{name:"badge-coloring",tagline:"Colour a Badge for localized labels and tiers via tone + icon (escape-hatch props).",tags:["statusbadge","badge","tone","color","status","tier","table"],code:`import { Badge } from "@godxjp/ui/data-display"
|
|
@@ -3010,7 +3075,7 @@ export function MfaChallengePage({ code, onCodeChange, error, pending, onVerify
|
|
|
3010
3075
|
// 390 panel 360px at x=15 (15px inline gutter, --auth-shell-recovery-main-padding-mobile),
|
|
3011
3076
|
// 310px content column, OTP still ONE row, primary still full-width, the fallback
|
|
3012
3077
|
// row wraps to a stack only when the localized labels exceed the column.
|
|
3013
|
-
// \u26A0\uFE0F The 390 canonical reference
|
|
3078
|
+
// \u26A0\uFE0F The 390 canonical reference is a desktop 2\xD72 COMPOSITE that overflows
|
|
3014
3079
|
// and crops horizontally. It is NOT a valid mobile source and was not traced; the row above is a
|
|
3015
3080
|
// DECIDED contract, documented in docs/layout/auth-recovery/ and pinned by tests.
|
|
3016
3081
|
|
|
@@ -3255,7 +3320,7 @@ export function AccountChip({ user, onSignOut }: { user: { name: string; email:
|
|
|
3255
3320
|
|
|
3256
3321
|
// In the page header, a language Select takes width="auto" so it does not swallow the row:
|
|
3257
3322
|
// <PageContainer title="\u2026" extra={<><Select \u2026><SelectTrigger width="auto">\u2026</SelectTrigger>\u2026</Select><AccountChip \u2026 /><Button>Send</Button></>}>
|
|
3258
|
-
`}];function E(t){let a=t.trim().toLowerCase();return b.find(e=>e.name===a||e.aliases?.includes(a))}function
|
|
3323
|
+
`}];function E(t){let a=t.trim().toLowerCase();return b.find(e=>e.name===a||e.aliases?.includes(a))}function U(t){let a=t.trim().toLowerCase();return a===""?b:b.filter(e=>e.name.includes(a)||e.aliases?.some(o=>o.includes(a))||e.tagline.toLowerCase().includes(a)||e.tags.some(o=>o.includes(a)))}function M(t){return t.audience!=="core"}var x=[{id:"taste",audience:"both",name:"Taste baseline \u2014 Senior UI/UX engineering",whenToUse:"Default for any production app screen. Metric-based rules, strict component architecture, CSS hardware acceleration, balanced design engineering.",source:"Leonxlnx/taste-skill (root) + @godxjp/ui design-thinking.ts",sections:[{id:"mobile-first",title:"Mobile-first non-negotiable",tagline:"Defaults target xs (\u22650px); enhance via sm: / md: / lg: / xl: / 2xl:",body:`Cardinal rule 24. Touch targets \u2265 44\xD744 px. NEVER read
|
|
3259
3324
|
window.innerWidth \u2014 use useBreakpoint(). Stories render at narrow
|
|
3260
3325
|
viewport first. Multi-column layouts: grid grid-cols-1 sm:grid-cols-N.
|
|
3261
3326
|
EXCEPTION: name pairs (\u59D3+\u540D) use grid-cols-2 always.`},{id:"one-intent-per-screen",title:"One intent per screen",tagline:"Pick the ONE primary question this page answers. 60-80% visual weight to it.",body:`Wall-of-cards dashboards are AI slop. Show 1-2 hero metrics
|
|
@@ -3780,7 +3845,7 @@ Only after sign-off do you move to code. A wireframe skipped = a redesign reliti
|
|
|
3780
3845
|
6. Hand-rolled UI instead of @godxjp/ui primitives (raw nav buttons, styled-div "Card", raw table).
|
|
3781
3846
|
Every run of this skill produces, in order: (1) a stakeholder-confirmed concept/entity artifact \u2192
|
|
3782
3847
|
(2) a layered IA doc \u2192 (3) an approved wireframe Artifact \u2192 (4) a per-route implementation checklist.
|
|
3783
|
-
Do not advance a step until the prior artifact is signed off.`}]}];function A(t){return x.find(a=>a.id===t)}function
|
|
3848
|
+
Do not advance a step until the prior artifact is signed off.`}]}];function A(t){return x.find(a=>a.id===t)}function V(t,a){return A(t)?.sections.find(e=>e.id===a)}function G(t,a){let e=t.toLowerCase(),o=[],r=(n,s,g,p,d)=>{n.some(f=>e.includes(f))&&o.push({skill:s,section:g,why:p,alsoSee:d})};r(["premium","awwwards","agency","linear","apple","high-end","luxury"],"soft","vibe-archetypes","Premium tier \u2014 pick a Vibe + Layout archetype + apply Double-Bezel.",["soft/double-bezel","soft/magnetic-hover"]),r(["landing page","marketing","hero","long scroll"],"imagegen-web","hero-composition-bias","Landing pages benefit from hero composition variety + per-section image generation.",["gpt-tasteskill/principles","soft/layout-archetypes"]),r(["mobile app","ios","android","phone screen","onboarding flow"],"imagegen-mobile","principles","Mobile app design \u2014 generate screens first, avoid phone-shaped-website.",["taste/mobile-first"]),r(["workspace","notion","document","editorial","knowledge base"],"minimalist","palette","Editorial workspace = warm monochrome + spot pastels + serif headings.",["minimalist/typography","minimalist/bento-grids"]),r(["dashboard","data heavy","tabular","ops table"],"brutalist","principles","Data-heavy dashboards work with Brutalist (rigid grids, utilitarian color).",["taste/one-intent-per-screen"]),r(["brand","identity","logo","guidelines"],"brandkit","principles","Brand identity work \u2014 boards before screens."),r(["refactor","redesign","upgrade existing","audit"],"redesign","fix-priority","Existing project = run audit first, fix in priority order (font \u2192 palette \u2192 states \u2192 ...).",["redesign/audit-checklist"]),r(["form","validation","submit","sign up","registration"],"taste","form-discipline","Form must have explicit label + help + error wired via FormField (rule 34)."),r(["loading","saving","skeleton","spinner"],"taste","loading-states","Skeleton for INIT fetch, Spinner for active work. Never mix."),r(["mobile first","responsive","breakpoint"],"taste","mobile-first","Default styles target xs. Touch targets \u2265 44px. Use useBreakpoint()."),r(["complete code","full implementation","no placeholder"],"output","banned","Banned: // ..., // TODO, 'for brevity'. Ship complete runnable code."),r(["gsap","scrolltrigger","scroll choreography","pinning"],"gpt-tasteskill","principles","GSAP ScrollTrigger \u2014 pinning, stacking, scrubbing."),r(["from image","image to code","design first"],"image-to-code","workflow","Generate design image first \u2192 analyze \u2192 implement."),r(["handoff","design bundle","claude design","prototype","build the page","implement the design","build this screen","mockup"],"design-to-page","map-to-primitives","Map every block to a real @godxjp/ui primitive (MCP-first), consume existing tokens, apply the reference-design DNA, tables central, gaps \u2192 extend-or-ask, verify.",["design-to-page/read-intent","design-to-page/dna","design-to-page/tables-central"]),r(["compose a screen","new screen","new page","create a page","create a screen","build a view","build a screen","from scratch","screen from a brief"],"compose-a-screen","pick-primitives","Build a new app screen from real @godxjp/ui primitives (MCP-first): one-intent-per-screen, real chrome, every state + a11y + i18n, verify.",["compose-a-screen/assemble-screen","compose-a-screen/state-and-a11y","taste/one-intent-per-screen"]),r(["bug in godx","godx-ui bug","report a bug","file an issue","gh issue","can't follow the rule","library is broken","primitive is broken"],"compose-a-screen","report-bug","If @godxjp/ui itself is at fault, don't fake a workaround \u2014 file a detailed gh issue (use draft_bug_report).",["design-to-page/report-bug"]),r(["slow","performance","perf","violation","long task","lag","janky","re-render","rerender","bundle size","code splitting","tree-shak","ch\u1EADm","l\u1ED7i hi\u1EC7u n\u0103ng","\u91CD\u3044"],"app-performance","measure-first","Measure FIRST (longtask + temporary Profiler), then apply the matching proven fix \u2014 page architecture, not the library, is almost always the culprit.",["app-performance/filter-pane-memo","app-performance/heavy-panels","app-performance/bundle-budget"]),r(["admin console","admin panel","control panel","back-office","back office","management console","management ui","multi-tenant","multi tenant","platform admin","workspace admin","console qu\u1EA3n tr\u1ECB","layered ia","information architecture"],"design-complex-admin","entity-model-first","A complex multi-tier admin console: lock the entity model \u2192 study exemplars \u2192 design a LAYERED IA \u2192 wireframe (Artifact) \u2192 validate \u2192 build route-by-route. Kills the flat-console anti-pattern.",["design-complex-admin/layered-ia","design-complex-admin/wireframe-first","compose-a-screen/pick-primitives"]);let i=a?.consumerOnly?o.filter(n=>{let s=A(n.skill);return!s||s.audience!=="core"}):o;return i.length===0?a?.consumerOnly?[{skill:"compose-a-screen",section:"pick-primitives",why:`No keyword match for "${t}". Default consumer path: compose the screen from real primitives via the MCP.`,alsoSee:["design-to-page/map-to-primitives","taste/one-intent-per-screen"]}]:[{skill:"taste",section:"<see whenToUse>",why:`No keyword match for "${t}". Default to the "taste" baseline \u2014 see whenToUse for sections.`}]:i}var D=[{category:"visual",name:"Purple-blue gradient hero",body:`The default LLM color palette \u2014 purple \u2192 blue \u2192 cyan radial /
|
|
3784
3849
|
linear gradient as hero background. Looks like every AI-generated
|
|
3785
3850
|
SaaS landing page from 2023.`,fix:`Use the framework's accent palette (\`data-accent="blue"\` /
|
|
3786
3851
|
"violet" / "cyan" / "green" / "orange" / "rose"). Solid surface
|
|
@@ -3889,7 +3954,7 @@ shell with a centered spinner. Layout shifts when content
|
|
|
3889
3954
|
arrives.`,fix:`Use Skeleton placeholders matching the eventual content shape.
|
|
3890
3955
|
Render \`<Skeleton className="h-9 w-full rounded-md" />\` in place of
|
|
3891
3956
|
each control \u2014 inside the FormField that will hold it, so the labels
|
|
3892
|
-
and grid stay put. Layout stays stable, perceived speed improves.`}];function G(t){return D.filter(a=>a.category===t)}var R=[{category:"typography",symptom:"Inter / Roboto / Open Sans everywhere \u2014 the AI default.",fix:"Pick a font with character: Geist, Outfit, Cabinet Grotesk, Satoshi for sans. For editorial / creative \u2014 pair a serif heading (Newsreader, Lyon, Playfair) with a sans body.",uiNote:"Override --font-sans + --font-serif at the consumer's root CSS. Framework reads from these tokens."},{category:"typography",symptom:"Headlines lack presence \u2014 small + thin + default tracking.",fix:"Increase display size, tighten letter-spacing (-0.02em to -0.04em), reduce line-height (1.1). Headlines should feel HEAVY and INTENTIONAL.",uiNote:"Typography.Title size={1} for hero; override fontFamily + letterSpacing inline."},{category:"typography",symptom:"Body paragraphs full-width \u2014 hard to read.",fix:"Limit paragraph max-width to ~65ch. Increase line-height to 1.6+.",uiNote:"Wrap Typography.Paragraph in `<div style={{ maxWidth: '65ch' }}>`."},{category:"typography",symptom:"Only Regular (400) + Bold (700) weights \u2014 flat hierarchy.",fix:"Introduce Medium (500) + SemiBold (600) for subtle weight contrasts."},{category:"typography",symptom:"Numbers in proportional font \u2014 columns jitter in tables.",fix:"`font-variant-numeric: tabular-nums` for data, or a monospace font like Geist Mono.",uiNote:"Table primitive already uses `tabular-nums` on `.num` cells. For ad-hoc numeric labels, add the CSS prop manually."},{category:"typography",symptom:"Orphaned words \u2014 single word on the last line of a heading.",fix:"`text-wrap: balance` (h1/h2/h3) or `text-wrap: pretty` (body)."},{category:"typography",symptom:"Title Case On Every Header.",fix:"Use sentence case instead. More modern, easier to read."},{category:"color-surface",symptom:"Pure #000000 background.",fix:"Replace with off-black / dark charcoal / tinted dark (deep navy).",uiNote:"Framework dark theme already uses tinted dark values \u2014 verify the consumer's override didn't force pure black."},{category:"color-surface",symptom:"Oversaturated accent colors.",fix:"Keep saturation below 80%. Desaturate so accents BLEND with neutrals rather than scream."},{category:"color-surface",symptom:"More than one accent color competing.",fix:"Pick ONE. Remove the rest. Consistency beats variety in palette.",uiNote:"Set ONE `data-accent` at `<html>` root. Use semantic colors (success / warning / destructive) only for genuinely semantic content."},{category:"color-surface",symptom:"Purple/blue 'AI gradient' aesthetic \u2014 most common AI fingerprint.",fix:"Replace with neutral base + ONE considered accent. Drop the gradient entirely if it has no narrative purpose."},{category:"color-surface",symptom:"Generic black `box-shadow` everywhere.",fix:"Tint shadow to match background hue (e.g. cool gray bg \u2192 cool gray shadow). Colored shadows over pure black."},{category:"color-surface",symptom:"Random dark section breaking an otherwise light page.",fix:"Either commit to full dark mode OR keep light consistently. If contrast needed, use a SLIGHTLY darker shade of the same palette \u2014 not a sudden jump to #111."},{category:"color-surface",symptom:"Empty flat sections with no visual depth.",fix:"Add depth with an ambient gradient at 0.02-0.05 opacity, or background imagery you COMMIT to the repo. Never a hotlinked placeholder service: a third-party image that never settles blocks `networkidle`, which times out a page load outright \u2014 it cost this repo two docs pages failing every browser gate. Empty flat = unfinished."},{category:"layout",symptom:"Everything centered + symmetric.",fix:"Break symmetry: offset margins, mixed aspect ratios, left-aligned header over centered body."},{category:"layout",symptom:"Three equal card columns as feature row \u2014 the most generic AI layout.",fix:"Replace with 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry. The 3-equal-cols pattern is RED FLAG #1.",uiNote:"Use Bento Grid (custom CSS grid with `gridColumn: 'span N'`) instead of `<Grid cols={3}>` for hero sections."},{category:"layout",symptom:"`height: 100vh` causing iOS Safari jump.",fix:"Use `min-height: 100dvh` (dynamic viewport) instead."},{category:"layout",symptom:"No max-width container \u2014 content stretches edge-to-edge.",fix:"Add a container constraint (1200-1440px) with `margin: auto`. Or use `max-w-4xl / max-w-5xl` for content-heavy pages.",uiNote:"Framework's PageContent constrains via `var(--container-max-width)`. Consumer may override."},{category:"layout",symptom:"Cards forced to same height by flexbox.",fix:"Allow variable heights or use masonry when content varies.",uiNote:"Use Masonry primitive \u2014 handles variable heights without flexbox stretch."},{category:"layout",symptom:"Buttons at random vertical positions in card rows.",fix:"Pin CTAs to card bottom \u2014 same Y-position across the row regardless of content above.",uiNote:"Card's `actions` footer slot bottom-aligns automatically."},{category:"layout",symptom:"Feature lists starting at different vertical positions in pricing tables.",fix:"Fixed-height title/price block + consistent spacing above the feature list. Cards align across columns."},{category:"layout",symptom:"Dashboard ALWAYS has a left sidebar.",fix:"Consider top navigation, floating command menu, or collapsible panel. Sidebar isn't the only chrome.",uiNote:"Framework supports both \u2014 AppShell with sidebar slot is optional; can use Topbar-only for some flows."},{category:"interactivity",symptom:"No hover states on buttons.",fix:"Background shift, scale, or translate on hover \u2014 150-200ms ease.",uiNote:"Framework Button has built-in hover. If overridden \u2014 restore."},{category:"interactivity",symptom:"No active/pressed feedback.",fix:"`scale(0.98)` or `translateY(1px)` on `:active`. Simulates a physical click."},{category:"interactivity",symptom:"No focus ring (`outline: none`).",fix:"Restore visible `:focus-visible` ring. Accessibility requirement, not optional."},{category:"interactivity",symptom:"Generic circular spinner for page-level loading.",fix:"Replace with Skeleton placeholders matching the eventual content shape.",uiNote:"Framework Skeleton + Form `loading={{ kind: 'skeleton' }}` handles cascading initial-fetch state."},{category:"interactivity",symptom:"No empty states \u2014 empty dashboard shows nothing.",fix:"Design a composed 'getting started' view: Empty primitive with title + description + next-action button."},{category:"interactivity",symptom:"`window.alert()` for errors.",fix:"Inline error in the relevant Field, OR toast for non-form errors, OR Dialog for blocking errors."},{category:"interactivity",symptom:"Dead links (`href='#'`).",fix:"Either link to real destinations or visually disable the button."},{category:"interactivity",symptom:"No indication of current page in navigation.",fix:"Style the active nav link distinctly.",uiNote:"Sidebar handles via `activeId` \u2014 pass it."},{category:"content",symptom:"Generic names \u2014 'John Doe', 'Jane Smith'.",fix:"Diverse, realistic names. For Japanese apps: \u7530\u4E2D \u592A\u90CE, \u4F50\u85E4 \u7F8E\u54B2, Nguy\u1EC5n Lan, Maria Cruz."},{category:"content",symptom:"Fake round numbers \u2014 '99.99%', '50%', '$100.00'.",fix:"Organic data: '47.2%', '$99.00', '+1 (312) 847-1928'."},{category:"content",symptom:"Placeholder brand names \u2014 Acme, Nexus, SmartFlow.",fix:"Invent contextual believable brands or use the consumer's real brand."},{category:"content",symptom:"AI copy clich\xE9s \u2014 'elevate', 'seamless', 'unleash', 'next-gen', 'game-changer', 'delve', 'tapestry', 'in the world of'.",fix:"Plain specific language. Numbers, nouns, verbs.",uiNote:"Framework's cardinal rule 9 bans this in framework docs; same discipline applies to consumer copy."},{category:"content",symptom:"Exclamation marks in success messages.",fix:"Remove. Be confident, not loud."},{category:"content",symptom:"'Oops!' or apologetic error messages.",fix:"Direct + specific: 'Connection failed. Please try again.' / '\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093'."},{category:"content",symptom:"Lorem Ipsum.",fix:"Real draft copy. Even rough placeholder beats Latin."},{category:"components",symptom:"Generic card look (border + shadow + white).",fix:"Remove border OR shadow OR background \u2014 keep ONE. Cards exist only when elevation communicates hierarchy."},{category:"components",symptom:"Always one filled + one ghost button.",fix:"Add text links / tertiary styles for variety.",uiNote:"Button has `variant='link'` for tertiary actions."},{category:"components",symptom:"3-card carousel testimonials with dots.",fix:"Replace with masonry wall of quotes, embedded social posts, or single rotating quote."},{category:"components",symptom:"Pricing table with 3 equal towers.",fix:"Highlight recommended tier with COLOR and emphasis, not just extra height."},{category:"components",symptom:"Modals for everything.",fix:"Use inline editing, Sheet (slide-over), or expandable Collapse for simple actions. Reserve Dialog for true blocking decisions."},{category:"components",symptom:"Footer link farm with 4 columns.",fix:"Simplify. Main nav paths + legally required links. No marketing kitchen sink."},{category:"iconography",symptom:"Lucide or Feather icons exclusively.",fix:"Use Phosphor (Bold / Fill), Heroicons, or a custom set. AI default tell.",uiNote:"Framework ships with lucide as locked dependency (rule 14). For editorial differentiation, layer Phosphor on top."},{category:"iconography",symptom:"Cliche icon metaphors \u2014 rocketship 'launch', shield 'security'.",fix:"Less obvious: bolt, fingerprint, spark, vault, gem."},{category:"iconography",symptom:"Stock 'diverse team in office' photo.",fix:"Real team photos, candid shots, or a consistent illustration style. Avatar initials fallback > generic stock person."},{category:"code-quality",symptom:"Div soup \u2014 no semantic HTML.",fix:"`<nav>`, `<main>`, `<article>`, `<aside>`, `<section>` for landmarks.",uiNote:"AppShell renders the canonical landmark structure automatically."},{category:"code-quality",symptom:"Inline styles mixed with CSS classes haphazardly.",fix:"Move styling into the project's system. Inline `style={{}}` only for layout / positioning (rule 29)."},{category:"code-quality",symptom:"Missing alt text on images.",fix:"Describe content for SR. Never leave `alt=''` or `alt='image'` on meaningful images."},{category:"code-quality",symptom:"Arbitrary z-index values like `9999`.",fix:"Establish a clean z-index scale in CSS variables."},{category:"omissions",symptom:"No legal links in footer.",fix:"Add Privacy Policy + Terms of Service."},{category:"omissions",symptom:"Dead ends in user flows \u2014 no 'back'.",fix:"Every page has a way back. Breadcrumb, back button, OR clear nav state."},{category:"omissions",symptom:"No custom 404 page.",fix:"Design a helpful branded 404 with a way home and search."},{category:"omissions",symptom:"No form validation.",fix:"Client-side validation via zod schema. Framework's Form + FormField handle field-level errors automatically."},{category:"omissions",symptom:"No 'skip to content' link.",fix:"Hidden skip-link, first focusable element. Essential for keyboard users.",uiNote:"AppShell renders one automatically."}],H=["1. Font swap \u2014 biggest instant improvement, lowest risk","2. Color palette cleanup \u2014 remove clashing / oversaturated colors","3. Hover + active states \u2014 makes the interface feel alive","4. Layout + spacing \u2014 proper grid, max-width, consistent padding","5. Replace generic components \u2014 swap cliche patterns for modern alternatives","6. Add loading, empty, error states \u2014 makes it feel finished","7. Polish typography scale + spacing \u2014 the premium final touch"],W=["Work with the existing tech stack. Do NOT migrate frameworks or styling libraries.","Do NOT break existing functionality. Test after every change.","Before importing any new library, check `package.json` first.","Keep changes reviewable + focused. Small targeted improvements over big rewrites.","Run the audit before fixing \u2014 listing issues first prevents accidental scope creep."];function _(t){return R.filter(a=>a.category===t)}var $="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",K=[{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function Y(t){return t?K.filter(a=>a.category===t):K}var X="node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (optional peers, TESTED range: playwright >=1.55 <2 [1.61.1] + @axe-core/playwright >=4.10 <5 [4.12.1] + axe-core >=4.10 <5 [4.12.1] + a chromium via `playwright install chromium`; --strict for a CI gate, --format json ALWAYS emits valid JSON with a status of ok|partial|error separating infra errors[] from product findings[], --rules to print this catalog)",Q=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function J(t){return t?Q.filter(a=>a.category===t):Q}var m={name:"@godxjp/ui-mcp",version:"19.3.1",godxUiCompatibility:"19.3.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var ee=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:"List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one's full API.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",description:"Installed @godxjp/ui version in the target project, e.g. '16.10.0'. Omit to just read the catalog's own version + compatible range."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:"Fuzzy-search primitives by name / tagline / prop. Returns ranked matches.",inputSchema:{type:"object",properties:{query:{type:"string"}},required:["query"]}},{name:"get_frame_coverage",description:"Verified preview-contract coverage for a component (issue #163). Answers 'is this state actually PROVEN?' \u2014 returns, per contract dimension (variants, tones, sizes, shapes, density, controlled/uncontrolled ownership, disabled/read-only/loading/empty/error/success, async retry/cancel/offline, responsive viewport matrix, RTL, long/localized content, keyboard/focus, accessible name/description/error, reduced motion / coarse touch), whether an EXECUTED case proves it (covered), whether nothing proves it (UNTESTED), or whether it cannot exist (not-applicable, with a reason). UNTESTED IS NOT A PASS: never infer that a component supports a state because an example renders. Call this before telling a user a component 'supports' anything. Omit `name` for the repo-wide summary and the tracked known gaps.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Public export name (e.g. 'Button', 'DataTable', 'CardFooter'). Omit for the repo-wide coverage summary."}}}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}];async function te(t,a){switch(t){case"list_skills":return ce();case"list_primitives":return ae(a.group);case"list_patterns":return ge();case"list_anti_ai_tells":return ye(a.category);case"list_redesign_checks":return we(a.category);case"list_audit_rules":return fe(a.category);case"list_visual_checks":return be(a.category);case"get_anti_ai_tell":return ve(String(a.name??""));case"get_redesign_check":return ke(String(a.symptom??""));case"get_skill_section":return oe(String(a.skill??""),String(a.section??""));case"get_component":return Ce(String(a.name??""),a.verbose===!0);case"get_pattern":return De(String(a.name??""));case"get_rule":return Oe(typeof a.number=="number"?a.number:void 0);case"get_vocab":return Ie(a.name==null?void 0:String(a.name));case"get_tokens":return Ne(a.category);case"list_consumer_skills":return he();case"get_consumer_skill":return pe(String(a.skill??""),String(a.section??""));case"route_consumer_task":return Z(String(a.task??""),{consumerOnly:!0});case"draft_bug_report":return ue(a);case"check_compatibility":return me(a.version==null?void 0:String(a.version));case"route_task":return Z(String(a.task??""));case"suggest_primitive":return Ee(String(a.use_case??""));case"search_components":return Re(String(a.query??""));case"get_frame_coverage":return Ae(a.name===void 0?void 0:String(a.name));case"lint_jsx":return Pe(String(a.jsx??""));default:return`Unknown tool: ${t}`}}function ce(){let t=`# Available skills (${x.length})
|
|
3957
|
+
and grid stay put. Layout stays stable, perceived speed improves.`}];function H(t){return D.filter(a=>a.category===t)}var R=[{category:"typography",symptom:"Inter / Roboto / Open Sans everywhere \u2014 the AI default.",fix:"Pick a font with character: Geist, Outfit, Cabinet Grotesk, Satoshi for sans. For editorial / creative \u2014 pair a serif heading (Newsreader, Lyon, Playfair) with a sans body.",uiNote:"Override --font-sans + --font-serif at the consumer's root CSS. Framework reads from these tokens."},{category:"typography",symptom:"Headlines lack presence \u2014 small + thin + default tracking.",fix:"Increase display size, tighten letter-spacing (-0.02em to -0.04em), reduce line-height (1.1). Headlines should feel HEAVY and INTENTIONAL.",uiNote:"Typography.Title size={1} for hero; override fontFamily + letterSpacing inline."},{category:"typography",symptom:"Body paragraphs full-width \u2014 hard to read.",fix:"Limit paragraph max-width to ~65ch. Increase line-height to 1.6+.",uiNote:"Wrap Typography.Paragraph in `<div style={{ maxWidth: '65ch' }}>`."},{category:"typography",symptom:"Only Regular (400) + Bold (700) weights \u2014 flat hierarchy.",fix:"Introduce Medium (500) + SemiBold (600) for subtle weight contrasts."},{category:"typography",symptom:"Numbers in proportional font \u2014 columns jitter in tables.",fix:"`font-variant-numeric: tabular-nums` for data, or a monospace font like Geist Mono.",uiNote:"Table primitive already uses `tabular-nums` on `.num` cells. For ad-hoc numeric labels, add the CSS prop manually."},{category:"typography",symptom:"Orphaned words \u2014 single word on the last line of a heading.",fix:"`text-wrap: balance` (h1/h2/h3) or `text-wrap: pretty` (body)."},{category:"typography",symptom:"Title Case On Every Header.",fix:"Use sentence case instead. More modern, easier to read."},{category:"color-surface",symptom:"Pure #000000 background.",fix:"Replace with off-black / dark charcoal / tinted dark (deep navy).",uiNote:"Framework dark theme already uses tinted dark values \u2014 verify the consumer's override didn't force pure black."},{category:"color-surface",symptom:"Oversaturated accent colors.",fix:"Keep saturation below 80%. Desaturate so accents BLEND with neutrals rather than scream."},{category:"color-surface",symptom:"More than one accent color competing.",fix:"Pick ONE. Remove the rest. Consistency beats variety in palette.",uiNote:"Set ONE `data-accent` at `<html>` root. Use semantic colors (success / warning / destructive) only for genuinely semantic content."},{category:"color-surface",symptom:"Purple/blue 'AI gradient' aesthetic \u2014 most common AI fingerprint.",fix:"Replace with neutral base + ONE considered accent. Drop the gradient entirely if it has no narrative purpose."},{category:"color-surface",symptom:"Generic black `box-shadow` everywhere.",fix:"Tint shadow to match background hue (e.g. cool gray bg \u2192 cool gray shadow). Colored shadows over pure black."},{category:"color-surface",symptom:"Random dark section breaking an otherwise light page.",fix:"Either commit to full dark mode OR keep light consistently. If contrast needed, use a SLIGHTLY darker shade of the same palette \u2014 not a sudden jump to #111."},{category:"color-surface",symptom:"Empty flat sections with no visual depth.",fix:"Add depth with an ambient gradient at 0.02-0.05 opacity, or background imagery you COMMIT to the repo. Never a hotlinked placeholder service: a third-party image that never settles blocks `networkidle`, which times out a page load outright \u2014 it cost this repo two docs pages failing every browser gate. Empty flat = unfinished."},{category:"layout",symptom:"Everything centered + symmetric.",fix:"Break symmetry: offset margins, mixed aspect ratios, left-aligned header over centered body."},{category:"layout",symptom:"Three equal card columns as feature row \u2014 the most generic AI layout.",fix:"Replace with 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry. The 3-equal-cols pattern is RED FLAG #1.",uiNote:"Use Bento Grid (custom CSS grid with `gridColumn: 'span N'`) instead of `<Grid cols={3}>` for hero sections."},{category:"layout",symptom:"`height: 100vh` causing iOS Safari jump.",fix:"Use `min-height: 100dvh` (dynamic viewport) instead."},{category:"layout",symptom:"No max-width container \u2014 content stretches edge-to-edge.",fix:"Add a container constraint (1200-1440px) with `margin: auto`. Or use `max-w-4xl / max-w-5xl` for content-heavy pages.",uiNote:"Framework's PageContent constrains via `var(--container-max-width)`. Consumer may override."},{category:"layout",symptom:"Cards forced to same height by flexbox.",fix:"Allow variable heights or use masonry when content varies.",uiNote:"Use Masonry primitive \u2014 handles variable heights without flexbox stretch."},{category:"layout",symptom:"Buttons at random vertical positions in card rows.",fix:"Pin CTAs to card bottom \u2014 same Y-position across the row regardless of content above.",uiNote:"Card's `actions` footer slot bottom-aligns automatically."},{category:"layout",symptom:"Feature lists starting at different vertical positions in pricing tables.",fix:"Fixed-height title/price block + consistent spacing above the feature list. Cards align across columns."},{category:"layout",symptom:"Dashboard ALWAYS has a left sidebar.",fix:"Consider top navigation, floating command menu, or collapsible panel. Sidebar isn't the only chrome.",uiNote:"Framework supports both \u2014 AppShell with sidebar slot is optional; can use Topbar-only for some flows."},{category:"interactivity",symptom:"No hover states on buttons.",fix:"Background shift, scale, or translate on hover \u2014 150-200ms ease.",uiNote:"Framework Button has built-in hover. If overridden \u2014 restore."},{category:"interactivity",symptom:"No active/pressed feedback.",fix:"`scale(0.98)` or `translateY(1px)` on `:active`. Simulates a physical click."},{category:"interactivity",symptom:"No focus ring (`outline: none`).",fix:"Restore visible `:focus-visible` ring. Accessibility requirement, not optional."},{category:"interactivity",symptom:"Generic circular spinner for page-level loading.",fix:"Replace with Skeleton placeholders matching the eventual content shape.",uiNote:"Framework Skeleton + Form `loading={{ kind: 'skeleton' }}` handles cascading initial-fetch state."},{category:"interactivity",symptom:"No empty states \u2014 empty dashboard shows nothing.",fix:"Design a composed 'getting started' view: Empty primitive with title + description + next-action button."},{category:"interactivity",symptom:"`window.alert()` for errors.",fix:"Inline error in the relevant Field, OR toast for non-form errors, OR Dialog for blocking errors."},{category:"interactivity",symptom:"Dead links (`href='#'`).",fix:"Either link to real destinations or visually disable the button."},{category:"interactivity",symptom:"No indication of current page in navigation.",fix:"Style the active nav link distinctly.",uiNote:"Sidebar handles via `activeId` \u2014 pass it."},{category:"content",symptom:"Generic names \u2014 'John Doe', 'Jane Smith'.",fix:"Diverse, realistic names. For Japanese apps: \u7530\u4E2D \u592A\u90CE, \u4F50\u85E4 \u7F8E\u54B2, Nguy\u1EC5n Lan, Maria Cruz."},{category:"content",symptom:"Fake round numbers \u2014 '99.99%', '50%', '$100.00'.",fix:"Organic data: '47.2%', '$99.00', '+1 (312) 847-1928'."},{category:"content",symptom:"Placeholder brand names \u2014 Acme, Nexus, SmartFlow.",fix:"Invent contextual believable brands or use the consumer's real brand."},{category:"content",symptom:"AI copy clich\xE9s \u2014 'elevate', 'seamless', 'unleash', 'next-gen', 'game-changer', 'delve', 'tapestry', 'in the world of'.",fix:"Plain specific language. Numbers, nouns, verbs.",uiNote:"Framework's cardinal rule 9 bans this in framework docs; same discipline applies to consumer copy."},{category:"content",symptom:"Exclamation marks in success messages.",fix:"Remove. Be confident, not loud."},{category:"content",symptom:"'Oops!' or apologetic error messages.",fix:"Direct + specific: 'Connection failed. Please try again.' / '\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093'."},{category:"content",symptom:"Lorem Ipsum.",fix:"Real draft copy. Even rough placeholder beats Latin."},{category:"components",symptom:"Generic card look (border + shadow + white).",fix:"Remove border OR shadow OR background \u2014 keep ONE. Cards exist only when elevation communicates hierarchy."},{category:"components",symptom:"Always one filled + one ghost button.",fix:"Add text links / tertiary styles for variety.",uiNote:"Button has `variant='link'` for tertiary actions."},{category:"components",symptom:"3-card carousel testimonials with dots.",fix:"Replace with masonry wall of quotes, embedded social posts, or single rotating quote."},{category:"components",symptom:"Pricing table with 3 equal towers.",fix:"Highlight recommended tier with COLOR and emphasis, not just extra height."},{category:"components",symptom:"Modals for everything.",fix:"Use inline editing, Sheet (slide-over), or expandable Collapse for simple actions. Reserve Dialog for true blocking decisions."},{category:"components",symptom:"Footer link farm with 4 columns.",fix:"Simplify. Main nav paths + legally required links. No marketing kitchen sink."},{category:"iconography",symptom:"Lucide or Feather icons exclusively.",fix:"Use Phosphor (Bold / Fill), Heroicons, or a custom set. AI default tell.",uiNote:"Framework ships with lucide as locked dependency (rule 14). For editorial differentiation, layer Phosphor on top."},{category:"iconography",symptom:"Cliche icon metaphors \u2014 rocketship 'launch', shield 'security'.",fix:"Less obvious: bolt, fingerprint, spark, vault, gem."},{category:"iconography",symptom:"Stock 'diverse team in office' photo.",fix:"Real team photos, candid shots, or a consistent illustration style. Avatar initials fallback > generic stock person."},{category:"code-quality",symptom:"Div soup \u2014 no semantic HTML.",fix:"`<nav>`, `<main>`, `<article>`, `<aside>`, `<section>` for landmarks.",uiNote:"AppShell renders the canonical landmark structure automatically."},{category:"code-quality",symptom:"Inline styles mixed with CSS classes haphazardly.",fix:"Move styling into the project's system. Inline `style={{}}` only for layout / positioning (rule 29)."},{category:"code-quality",symptom:"Missing alt text on images.",fix:"Describe content for SR. Never leave `alt=''` or `alt='image'` on meaningful images."},{category:"code-quality",symptom:"Arbitrary z-index values like `9999`.",fix:"Establish a clean z-index scale in CSS variables."},{category:"omissions",symptom:"No legal links in footer.",fix:"Add Privacy Policy + Terms of Service."},{category:"omissions",symptom:"Dead ends in user flows \u2014 no 'back'.",fix:"Every page has a way back. Breadcrumb, back button, OR clear nav state."},{category:"omissions",symptom:"No custom 404 page.",fix:"Design a helpful branded 404 with a way home and search."},{category:"omissions",symptom:"No form validation.",fix:"Client-side validation via zod schema. Framework's Form + FormField handle field-level errors automatically."},{category:"omissions",symptom:"No 'skip to content' link.",fix:"Hidden skip-link, first focusable element. Essential for keyboard users.",uiNote:"AppShell renders one automatically."}],j=["1. Font swap \u2014 biggest instant improvement, lowest risk","2. Color palette cleanup \u2014 remove clashing / oversaturated colors","3. Hover + active states \u2014 makes the interface feel alive","4. Layout + spacing \u2014 proper grid, max-width, consistent padding","5. Replace generic components \u2014 swap cliche patterns for modern alternatives","6. Add loading, empty, error states \u2014 makes it feel finished","7. Polish typography scale + spacing \u2014 the premium final touch"],W=["Work with the existing tech stack. Do NOT migrate frameworks or styling libraries.","Do NOT break existing functionality. Test after every change.","Before importing any new library, check `package.json` first.","Keep changes reviewable + focused. Small targeted improvements over big rewrites.","Run the audit before fixing \u2014 listing issues first prevents accidental scope creep."];function _(t){return R.filter(a=>a.category===t)}var Y="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",K=[{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function $(t){return t?K.filter(a=>a.category===t):K}var X="node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (optional peers, TESTED range: playwright >=1.55 <2 [1.61.1] + @axe-core/playwright >=4.10 <5 [4.12.1] + axe-core >=4.10 <5 [4.12.1] + a chromium via `playwright install chromium`; --strict for a CI gate, --format json ALWAYS emits valid JSON with a status of ok|partial|error separating infra errors[] from product findings[], --rules to print this catalog)",Q=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function J(t){return t?Q.filter(a=>a.category===t):Q}var m={name:"@godxjp/ui-mcp",version:"19.4.1",godxUiCompatibility:"19.4.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var ee=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:"List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one's full API.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",description:"Installed @godxjp/ui version in the target project, e.g. '16.10.0'. Omit to just read the catalog's own version + compatible range."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:"Fuzzy-search primitives by name / tagline / prop. Returns ranked matches.",inputSchema:{type:"object",properties:{query:{type:"string"}},required:["query"]}},{name:"get_frame_coverage",description:"Verified preview-contract coverage for a component (issue #163). Answers 'is this state actually PROVEN?' \u2014 returns, per contract dimension (variants, tones, sizes, shapes, density, controlled/uncontrolled ownership, disabled/read-only/loading/empty/error/success, async retry/cancel/offline, responsive viewport matrix, RTL, long/localized content, keyboard/focus, accessible name/description/error, reduced motion / coarse touch), whether an EXECUTED case proves it (covered), whether nothing proves it (UNTESTED), or whether it cannot exist (not-applicable, with a reason). UNTESTED IS NOT A PASS: never infer that a component supports a state because an example renders. Call this before telling a user a component 'supports' anything. Omit `name` for the repo-wide summary and the tracked known gaps.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Public export name (e.g. 'Button', 'DataTable', 'CardFooter'). Omit for the repo-wide coverage summary."}}}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}];async function te(t,a){switch(t){case"list_skills":return ce();case"list_primitives":return ae(a.group);case"list_patterns":return ge();case"list_anti_ai_tells":return ye(a.category);case"list_redesign_checks":return we(a.category);case"list_audit_rules":return fe(a.category);case"list_visual_checks":return be(a.category);case"get_anti_ai_tell":return ve(String(a.name??""));case"get_redesign_check":return ke(String(a.symptom??""));case"get_skill_section":return oe(String(a.skill??""),String(a.section??""));case"get_component":return Ce(String(a.name??""),a.verbose===!0);case"get_pattern":return De(String(a.name??""));case"get_rule":return Oe(typeof a.number=="number"?a.number:void 0);case"get_vocab":return Ne(a.name==null?void 0:String(a.name));case"get_tokens":return Ie(a.category);case"list_consumer_skills":return he();case"get_consumer_skill":return pe(String(a.skill??""),String(a.section??""));case"route_consumer_task":return Z(String(a.task??""),{consumerOnly:!0});case"draft_bug_report":return ue(a);case"check_compatibility":return me(a.version==null?void 0:String(a.version));case"route_task":return Z(String(a.task??""));case"suggest_primitive":return Ee(String(a.use_case??""));case"search_components":return Re(String(a.query??""));case"get_frame_coverage":return Ae(a.name===void 0?void 0:String(a.name));case"lint_jsx":return Pe(String(a.jsx??""));default:return`Unknown tool: ${t}`}}function ce(){let t=`# Available skills (${x.length})
|
|
3893
3958
|
|
|
3894
3959
|
`;t+="Each is tagged `[audience]` \u2014 `core` = building @godxjp/ui itself, `consumer` = building an app with it, `both`. App-devs: use `list_consumer_skills` to hide core material.\n\n",t+='Use `get_skill_section skill="..." section="..."` to drill in.\n\n';for(let a of x)t+=`## ${a.id} \u2014 ${a.name} \`[${a.audience}]\`
|
|
3895
3960
|
`,t+=`**When to use:** ${a.whenToUse}
|
|
@@ -3982,18 +4047,18 @@ ${X}
|
|
|
3982
4047
|
|
|
3983
4048
|
`,e+="_Static `list_audit_rules` first (cheap, every save) \u2192 THIS (before review) \u2192 human eyes for taste._\n\n";for(let o of a)e+=`- **${o.id}** (${o.severity}, ${o.category}) \u2014 _${o.standard}_
|
|
3984
4049
|
${o.fix}
|
|
3985
|
-
`;return e}function fe(t){let a
|
|
4050
|
+
`;return e}function fe(t){let a=$(t),e=`# Local UI-audit rules${t?` \u2014 ${t}`:""} (${a.length})
|
|
3986
4051
|
|
|
3987
4052
|
`;e+=`_Run BEFORE any visual review (warnings are agent guidance, non-blocking):_
|
|
3988
4053
|
`,e+=`\`\`\`
|
|
3989
|
-
${
|
|
4054
|
+
${Y}
|
|
3990
4055
|
\`\`\`
|
|
3991
4056
|
|
|
3992
4057
|
`;let o=a.reduce((r,i)=>((r[i.category]??=[]).push(i),r),{});for(let[r,i]of Object.entries(o)){e+=`## ${r}
|
|
3993
4058
|
`;for(let n of i)e+=`- **${n.id}** (${n.severity})${n.standard?` \u2014 _${n.standard}_`:""}
|
|
3994
4059
|
${n.fix}
|
|
3995
4060
|
`;e+=`
|
|
3996
|
-
`}return e}function ye(t){let a=t?
|
|
4061
|
+
`}return e}function ye(t){let a=t?H(t):D,e=`# AI tells to AVOID${t?` \u2014 ${t}`:""} (${a.length})
|
|
3997
4062
|
|
|
3998
4063
|
`;e+='_Compact list. Use `get_anti_ai_tell name="<name>"` for the full body + fix._\n\n';let o=a.reduce((r,i)=>((r[i.category]??=[]).push(i),r),{});for(let[r,i]of Object.entries(o)){e+=`## ${r}
|
|
3999
4064
|
`;for(let n of i)e+=`- ${n.name}
|
|
@@ -4015,7 +4080,7 @@ ${a.fix}
|
|
|
4015
4080
|
`}function we(t){let a=t?_(t):R,e=`# Redesign audit${t?` \u2014 ${t}`:""} (${a.length} checks)
|
|
4016
4081
|
|
|
4017
4082
|
`;t||(e+=`## Fix priority
|
|
4018
|
-
${
|
|
4083
|
+
${j.map(r=>r).join(`
|
|
4019
4084
|
`)}
|
|
4020
4085
|
|
|
4021
4086
|
`,e+=`## Rules
|
|
@@ -4041,7 +4106,7 @@ ${e.whenToUse}
|
|
|
4041
4106
|
|
|
4042
4107
|
## Sections
|
|
4043
4108
|
`;for(let i of e.sections)r+=`- \`${i.id}\` \u2014 ${i.tagline}
|
|
4044
|
-
`;return r}let o=
|
|
4109
|
+
`;return r}let o=V(t,a);if(!o){let r=`Section "${a}" not in skill "${t}". Available:
|
|
4045
4110
|
`;for(let i of e.sections)r+=`- \`${i.id}\` \u2014 ${i.tagline}
|
|
4046
4111
|
`;return r}return`# ${e.name} \u2192 ${o.title}
|
|
4047
4112
|
|
|
@@ -4049,7 +4114,7 @@ ${o.tagline}
|
|
|
4049
4114
|
|
|
4050
4115
|
${o.body}
|
|
4051
4116
|
|
|
4052
|
-
_Source: ${e.source}_`}var xe={Badge:["badge"],Button:["button"],Toggle:["toggle"],TagInput:["tag-input"],Card:["card"],StatCard:["stat-card"],Table:["table"],DataTable:["table"],Dialog:["dialog"],AlertDialog:["dialog"],Sheet:["dialog"],Drawer:["dialog"],Alert:["alert"],EmptyState:["empty-state"],Skeleton:["skeleton"],Pagination:["pagination"],Toolbar:["filter"],Breadcrumb:["breadcrumb"],Menubar:["menubar"],Progress:["progress"],TreeSelect:["tree"],Timeline:["timeline"],PasswordStrength:["password-strength"],PasswordInput:["password-strength"],Checkbox:["checkbox"],Switch:["switch"],Slider:["slider"],ColorPicker:["color-picker"],Command:["command"],Radio:["choice"],RadioGroup:["choice"],Field:["choice"],AppShell:["app-shell","sidebar","topbar"],Sidebar:["sidebar"],Topbar:["topbar"],Form:["form"],FormField:["form"],Input:["control"],Textarea:["control"],NumberInput:["control"],Select:["control","search-input"],Cascader:["control"],DatePicker:["control"],TimePicker:["control"],InputOTP:["control"]};function Se(t){let a=xe[t]??[t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()];return q.filter(e=>a.some(o=>e.name.startsWith(`--${o}-`)))}function Ce(t,a=!1){let e=
|
|
4117
|
+
_Source: ${e.source}_`}var xe={Badge:["badge"],Button:["button"],Toggle:["toggle"],TagInput:["tag-input"],Card:["card"],StatCard:["stat-card"],Table:["table"],DataTable:["table"],Dialog:["dialog"],AlertDialog:["dialog"],Sheet:["dialog"],Drawer:["dialog"],Alert:["alert"],EmptyState:["empty-state"],Skeleton:["skeleton"],Pagination:["pagination"],Toolbar:["filter"],Breadcrumb:["breadcrumb"],Menubar:["menubar"],Progress:["progress"],TreeSelect:["tree"],Timeline:["timeline"],PasswordStrength:["password-strength"],PasswordInput:["password-strength"],Checkbox:["checkbox"],Switch:["switch"],Slider:["slider"],ColorPicker:["color-picker"],Command:["command"],Radio:["choice"],RadioGroup:["choice"],Field:["choice"],AppShell:["app-shell","sidebar","topbar"],Sidebar:["sidebar"],Topbar:["topbar"],Form:["form"],FormField:["form"],Input:["control"],Textarea:["control"],NumberInput:["control"],Select:["control","search-input"],Cascader:["control"],DatePicker:["control"],TimePicker:["control"],InputOTP:["control"]};function Se(t){let a=xe[t]??[t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()];return q.filter(e=>a.some(o=>e.name.startsWith(`--${o}-`)))}function Ce(t,a=!1){let e=N(t);if(!e)return`Component "${t}" not found. Use \`list_primitives\` to discover.`;let o=`# ${e.name}
|
|
4053
4118
|
|
|
4054
4119
|
**Group:** ${e.group}`,r=e.importPath??`@godxjp/ui/${e.group==="providers"?"app":e.group}`;o+=` \xB7 **Import:** \`import { ${e.name} } from "${r}"\`
|
|
4055
4120
|
|
|
@@ -4140,7 +4205,7 @@ ${e.example}
|
|
|
4140
4205
|
`:"")+"Call `get_frame_coverage` with no name for the repo-wide summary.\n"}return`# ${e.name} \u2014 contract coverage
|
|
4141
4206
|
|
|
4142
4207
|
**Group:** ${e.group}
|
|
4143
|
-
${re(e,e.name)}`}function De(t){let a=E(t);if(!a){let e=
|
|
4208
|
+
${re(e,e.name)}`}function De(t){let a=E(t);if(!a){let e=U(t);if(e.length===0)return`Pattern "${t}" not found.`;let o=`Pattern "${t}" not found. Closest:
|
|
4144
4209
|
`;for(let r of e)o+=`- ${r.name} \u2014 ${r.tagline}
|
|
4145
4210
|
`;return o}return`# Pattern: ${a.name}
|
|
4146
4211
|
|
|
@@ -4160,7 +4225,7 @@ ${e.body}
|
|
|
4160
4225
|
|
|
4161
4226
|
${e.body}
|
|
4162
4227
|
|
|
4163
|
-
`;return a}function
|
|
4228
|
+
`;return a}function Ne(t){if(t){let e=B(t);if(!e)return`Vocab "${t}" not found.`;let o=`# ${e.name}
|
|
4164
4229
|
|
|
4165
4230
|
${e.concept}
|
|
4166
4231
|
|
|
@@ -4178,7 +4243,7 @@ ${e.concept}
|
|
|
4178
4243
|
|
|
4179
4244
|
Values: ${e.values.map(o=>`\`${o}\``).join(" | ")}
|
|
4180
4245
|
|
|
4181
|
-
`;return a}function
|
|
4246
|
+
`;return a}function Ie(t){let a=t?I(t):C;if(a.length===0)return`No tokens${t?` in "${t}"`:""}.`;let e=`# Design tokens${t?` \u2014 ${t}`:""}
|
|
4182
4247
|
|
|
4183
4248
|
`,o=a.reduce((r,i)=>((r[i.category]??=[]).push(i),r),{});for(let[r,i]of Object.entries(o)){e+=`## ${r}
|
|
4184
4249
|
|
|
@@ -4186,7 +4251,7 @@ Values: ${e.values.map(o=>`\`${o}\``).join(" | ")}
|
|
|
4186
4251
|
|---|---|---|
|
|
4187
4252
|
`;for(let n of i)e+=`| \`${n.name}\` | ${n.role} | ${n.tier} |
|
|
4188
4253
|
`;e+=`
|
|
4189
|
-
`}return e}function Z(t,a){if(!t.trim())return"Describe the task (e.g. 'design a premium agency hero', 'audit existing settings page').";let e=
|
|
4254
|
+
`}return e}function Z(t,a){if(!t.trim())return"Describe the task (e.g. 'design a premium agency hero', 'audit existing settings page').";let e=G(t,a),o=`# Routing "${t}"${a?.consumerOnly?" (consumer)":""}
|
|
4190
4255
|
|
|
4191
4256
|
`;for(let r of e)o+=`- **skill:** \`${r.skill}\`, **section:** \`${r.section}\`
|
|
4192
4257
|
${r.why}
|
|
@@ -4202,7 +4267,7 @@ Values: ${e.values.map(o=>`\`${o}\``).join(" | ")}
|
|
|
4202
4267
|
`;for(let r of a)o+=`- ${r}
|
|
4203
4268
|
`;return o+=`
|
|
4204
4269
|
Note: heuristic only \u2014 not a substitute for the full CI gate.
|
|
4205
|
-
`,o}var ne=u.length,ie=[{uri:"godx-ui://compatibility",name:"Package compatibility",description:"MCP package/server version and the compatible @godxjp/ui release range.",mimeType:"application/json"},{uri:"godx-ui://components",name:"All components",description:"Full component catalog as JSON \u2014 name, group, tagline, props, example, rules.",mimeType:"application/json"},{uri:"godx-ui://prop-vocabulary",name:"Shared prop vocabulary",description:"Cross-cutting prop types (SizeProp, StatusProp, ColorProp, LoadingProp, \u2026) as JSON.",mimeType:"application/json"},{uri:"godx-ui://tokens",name:"All design tokens",description:"Every CSS variable + role + value + axis as JSON.",mimeType:"application/json"},{uri:"godx-ui://rules",name:`Cardinal rules (${ne})`,description:`The ${ne} binding rules from CLAUDE.md as Markdown.`,mimeType:"text/markdown"},{uri:"godx-ui://patterns",name:"Code patterns",description:"Canonical pattern catalog (registration-form, settings-page, data-table, \u2026) as JSON.",mimeType:"application/json"}];async function se(t){if(t==="godx-ui://compatibility")return JSON.stringify({mcpVersion:m.version,serverVersion:m.version,compatibleUi:m.godxUiCompatibility,policy:"UI and MCP are released from the same source commit and minor release train."},null,2);if(t==="godx-ui://components")return JSON.stringify(v,null,2);if(t.startsWith("godx-ui://components/")){let a=t.slice(21),e=
|
|
4270
|
+
`,o}var ne=u.length,ie=[{uri:"godx-ui://compatibility",name:"Package compatibility",description:"MCP package/server version and the compatible @godxjp/ui release range.",mimeType:"application/json"},{uri:"godx-ui://components",name:"All components",description:"Full component catalog as JSON \u2014 name, group, tagline, props, example, rules.",mimeType:"application/json"},{uri:"godx-ui://prop-vocabulary",name:"Shared prop vocabulary",description:"Cross-cutting prop types (SizeProp, StatusProp, ColorProp, LoadingProp, \u2026) as JSON.",mimeType:"application/json"},{uri:"godx-ui://tokens",name:"All design tokens",description:"Every CSS variable + role + value + axis as JSON.",mimeType:"application/json"},{uri:"godx-ui://rules",name:`Cardinal rules (${ne})`,description:`The ${ne} binding rules from CLAUDE.md as Markdown.`,mimeType:"text/markdown"},{uri:"godx-ui://patterns",name:"Code patterns",description:"Canonical pattern catalog (registration-form, settings-page, data-table, \u2026) as JSON.",mimeType:"application/json"}];async function se(t){if(t==="godx-ui://compatibility")return JSON.stringify({mcpVersion:m.version,serverVersion:m.version,compatibleUi:m.godxUiCompatibility,policy:"UI and MCP are released from the same source commit and minor release train."},null,2);if(t==="godx-ui://components")return JSON.stringify(v,null,2);if(t.startsWith("godx-ui://components/")){let a=t.slice(21),e=N(a);if(!e)throw new Error(`Component not found: ${a}`);return ze(e)}if(t==="godx-ui://prop-vocabulary")return JSON.stringify(k,null,2);if(t==="godx-ui://tokens")return JSON.stringify(C,null,2);if(t.startsWith("godx-ui://tokens/")){let a=t.slice(17);return JSON.stringify(I(a),null,2)}if(t==="godx-ui://rules"){let a=`# Cardinal rules (${u.length})
|
|
4206
4271
|
|
|
4207
4272
|
`;for(let e of u)a+=`## ${e.number}. ${e.title}
|
|
4208
4273
|
|
|
@@ -4237,4 +4302,4 @@ ${t.tagline}
|
|
|
4237
4302
|
\`\`\`tsx
|
|
4238
4303
|
${t.example}
|
|
4239
4304
|
\`\`\`
|
|
4240
|
-
`,a}async function
|
|
4305
|
+
`,a}async function Ve(){let t=new Le({name:"godx-ui-mcp",version:m.version},{capabilities:{tools:{},resources:{}}});t.setRequestHandler(qe,async()=>({tools:ee})),t.setRequestHandler(Fe,async e=>{let{name:o,arguments:r}=e.params;return{content:[{type:"text",text:await te(o,r??{})}]}}),t.setRequestHandler(Be,async()=>({resources:ie})),t.setRequestHandler(Ue,async e=>{let{uri:o}=e.params,r=await se(o);return{contents:[{uri:o,mimeType:o.endsWith(".json")?"application/json":"text/markdown",text:r}]}});let a=new Me;await t.connect(a),console.error("[godx-ui-mcp] connected (stdio)")}Ve().catch(t=>{console.error("[godx-ui-mcp] fatal:",t),process.exit(1)});
|