# App foundations
> What @sdxc/ui/app solves, the rules its parts share for URLs, saving, feedback and keys, and every variable it reads.

The screens of a signed-in product: an app shell, page headers, lists with filters and bulk actions, data tables, dashboards, detail sheets and split views, settings, editors, toasts and a keyboard layer.

It solves the parts every app re-builds by hand, and the judgment behind them: a sidebar that remembers its width, a filtered list that is a link, selection that turns pagination into a bulk bar, saving that is quiet until it fails, and states typed by cause.

Each part renders working server HTML. Small islands add what the platform cannot do: ⌘B, j/k, ⌘K, the bulk bar's count, save timings and toasts.

Import the components from `@sdxc/ui/app` and the theme once from `@sdxc/ui/theme.css`.

## When to reach for it

- A dashboard, an admin panel, a CMS, an inbox or any screen behind a sign-in.
- A list people filter, sort, select and act on: FilterBar, Table, SelectionBar.
- A record opened beside its list (DetailSheet, SplitView) or on its own page (PageHeader, PropertyList, Timeline).
- Settings and editors: SettingsLayout, SettingsSection, SaveBar, SaveStatus, DangerZone, EditorLayout.
- Metrics with context: StatCard, Sparkline, StatusStrip, RangePicker, Chart.

## Shared ideas

- **State in the URL.** Filters, sort, view, range and the open record are search params written by GET forms and links, so a view is a link and Back undoes it.
- **Preferences the server renders.** The sidebar's collapse and a split view's widths live in cookies (`readAppShellCookie`, `readSplitViewLayout`), so the first paint is right.
- **One selection contract.** Any list that renders `data-selection-item` checkboxes works with `SelectionBar`; `Table.Select` and `Table.SelectAll` render them for you.
- **Saving is a model.** `SettingsSection` saves `explicit` or `instant`, never both; `SaveStatus` holds Saving… for 1s; `OptimisticToggle` flips at once and admits failure.
- **Feedback by place.** Inline first, `Banner` for state that needs resolving, `Toasts` last: one region for server flashes and client toasts, three visible, Undo held 8s.
- **Typed states.** `Empty` has `kind` first-run, no-results, cleared, permission and error; `Skeleton` presets keep the real row and card heights; `aria-busy` dims a refetching table.
- **One keyboard layer.** `shortcutRegistry()` ranks modal > sheet > page; `data-shortcut` registers keys from HTML; `KeyboardHelp` lists them; `CommandPalette` opens on ⌘K.
- **No copy in the kit.** Every string a part shows or announces is a `labels` prop, so an app translates them all.
- **Names required.** `Table`, `Tree`, `GridList`, `Menubar` and the rest warn in development without `aria-label`.
- **Series by slot.** `color={n}` picks `--ui-chart-<n>`, so a legend always matches its series.
- **Targets.** Drag handles, resize handles and toast close buttons grow to `--ui-target-size`.
- **Motion.** The rail peek, toast stack, save status and every transition stop under `prefers-reduced-motion`.

## CSS variables

Every custom property the namespace reads or declares. Reads every role, brand, neutral, success, warning and danger, for fills, borders and toasts, and the raw scales through the chart slots. The roles are documented on Theming.

### Charts

The eight slots are declared in `theme.css`; the rest fall back to the default in the code.

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-chart-1 … --ui-chart-8` | `brand, success, warning, danger and neutral 600, then brand, success and warning 400; one step lighter in dark` | The eight categorical series colors; color={n} picks slot n. |
| `--ui-chart-line-width` | `2px` | Stroke width of a Chart.Line. |
| `--ui-chart-area-width` | `2px` | Stroke width of a Chart.Area's outline. |
| `--ui-chart-area-fill-opacity` | `0.25` | Opacity of the fill under a Chart.Area. |
| `--ui-chart-point-radius` | `0.1875rem` | Radius of every focusable point marker. |
| `--ui-chart-tooltip-gap` | `0.5rem` | Space between the active point and the tooltip. |

### Shell and page header

Read by `AppShell`, `PageHeader` and every part that sticks under the shell's top bar.

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-app-sidebar-width` | `16rem` | The expanded sidebar column. |
| `--ui-app-rail-width` | `3.25rem` | The collapsed icon rail. |
| `--ui-app-sheet-width` | `18rem, at most 85vw` | The sidebar as a phone sheet. |
| `--ui-app-inset-gap` | `0.5rem` | Space between the frame's edge and the content card. |
| `--ui-app-inset-radius` | `0.75rem` | Corners of the content card. |
| `--ui-app-inset-shadow` | `0 1px 3px rgb(0 0 0 / 0.04)` | The soft lift under the content card; its border is the card's only edge. |
| `--ui-app-nav-row-size` | `2rem` | Height of a nav row. |
| `--ui-app-header-height` | `3.5rem` | The shell's top bar. |
| `--ui-app-content-padding` | `1rem; 1.5rem from 48rem` | Padding of the shell's main region. |
| `--ui-app-sticky-top` | `0px; the shell sets it to its header height` | Where sticky page headers, table headers and the settings nav stick. |
| `--ui-app-gutter` | `set by the shell's main region` | Its padding, so a sticky PageHeader can bleed to the card's edges. |

### Lists and tables

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-table-row-block-size` | `2.5rem compact, 3.5rem comfortable` | Row height, set by Table density. |
| `--ui-table-header-block-size` | `2.5rem` | Header row height. |
| `--ui-table-bg` | `var(--ui-bg, Canvas)` | Surface behind the sticky header and pinned cells. |
| `--ui-table-id-max-inline-size` | `14ch` | Width of a middle-truncated id cell. |
| `--ui-table-bar-min-inline-size` | `6rem` | Smallest width of an in-cell data bar. |
| `--ui-selection-bar-offset` | `1rem` | Gap between the floating SelectionBar and the bottom of its scroll box. |
| `--ui-status-dot-size` | `0.5rem` | Diameter of a StatusDot. |

### Metrics

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-stat-value-size` | `1.875rem; 1.5rem ruled` | A StatCard's value. |
| `--ui-stat-card-padding` | `1.25rem` | A StatCard's padding. |
| `--ui-stat-delta-block-size` | `1.375rem` | Height of the delta pill. |
| `--ui-stat-chart-block-size` | `2.5rem` | Height of a StatCard's chart slot. |
| `--ui-sparkline-block-size` | `1.5rem` | Height of a Sparkline. |
| `--ui-sparkline-line-width` | `1.5px` | Its stroke. |
| `--ui-sparkline-dot-size, --ui-sparkline-area-opacity` | `0.375rem, 0.16` | Its last-point dot and area fill. |
| `--ui-status-strip-block-size` | `2rem` | Height of a StatusStrip bar. |
| `--ui-status-strip-gap` | `2px; 1px under a 30rem container` | Gap between its bars. |
| `--ui-chart-comparison-color, --ui-chart-comparison-dash` | `neutral muted ink, 4 4` | A comparison series. |
| `--ui-chart-threshold-width, --ui-chart-threshold-dash` | `1px, 6 4` | A Chart.Threshold line. |
| `--ui-chart-band-opacity, --ui-chart-band-opacity-dark` | `0.08, 0.12` | Fill of a Chart.Band in a light and a dark colour scheme. |
| `--ui-chart-band-edge-width, --ui-chart-band-edge-dash` | `1px, 4 3` | The dashed rules at a Chart.Band's edges. |

### Detail, split views, settings and editors

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-app-detail-sheet-width` | `clamp(28rem, 36cqi, 36rem)` | Width of a DetailSheet. |
| `--ui-app-detail-sheet-top` | `0px` | Offset of a DetailSheet below an app header. |
| `--ui-app-list-width` | `20rem` | Default width of a SplitView's list pane. |
| `--ui-property-list-label-width` | `9rem` | A PropertyList's label column. |
| `--ui-timeline-icon-box` | `1.5rem` | The icon box a Timeline's rail runs through. |
| `--ui-settings-nav-width` | `13rem` | The SettingsLayout nav column. |
| `--ui-settings-measure` | `42rem` | The settings content column. |
| `--ui-editor-measure` | `42.5rem` | An EditorLayout's writing column. |
| `--ui-editor-sidebar-width` | `18rem` | An EditorLayout's properties sidebar. |

### Feedback and keyboard

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-toast-peek` | `0.625rem` | How far each folded toast peeks out of the stack. |
| `--ui-empty-icon-size` | `3rem; 2rem compact` | An Empty state's icon well. |
| `--ui-skeleton-list-row-block-size` | `3.5rem` | A Skeleton.List row. |
| `--ui-skeleton-shimmer-duration` | `1.6s` | One shimmer sweep. |
| `--ui-app-palette-width` | `40rem, at most the viewport less 1rem` | The CommandPalette. |
| `--ui-app-palette-top` | `15vh` | Gap above the CommandPalette. |
| `--ui-app-keyboard-help-width` | `40rem` | The KeyboardHelp sheet. |

### Trees, panes and toasts

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-tree-indent` | `1.25rem` | Indent added per level of a `Tree` row. |
| `--ui-table-body-intrinsic-size` | `32rem` | Height an off-screen Table.Body reserves while its layout is skipped. |
| `--ui-grid-list-intrinsic-size` | `32rem` | Height off-screen `GridList` rows reserve while their layout is skipped. |
| `--ui-skeleton-block-size` | `1rem` | Height of a `Skeleton` placeholder. |
| `--ui-resizable-handle-size` | `0.75rem` | Thickness of the track a Resizable.Handle reserves. |
| `--ui-resizable-panel-min-size, --ui-resizable-panel-max-size` | `set by Resizable.Panel from minSize and maxSize (0% and 100%)` | Smallest and largest share a pane may take. |
| `--ui-toast-z` | `50` | Stacking order of the toast region. |
| `--ui-toast-bg` | `set by Toast from color: that role's bg-tint` | Fill of a toast, mixed with transparency behind its blur. |

### Shared tokens

Read through `@sdxc/u`'s helpers. `--ui-target-size` and `--ui-spacing` are declared by the themes; radius and shadow fall back to the default an app overrides by declaring the variable.

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-target-size` | `1.5rem; 2.75rem on a coarse pointer` | Smallest pointer target of drag handles, resize handles and a toast's close. |
| `--ui-spacing` | `0.25rem` | Unit of every numeric gap and padding, and of a leaf tree row's gutter. |
| `--ui-text-sm` | `0.875rem` | Size of tree rows. |
| `--ui-radius-md, --ui-radius-lg, --ui-radius-full` | `0.375rem, 0.5rem, 9999px` | Corners of skeletons and chart tooltips, toasts, and legend swatches. |
| `--ui-shadow-md` | `0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)` | Elevation of a chart tooltip. |
| `--ui-shadow-lg` | `0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)` | Elevation of a toast. |

### Set by the components

Written by a mixin or from a prop and read back by the part itself. Leave them unset.

| Variable | Default | What it controls |
| --- | --- | --- |
| `--ui-chart-tooltip-x, --ui-chart-tooltip-y` | `0px, written by chartTooltip()` | Where the tooltip sits, from the active point. |
| `--ui-resizable-panel-size` | `set from defaultSize; resizeHandle() rewrites it while dragging` | A pane's share of the group. |
