# Osysharp.Ui (the UI kit)

> The bundled UI kit — ready-made styled controls like `Button`, the shared design-system vocabularies (`Tone`, `Size`) and a starter theme — reaches a whole app from ONE line: `use Osysharp.Ui;` in the manifest. No per-file `using` anywhere after that. You reference a kit control just like your own components, and you can fork any control by declaring one with the same name.

<!-- id: ui-kit · area: ui · stability: stable · html: https://osysharp.com/reference/ui/kit/ -->

## Summary        {#summary}
The **UI kit** — a bundled set of ready-made, styled controls and the shared vocabularies they use — reaches your
whole app from **one line in the manifest**:

```osy title="app.osy — the one line that brings the kit in" test app=ui-kit-reference
app Todo {
  use Osysharp.Ui;          // the whole opt-in, for every file in the app

  model "model/**/*.osy";
  tests "tests/**/*.test.osy";
}
```

```osy title="model/home.osy — and then no `using` anywhere" test app=ui-kit-reference
[Route("/")]
[AllowAnonymous]
component Home() {
  render {
    Card {
      Badge("Ready", tone: Tone.Success);
    }
  }
}
```

The manifest is the only place it is written. **No `using` in any file** — the kit is then in scope everywhere in
the app, which is the point of referencing it there rather than per file, and a control drops straight into a
`render` block beside your own:

```osy title="a kit control, dropped straight in" test app=ui-kit
[Route("/")]
[AllowAnonymous]
[Render(CSR)]
component Home() {
  action Save() { }
  render {
    Stack(align: Align.Center) {
      Button("Save", onPress: Save);
    }
  }
}
```

`Button` is a kit control — it isn't declared in your app, and this file imports nothing: the manifest's `use` did
that for every file at once. (`align: Align.Center` is built in too, and always was. See [layout primitives](https://osysharp.com/reference/ui/layout/).)

⚠ **An app that references no kit does not get one**, and the refusal names the kit rather than calling its own
control unknown: *unknown element 'Button' — the `Osysharp.Ui` kit ships it, and this app references no kit.* Add
the `use` above, or `using Osysharp.Ui;` in the one file that needs it. A throwaway snippet with no manifest at all
keeps the kit — it has nothing to reference *with*.

⚑ **`Button` is the one name the kit takes over.** The renderer also has a lower-level `Button` ATOM — `onClick`,
no label — and the kit's control shadows it. If you specifically want the primitive, name it `Osysharp.Button(…)`;
that `Osyrin.` prefix reaches any atom whose name a control shadows. You will rarely want it.

## Signature      {#signature}
```osy syntax
// in app.osy — once, for the whole app:
app MyApp { use Osysharp.Ui; }
// …with a version PIN, which is the only thing the suffix adds:
app MyApp { use Osysharp.Ui@3; }
// in ONE file instead, when only that file wants it:
using Osysharp.Ui;
```

The kit contributes three things to your app:

- **Controls** — styled components you reference by name.

  *Input and action*

  ⚑ **Every `value:` below is a two-way binding, and it takes one of exactly three things**: an assignable field of
  your component, a field of an **entity** row, or a `Binding<T>` you were handed. A field of a `class` value is
  **refused** — there is no row to write the edit through — so a list a person can EDIT is a list of entities.
  See [[ui-component#two-way]].

  | | |
  |---|---|
  | `Button(label, onPress, tone, size, disabled, canPress, whenDenied, confirm, confirmText)` | The pressable action. One filled `Primary` per view; `Ghost` for the rest. `canPress:` takes a declared **policy** — the button greys out and says `whenDenied` when it does not hold for the caller. **For an action that cannot be taken back** — signing, closing, sending, deleting — `confirmText: "Sign the minutes? They cannot be changed."` asks first (`ConfirmStep`, a small dialog: Cancel or the action) and runs `onPress` only on the second press; `confirm: true` asks "<label>?". A `Button` does not ask by tone, because a danger `Button` is as often the confirmation's own action. |
  | `Field(label, value, placeholder, hint, error, type, disabled)` | Labelled text input. `value` is a two-way binding, so typing writes straight to what you bound. The `label` also NAMES the input for a screen reader (and for `Ui.Fill`), and an `error` marks it invalid. `type:` is the kind of text a browser knows about — `"password"` masks what is typed, `"email"` / `"tel"` change the keyboard a phone offers. It is a CLOSED set, checked at compile time when written as a literal: `text`, `password`, `email`, `number`, `tel`, `url`, `search`, `date`, `time`, `datetime-local`, `month`, `week`, `color`, `range`. A checkbox, a radio group, a file picker and a submit button are their own controls (`Checkbox`, `Dropdown`, `Upload`, `Button`) rather than a `type:` — each keeps its state somewhere other than `value`, so the binding would be wired to nothing. |
  | `NumberField(label, value, min, max, unit, prefix, placeholder, hint, error, disabled)` | A whole number. Binds an `int` directly, so what is typed is stored AS a number — there is no string draft to parse. `min`/`max` are the field's RANGE: they bound the steppers and mark the box invalid the moment what is typed falls outside, so the reader sees it as they type rather than when the save is refused. `prefix` prints before the box (a currency symbol) and `unit` after it: `NumberField("How often", value: draft.EveryDays, min: 1, unit: "days")`. |
  | `DecimalField(label, value, min, max, unit, prefix, placeholder, hint, error, disabled)` | The same control over a `decimal` — money, a measurement, a rate: `DecimalField("Price", value: line.Price, min: 0m, prefix: "£")`. Two controls rather than one generic because a generic cannot do arithmetic over a number type it does not know. Both read and write their digits in the app's culture, so a reader under `app.DefaultCulture = "sv-SE"` sees and types `12,5` where one under `en-GB` sees and types `12.5` — see [Culture formatting — ToString(format, culture)](https://osysharp.com/reference/stdlib/culture-formatting/). |
  | `DatePicker(label, value, hint, error, disabled, clearable, placeholder)` | A date field that opens a real month calendar — not `<input type=date>`, which cannot be styled and looks different in every browser. `‹`/`›` move a month, the chosen day is a filled circle, today carries a ring, and days from the neighbouring months are muted. The backdrop closes it: `DatePicker("Due", value: draft.DueOn)`. It binds a `DateOnly` **or a `DateOnly?`** — an optional date shows `placeholder` ("Pick a date") while empty and opens on this month, and `clearable: true` adds a *Clear* that empties it again: `DatePicker("First day", value: startsOn, clearable: true)`. |
  | `TimePicker(label, value, minuteStep, use12Hour, hint, error, disabled)` | A time field that opens hour and minute columns. `minuteStep` is 5 by default, so quarter past is two clicks rather than fifteen; `use12Hour` switches the trigger to `h:mm tt`: `TimePicker("Opens", value: draft.OpensAt, minuteStep: 15)`. |
  | `DateTimePicker(label, value, minuteStep, hint, error, disabled)` | The calendar with a time strip under it, over one `DateTime` — a start, a deadline, a moment. Click the `14 : 30` strip to open the columns: `DateTimePicker("Starts", value: draft.StartsAt)`. For a calendar day with no time of day, use `DatePicker`. |
  | `DurationField(label, value, hint, error, disabled)` | A duration over a `TimeSpan` — how long something lasts or waits. Closed it reads "4 hours" or "7 days" (the largest whole unit); it opens to *How many* and a unit (minutes, hours, days), and *Done* sets it, so a value changed elsewhere (a Discard) is what the next opening shows: `DurationField("Valid for", value: setup.ValidFor)`. A `[Settings]` section draws every `TimeSpan` field with it — see [[Settings] and [Setting]: a settings section declared on its row, each field drawn by its type](https://osysharp.com/reference/ui/settings-declared/). |
  | `Dropdown(label, value, options, placeholder, disabled)` | A styled select — `(label, value, …)` like every other field control here. Bind an **enum** and there is nothing else to say, because the options are its members: `Dropdown("Status", value: order.Status)`. Give it `options:` and it picks from **any rows** instead, with your own template per row: `Dropdown("Lead", value: project.Lead, options: people, placeholder: "Choose a lead…") { p => Avatar(p.Initials); Text(p.Name); }`. `label` is required and is what the control is announced and addressed by; only you know whether it means "Status" or "Order status". Clicking outside closes it, and so does Escape. |
  | `RadioGroup(label, value, options, hint, disabled)` | One choice of a few, all in view — the same shape as `Dropdown`, for two to six options a person should compare before choosing. See [RadioGroup](https://osysharp.com/reference/ui/radio-group/). |
  | `TimeZonePicker(label, value, disabled)` | A time-zone picker over the app's declared zones, each read as its place and region ("Stockholm — Europe"); binds a `TimeZoneInfo`. In a test: `Ui.Select("Time zone", TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm"))`. |
  | `ActionArgumentField(p, choices, value)` | The input for one parameter of an `[Action]`, as its kind takes it — what an editor shows when a person sets up a step that runs an action later (an automation's, a macro's): a dropdown when `choices` offers any, otherwise yes/no, a number, a date or time, a duration, an id or JSON. `value` is the text an `ActionValue` stores; check it with `Actions.Check` before keeping it. In a test: `Ui.Fill("Due", "2026-11-02")`. |
  | `Option(label, selected)` | One row of a dropdown panel — the tick, and what a screen reader is told. Its content is the default slot, so `Option(selected: …) { Avatar(…); Text(…); }` draws your own row without losing the part that is not visible. Build your own picker from it. |
  | `Checkbox(label, value, onToggle, disabled)` | On/off in a form. The label is part of the hit target, and is also what it is announced as. |
  | `Switch(label, value, onToggle, disabled)` | On/off that takes effect **now** rather than on save. |
  | `ThemeToggle(glyph, label)` | Flips light/dark and remembers the choice. `glyph` is the button FACE (an emoji by default); `label` is what it is announced as — some apps say "Theme", some "Appearance". |

  *Structure*

  | | |
  |---|---|
  | `Card(title, subtitle)` | A raised surface. The block you wrap is its body; `slot actions { … }` puts controls in the header. |
  | `PageHead(title, subtitle)` | A page's heading row — title left, your actions right, hairline under. |
  | `Toolbar()` | A strip of controls with consistent spacing. Add a `Spacer()` to split left from right. |
  | `ListRow()` | One row of a list, with the between-rows hairline and rhythm. |
  | `Tabs()` · `Tab(label, selected, onPress, disabled)` | A tab strip. You own which tab is current. |
  | `Divider(vertical)` · `Spacer()` | A hairline, and a flex spacer that pushes what follows to the far end. |

  *Type roles* — say what the text **is**; the size follows.

  | | |
  |---|---|
  | `MoreText(text, summary, muted, about)` | Long text that opens on demand: one line cut with an ellipsis and "More" beside it, the whole text on a press. For a table cell or a caveat that would otherwise set the height of the row. |
  | `PageTitle(text)` · `CardTitle(text)` · `Heading(text, level)` | A page's level-1 heading, a card's level-2 one, and a heading of any level 1–6 in the type scale — headings a screen reader lists, not only large type ([Heading — a page's outline, h1 to h6](https://osysharp.com/reference/ui/headings/)). |
  | `TextAreaField(label, value, placeholder, hint, error, rows, disabled)` | A labelled MULTI-LINE input — a reason, a note, a description. Everything `Field` does, plus `rows` for how tall it starts. Named for the kit's convention (`NumberField`, `DecimalField`); a control called `TextArea` would shadow the atom of that name. |
  | `TextLink(label, to, tone)` | A navigation link — `to` is the route, `label` is what the reader sees. The `Link` ATOM takes the HREF as its first positional, which is the shape HTML habits get backwards. |
  | `SectionLabel(text)` | The small uppercase label above a group. |
  | `FieldLabel(text)` · `Hint(text)` | A field's label, and secondary/help text. |
  | `Strong(text)` · `Metric(text)` | Emphasised body text, and a big stat number. |

  *Status and feedback*

  | | |
  |---|---|
  | `Badge(label, tone)` | A tinted status pill, sized to sit in a table cell. `label` is a **string**, so an enum needs `.Label` — `Badge(o.Status.Label, tone: Tone.Success)`. Its block replaces the label when you give it one, and inside a block the enum needs nothing: `Badge(tone: Tone.Success) { Text(o.Status); }`, because it is `Text` that turns an enum into its `[Label]` words. |
  | `Alert(message, title, tone)` | An inline message banner — a validation summary, a warning above a destructive form. |
  | `EmptyState(title, body)` | What a list looks like before it has anything in it. Wrap a call to action in it. |
  | `Avatar(initials, src, alt, size)` | Image when there is one, initials when there is not. |
  | `Spinner(size)` | Indeterminate work. Prefer a `skeleton { }` block for a first data read. |
  | `ProgressBar(label, percent, tone)` | Determinate progress, 0-100. Announces its value (`role="progressbar"` + `aria-valuenow`) and, by `label`, what it measures — required, because the bar is announced on its own. |
  | `Skeleton(shape)` · `SkeletonText()` | Stand-ins for content still loading — put them in a `skeleton { }` block. |

  *Overlays* — render these behind an `if`.

  | | |
  |---|---|
  | `Dialog(title, onDismiss, subtitle)` | A modal. Brings its own scrim; `slot actions { … }` is the footer. |
  | `LeavePrompt(canSave, error, onSave, onDiscard, onKeep)` | The three answers to "this page has unsaved changes" — Keep editing, Save and leave, Discard and leave. The kit's shells raise it for you on the way out; render it yourself for a control of your own that leaves a page ([LeavePrompt — asking before a page with unsaved edits is left](https://osysharp.com/reference/ui/leave-prompt/)). |
  | `ConfirmStep(question, proceed, detail)` | The kit's "are you sure?" — Cancel or the action. What a danger `IconButton` and a `Button(confirmText: …)` open before they act; open it yourself with `Dialog.Ask<ConfirmAnswer>(ConfirmStep("Close the meeting?", "Close it"))`, which answers `ConfirmAnswer.Proceed` or nothing. |
  | `Menu(from, label)` · `MenuItem(label, onPress, tone, disabled, icon, checked, hintFor, toggled)` | A menu panel that grows from its trigger with a notch pointing back at it, and its rows — an icon column, a keycap DERIVED from the key bound to the row's action, a tick for a pick-one menu (`checked:`, a `menuitemradio`) or an on/off setting (`toggled:`, a `menuitemcheckbox`). The keyboard (arrows, type-ahead) is the platform's. See [Menus, the command menu, and keyboard shortcuts](https://osysharp.com/reference/ui/menus/). |
  | `CommandMenu(commands, onDismiss)` | ⌘K: a filter field over grouped `Command`s with their shortcuts. See [[ui-menus#command-menu]]. |
  | `KeyHint(keys)` · `KeyHintOf(action)` · `KeyboardSheet()` | A keycap: `KeyHint("Mod+K")` spells a binding for the reader's platform (⌘K / Ctrl+K); `KeyHintOf(Assign)` shows the key bound on the page to an action, or nothing; `KeyboardSheet()` is the `?` sheet of every key on the page. Bind keys with `shortcut:`. See [[ui-menus#hints]]. |
  | `Done(label, tone)` | "Did it land?" — a tick that draws itself beside a word ("Saved", "Sent", "Copied") where the thing the person did is. |
  | `Lamp(lit, live, tone, waiting)` | A status dot: lit, hollow, or breathing when `live`; `waiting` draws it slate-violet (paused on somebody else). Beside a queue with work in it, a channel that is connected, a ticket on hold. |
  | `Backdrop(onDismiss)` · `Scrim(onDismiss)` | Click-outside-to-close, invisible or dimmed. **This is the mechanism** behind every popover — pair it with your own panel. |
  | `Toast(message, tone, seconds, onDismiss)` | A transient message pinned to the corner that goes by itself after `seconds` (5; longer for a long message) and waits while the pointer is on it. An empty message shows nothing: render it always, `Toast(notice, onDismiss: ClearNotice)`, and `onDismiss` clears the state that shows it, so the same words can show again. `seconds: 0` keeps it until the message is cleared. In a test: `Ui.Tick()` (its time starts at the first beat that sees the message), `TestClock.Advance(…)`, `Ui.Tick()`, then `Assert.Hidden(…)`. |

  *Data*

  | | |
  |---|---|
  | `DataGrid(rows, columns, rowSelected)` | A sortable, resizable, templatable table that becomes a card list on a narrow screen. Each column names its value with a **selector** over the row, so a rename is a compile error rather than a blank cell. A column's `Value` selector may read the page's own state as well as the row. Fill `slot <Column> { row => … }` to template a cell; set `Sortable = false` on a column that renders only through its template, so its header stops offering a sort that would reorder nothing. **Columns wider than the space scroll sideways** — the header row and the per-column filter row travel with the data, so a column never labels the wrong values, nothing is dropped, and the page itself never scrolls. The search box and `Columns` stay put above the scrolling table. **The header reads calmly**: the per-column filter row opens on demand (`Filter`, or a column's funnel), a set filter stays in view as a chip, and the sort mark shows on the sorted column only. `selectable: true` adds a check per row and turns the toolbar into the selection's (`slot selection { picked => … }`); `Sort = UiSort.Descending` on a column sets the starting order — see [Acting on data — a calm grid header, filters as chips, and a toolbar that follows the selection](https://osysharp.com/reference/ui/selection/). |
  | `SearchField(label, value)` | A compact search box — a magnifier and one line, the label as its name and placeholder, Esc clears it — for a toolbar that should not carry a caption above its box. |
  | `Facts(label)` · `Fact(label, value, hint)` | A label-and-value table — a record's details, a server's state — one `Fact` per line, a status chip in its block; on a phone the label sits over its value. |
  | `LoadMoreButton(rows, label)` · `SearchPicker(label, value, search, options)` | Paging a long list: the button that asks a paged read for its next page (`rows.HasMore` / `rows.LoadMore()`), and a picker that searches the server rather than listing every row. |
  | `QrCode(rows, module, label)` | A QR code to scan — `QrCode(Qr.Encode(link))` — dark on white whatever the theme, named for a screen reader by `label`. |
  | `SkeletonCard(title, lines)` · `LoadingNote(text)` | What a page shows while it reads: a card's frame with its real title and placeholder lines, and a status a screen reader says at once and the eye sees only once the wait is long enough to notice. |
  | `FilterChip(label, removeLabel, onRemove)` · `SelectionBar(count, onClear)` | What acting on data needs beside a table: a filter that is SET, said in words with a × (the grid's own chips are these), and the toolbar a selection turns into — the count, the app's verbs, Clear. `DataGrid(selectable: true)` uses both; see [Acting on data — a calm grid header, filters as chips, and a toolbar that follows the selection](https://osysharp.com/reference/ui/selection/). |
  | `IconButton(label, onPress, tone, size, disabled, canPress, whenDenied, shortcut, confirm, confirmText)` | A square icon-only action — a row action, a toolbar affordance. The glyph is a slot, since icons are yours. **Pass `label`**: an icon has no text to be announced by, and only you know whether this one means "Save" or "Save draft". Reflects a policy exactly as `Button` does. **A `tone: Tone.Danger` one ASKS FIRST** — "Delete welcome.txt?", Cancel or the action — and runs `onPress` on the second press; `confirmText:` words the question, `confirm: false` turns it off for a removal that is as easily undone. In a test: `Ui.Click("Delete welcome.txt"); Assert.Dialog("Delete welcome.txt?"); Ui.Click("Delete welcome.txt", within: "Delete welcome.txt?");`. |

  *App shells* — a whole page's frame. Each takes the app's `AppChrome` and fills its regions from slots.

  | | |
  |---|---|
  | `SidebarShell(chrome)` | Navigation down the left, content beside it. The regions carry names, so a test scopes to them: `Ui.Click("Users", within: "Main navigation")`. |
  | `WorkbenchShell(chrome)` | A developer tool's or an operator's console: an activity bar of section marks, an explorer listing the current section's pages (plus the app's `explorer` slot), full-height panes and a status line (`status` slot) along the foot. Pair with `Dock`; see [WorkbenchShell](https://osysharp.com/reference/ui/shell-workbench/). |
  | `RailShell(chrome)` | A narrow icon rail instead of a full sidebar, for an app with few destinations and wide content. |
  | `TabbedShell(chrome)` | The app's areas as tabs across the top, and a bottom tab bar on a phone — for a handful of PEER areas, with the whole width left for the page. |
  | `FocusedShell(chrome)` | A single-task frame with no navigation at all: a checkout, a wizard, a form someone must finish. Its `primary` slot is the commit bar. |
  | `SplitShell(chrome)` · `SplitArt(texture, alt, focus)` | A page split in two: the content on one side and a photograph filling the other (`SplitArt`), which steps under the content on a phone — a sign-in, a landing, a booking. |
  | `StorefrontShell(chrome, bagCount, bagLabel)` · `StorePromo(text, linkLabel, to)` · `StoreFooter(chrome, blurb, note)` | A shop's frame: the wordmark, the category menu and the bag across the top, a promo strip above it, and a footer of the shop's links. One overlay at a time — the bag, the phone's menu, a category. |
  | `CatalogueShell(chrome)` | A shop's frame with the whole category tree open in a column beside the page, the wordmark at its head and the bag at its foot — for a catalogue browsed by its shelves. |
  | `EditorialShell(chrome, floatOnHome)` · `EditorialFooter(chrome, blurb, note)` | A brand site's frame: the wordmark, a centred nav and one call to action, floating over a page's opening photograph and solid elsewhere; the footer signs off with the brand's name set across the width. |
  | `MarqueeShell(chrome, ticker)` | A ticker across the top, the wordmark and the shelves set at poster size, the page under them — a loud storefront. |

  *Shell parts* — the affordances the shells' slots are shaped for.

  | | |
  |---|---|
  | `ShellAside(label)` | The summary column beside the page — an order total, a wizard's context. Beside the content on a wide screen, under it on a narrow one, never dropped. A container in a test: `Assert.Visible("Total", within: "Order summary")`. |
  | `ShellTaskBar(label)` | The commit bar a task shell's `primary` slot is shaped for. A container: `Ui.Click("Continue", within: "Task actions")`. |
  | `ShellSearch(placeholder, onPress, shortcut)` | The search affordance a shell's `slot search` is shaped for — a hairline box, a magnifier, a placeholder and a keycap hint. Pressed by its placeholder in a test. |
  | `ShellSheet(title, onDismiss)` | A bottom sheet — the phone's answer to a dropdown. Slides up over a `Scrim` and dismisses. |
  | `ShellCountButton(label, count, onPress, tone)` | An icon button with a count: notifications, messages, anything with a number waiting on the reader. |
  | `ShellFootCard(title, body, actionLabel, onPress)` | A card pinned at the foot of a rail — the help affordance `slot railFoot` is shaped for. |
  | `WorkspaceShell()` · `BareWorkspaceShell()` · `WorkspaceFrame()` | The frame around a kit's working pages: an app designs `WorkspaceShell` once (`component MyShell() : WorkspaceShell { … }`) and every page standing in `[Layout(WorkspaceFrame)]` — and the settings area — stands in the app's own shell. `BareWorkspaceShell` is the kit's default: the page and nothing around it. |
  | `RecordPanel(record)` · `RecordBadge(record)` | Slots on ANY record's page that other kits contribute to — a panel under the record (`: RecordPanel<T>`), a small fact beside its name (`: RecordBadge<T>`). The page draws `RecordPanel(order)` and whatever contributes appears. |

  *Page arrangements* — how a page is laid out once the shell has framed it. See [Arrangements — five ways to build a whole app, and which look wears each](https://osysharp.com/reference/ui/arrangements/).

  | | |
  |---|---|
  | `Dock(right, bottom, rightTitle, bottomTitle, onCloseRight, onCloseBottom)` | The page's drawers: the opened record docked on the right, a log or console along the bottom, each resizable and remembered; a sheet on a phone. See [Dock](https://osysharp.com/reference/ui/dock/). |
  | `SubNav(label)` · `SubNavItem(label, selected, onPress, hint, count)` | Navigation inside a tab — a list down the side of a tab's body (chips on a phone). See [SubNav](https://osysharp.com/reference/ui/subnav/). |
  | `Paper(label)` | A record as the document it is — a sheet at a reading width with margins. See [Paper](https://osysharp.com/reference/ui/paper/). |
  | `WidgetGrid()` · `Widget(title, subtitle, span, editing, onRemove, onEarlier, onLater)` | A workspace of cards on a four-column grid that a person can arrange. See [WidgetGrid](https://osysharp.com/reference/ui/widget-grid/). |
  | `AssistantPanel(title, turns, value, onSend, onClose, working, suggestions, onSuggest, placeholder)` | An assistant to ask, floating over the page — presentation only; the app keeps the turns and answers. See [AssistantPanel](https://osysharp.com/reference/ui/assistant-panel/). |

  *Settings* — the admin area every app's configuration shares. See [SettingsArea — an app's settings: sections, explicit save and discard, undo, who changed what](https://osysharp.com/reference/ui/settings/); a row of settings is declared
  rather than built, see [[Settings] and [Setting]: a settings section declared on its row, each field drawn by its type](https://osysharp.com/reference/ui/settings-declared/).

  | | |
  |---|---|
  | `SettingsArea(sections, title, home)` · `SettingsFrame()` · `SettingsOverview(sections, title)` | The area: its sections down the left (with no list, every section the app and its kits announce), the page beside them, and an overview of them all at its home. `SettingsFrame` is the `[Layout]` every settings page names; an app takes it over to put its shell around the area. |
  | `SettingsPage(title, description)` · `SettingsSection(title, description, locked, lockedBecause, changed, setByOrganization)` | A settings page's heading, and one section of it — what it is on the left, its controls on the right, with "who changed this" and "Set by your organization". |
  | `SettingRow(hint, setByOrganization)` · `SettingsGrid()` | One setting — its control, the line under it, and the organisation's mark when only it is set — and short settings side by side. |
  | `SaveBar(onSave, saved, blockedBy, onSaved)` | Save, Discard and Undo for a page's unit of work, shown only while something is unsaved; `blockedBy` holds Save back with the reason, and a save the server refuses says "Not saved — " and why. |
  | `SaveActions(saved, blockedBy, onSaved)` · `AutoSave(after, saved)` | The other two ways to show a page's unit of work: Save, Discard and Undo INLINE (a record's head, a drawer's), or no Save at all — kept by itself `after` seconds later, with an Undo while it waits. See [Save, discard, undo — three ways to show a page's unit of work](https://osysharp.com/reference/ui/saving/). |
  | `AmbientVideo(video, poster, pauseLabel, focus)` | A background film the app ships, filling its box with your words on top, muted and looping, with a pause button in the corner that keeps one name and says whether it is pressed; still, on its poster, for a reader who asked for reduced motion. See [Playing a video on a page](https://osysharp.com/reference/ui/video/). |

  ⚠ **This section used to say the app shell was NOT in the kit** — that it shipped only as a sample you copy,
  because a shell is one navigation model rather than a general one. That was true when it was written and is not
  now: the shells and their parts above ship, and a control an app cannot discover may as well not ship. The SAMPLE is still
  the place to start when you want a shell that is yours — `osy docs sample admin-shell` — and **[App shells](https://osysharp.com/reference/ui/shell/)** is
  the page that explains the regions and their slots.

  Each one is ordinary Osy# — atoms, style props and `Slot` — so you can read them to learn the house style, and
  fork any of them by dropping a same-named component in your own source.

  The kit's source ships inside the binary rather than in your project, so **`osy kit`** is how you read it:

  ```console
  $ osy kit              # every control's signature — the whole catalogue, one line each
  $ osy kit Field Card   # what those two are FOR, with a call you can copy
  $ osy kit --for "a labelled input"   # …when you cannot name the one you want
  $ osy kit Card         # Card's own declaration in full — the source you read to learn it, or fork
  $ osy kit Card --file  # …and the REST of the file it lives in: its file-mates, and the header's reasoning
  $ osy kit atoms        # the renderer's own primitives, which need no `using`
  $ osy kit tokens       # the starter theme's design tokens — every one, by group, with its value
  $ osy kit --json       # the same, for tools
  ```

  Both the listing and each signature are read out of the kit's own source, so they always describe the kit you
  actually have.

  This kit is not the only one. `osy kit` closes with the other kits the platform ships — what each is for, the
  controls it adds and the one line that turns it on — because a control you do not know exists is one you cannot
  choose. `osy kit --names` names the kit each set belongs to, and **`osy kit <Control>` finds a control in any of
  them**, telling you which kit it is in. They are listed from what the platform can actually resolve, not from
  what happens to be documented, so a kit with no reference page still appears.
- **Vocabularies** — the design-system enums a styled control reads: `Tone`, `Size`, `Step`. These are yours to
  extend (a brand adds `Tone.Success`).
- **A starter theme** — a complete token vocabulary, so the kit's own controls and your pages are written in names
  rather than pixels. It covers colour (`Bg`, `Surface`, `Border`, `Primary`, … with a light/dark mode map, so an app
  themes its whole page for free), radii, spacing, type (`Font`, `FontSize`, `FontWeight`), sizes (`ControlMd`,
  `IconMd`, `AvatarMd`, …), shadows, motion, z-layers and breakpoints. See [theme tokens](https://osysharp.com/reference/ui/theming/).

Those tokens are in scope for **your** components too, not just the kit's — `Bg = Surface`, `FontSize = Body`,
`H = ControlMd` resolve in a page you wrote, with no theme of your own. Declaring a theme is then how you *override*:
re-declare a token in the same group (`Radius { Md = "4px"; }`) and yours wins.

## Description    {#description}
A **kit control** is an ordinary component — you call it, pass its arguments, and it arg-checks exactly like a
component you wrote yourself. `Button` takes a `label`, an `onPress` handler, and `tone` / `size` variants:

```osy title="its tone and size variants" test app=ui-kit
[Route("/checkout")]
[AllowAnonymous]
[Render(CSR)]
component Checkout() {
  action Pay() { }
  render {
    Button("Pay now", onPress: Pay, tone: Tone.Primary, size: Size.Lg);
  }
}
```

### Greying out a button the user may not use — `canPress`   {#canpress}
A control that acts on data usually acts on data the caller may not be allowed to change. `canPress:` takes a
declared **policy** and greys the button out when it does not hold, with `whenDenied` as the reason — so the person
looking at it learns *why* instead of pressing and being refused:

```osy title="a button that greys out, and says why" test app=ui-kit-policy
[Principal]
entity Member { string Email; bool IsOwner = false; }

policy IsOwner => Member.Any(m => m.Email == user.Email && m.IsOwner);

[Route("/team")]
[Render(CSR)]
[AllowAnonymous]
component TeamPage() {
  action Invite() { }
  render {
    Button("Invite a member", onPress: Invite, tone: Tone.Primary,
           canPress: IsOwner, whenDenied: "Only an owner can invite members.");
  }
}
```

It only ever **reflects**: the server enforces the rule, and no button is a gate. `IconButton` takes the same pair.

The **`Tone`** and **`Size`** vocabularies are the variant dimensions a styled control exposes, and you can use them
in your own components too:

```osy title="the kit's vocabularies are yours to use" test app=ui-kit
[Composable] component Badge(Tone tone) {
  render { Text("badge"); }
}
```

### Forking a control     {#forking}
The kit is **forkable**: to change how a control looks or behaves, declare a component with the **same name** in
your own source. Your version **shadows** the kit's everywhere in your app — including inside other kit components
used by reference (a kit `Table` that renders `Button` renders *your* `Button`) — with no extra step:

```osy title="forking a kit control by shadowing its name" test app=ui-kit-fork
// This app's own Button — replaces the kit's for every reference in this app.
component Button(string label, Action onPress) {
  render { Pressable(label, onClick: onPress); }
}
```

A **`control`** of that name shadows the kit's too, and the same way — so an app whose grid is a foreign shim
(`control DataGrid<T> { … }`) keeps its own grid, and its call sites are checked against the shim it declares. One
name may only be declared once in your own source, though: a `component` and a `control` sharing a name is a compile
error naming both files, because between two of your own declarations there is nothing to prefer.

The fastest way to start from the kit's exact source is **`osy get`**, which vendors a control into `ui/lib/` as your
own source — the control's own declarations, verbatim, under a short header recording where they came from:

```console
$ osy get ui/hint        # one control → ui/lib/Hint.osy
$ osy get ui/*           # every control
```

**You get the control, not the file it lives in.** The kit groups components per file for the reader, and the two
groupings want opposite things from a fork: `Hint` shares a file with six other type roles you almost certainly do
not want to own, while a `DataGrid` is useless without its cells. So the unit is the **control**, and what travels
with it is reported:

```console
$ osy get ui/datagrid
✓ Forked DataGrid → ui/lib/DataGrid.osy
  ↳ also GridColumn (the class only DataGrid uses)
  ↳ also HeaderCell ([Internal] — part of DataGrid)
  ↳ also FixedCell ([Internal] — part of DataGrid)
  …
```

**Everything your fork does not take still resolves to the kit's.** A vendored `PageHead` goes on rendering the
kit's `PageTitle` and `Hint` — resolution is by name, and only the names you vendored are shadowed. That is what
makes a narrow fork safe: you own the one control you meant to change, and the rest keeps improving.

The vendored file is picked up by your `ui/**/*.osy` glob automatically, so it shadows the kit's with no manifest
edit. A forked control should stay **call-compatible** (keep its public parameters) so existing call sites — and kit
components that call it — keep resolving. Only your app is affected; the kit's original is untouched for every other
app.

## Examples       {#examples}
A small form using a kit `Button` and the layout vocabulary:

```osy title="a small page built from the kit" test app=ui-kit-contact
[Route("/contact")]
[AllowAnonymous]
[Render(CSR)]
component Contact() {
  string email = "";
  action Send() { }
  render {
    Stack(gap: 3, align: Align.Center) {
      Text("Get in touch");
      Input(value: email, placeholder: "you@example.com");
      Button("Send", onPress: Send, tone: Tone.Primary);
    }
  }
}
```

## See also       {#see-also}
- [App shells](https://osysharp.com/reference/ui/shell/) — the four app-shell arrangements, and how to build a `[Layout]` out of one.
- [component](https://osysharp.com/reference/ui/component/) — declaring your own components (kit controls are just components).
- [Pinning a kit version (using Ui@3)](https://osysharp.com/reference/ui/kit-versioning/) — pinning the kit's major with `using Ui@3;`.
- [theme tokens](https://osysharp.com/reference/ui/theming/) — the design tokens the kit's controls and your theme share.
- [layout primitives](https://osysharp.com/reference/ui/layout/) — `Stack` / `Row` and the `gap` / `align` / `justify` vocabulary.
