From dc31cb4172a909b39aae6622f4e4bf1eb4784d98 Mon Sep 17 00:00:00 2001
From: Luke Bennett
Date: Fri, 17 Jul 2026 10:38:57 +1000
Subject: [PATCH] Refresh documentation (#158)
---
.../docs/components/actions/button.mdx | 106 +++++++++---------
.../docs/components/actions/icon-button.mdx | 63 +++++------
.../content/docs/components/actions/link.mdx | 54 ++++-----
.../components/feedback/loading-skeleton.mdx | 98 ++++++++--------
.../components/feedback/loading-spinner.mdx | 38 ++++---
.../docs/components/forms/combobox-field.mdx | 104 +++++++----------
.../docs/components/forms/text-field.mdx | 70 +++++-------
.../content/docs/components/layout/box.mdx | 74 +++++++++---
.../docs/components/primitives/button.mdx | 36 +++---
.../docs/components/primitives/combobox.mdx | 60 ++++++----
.../docs/components/primitives/field.mdx | 32 ++++--
.../docs/components/primitives/text-input.mdx | 30 +++--
.../docs/components/typography/emoji.mdx | 27 +++--
.../docs/components/typography/heading.mdx | 52 ++++-----
.../docs/components/typography/numeral.mdx | 33 ++++--
.../docs/components/typography/text.mdx | 98 ++++++++--------
.../content/docs/components/visuals/icon.mdx | 41 +++----
apps/docs/content/docs/getting-started.mdx | 46 --------
apps/docs/content/docs/index.mdx | 80 +++++++++++--
apps/docs/content/docs/meta.json | 2 +-
.../docs/content/docs/overview/color-mode.mdx | 61 ++++++++++
apps/docs/content/docs/overview/color.mdx | 71 ++++++++++++
.../content/docs/overview/getting-started.mdx | 90 +++++++++++++++
apps/docs/content/docs/overview/layout.mdx | 73 ++++++++++++
apps/docs/content/docs/overview/meta.json | 4 +
apps/docs/content/docs/overview/styling.mdx | 79 +++++++++++++
apps/docs/content/docs/overview/theme.mdx | 74 ++++++++++++
.../docs/content/docs/overview/typography.mdx | 71 ++++++++++++
apps/docs/content/docs/theming/applying.mdx | 63 ++++++-----
apps/docs/content/docs/theming/authoring.mdx | 53 +++++----
apps/docs/content/docs/theming/index.mdx | 89 +++++++++++----
.../content/docs/theming/token-reference.mdx | 42 ++++---
apps/docs/src/components/example-block.tsx | 32 +++---
apps/docs/src/components/not-found.tsx | 3 +-
.../theme-controls.browser.test.tsx | 14 +++
.../src/examples/box/responsive-layout.tsx | 31 ++++-
apps/docs/src/examples/button/pending.tsx | 12 +-
apps/docs/src/examples/button/sizes.tsx | 13 ++-
.../examples/button/tone-and-appearance.tsx | 18 +--
apps/docs/src/examples/button/variants.tsx | 21 ++++
.../src/examples/combobox-field/basic.tsx | 26 +++--
.../src/examples/combobox-field/grouped.tsx | 12 +-
.../src/examples/combobox-field/required.tsx | 18 +--
apps/docs/src/examples/emoji/basic.tsx | 10 +-
.../examples/heading/automatic-leveling.tsx | 6 +-
apps/docs/src/examples/heading/typography.tsx | 16 ++-
apps/docs/src/examples/icon-button/size.tsx | 9 +-
.../icon-button/tone-and-appearance.tsx | 10 +-
apps/docs/src/examples/icon/basic.tsx | 12 +-
apps/docs/src/examples/icon/custom.tsx | 2 +-
apps/docs/src/examples/link/disabled.tsx | 4 +-
apps/docs/src/examples/link/standalone.tsx | 16 +--
apps/docs/src/examples/link/tones.tsx | 11 +-
.../loading-skeleton/border-radius.tsx | 23 ++--
.../loading-skeleton/custom-dimensions.tsx | 28 +++--
.../src/examples/loading-skeleton/element.tsx | 30 ++---
.../examples/loading-skeleton/provider.tsx | 26 +++--
.../src/examples/loading-skeleton/text.tsx | 28 +++--
.../src/examples/loading-spinner/basic.tsx | 2 +-
.../src/examples/loading-spinner/colors.tsx | 13 ++-
.../src/examples/loading-spinner/progress.tsx | 11 +-
.../src/examples/loading-spinner/sizes.tsx | 9 +-
apps/docs/src/examples/numeral/basic.tsx | 11 +-
apps/docs/src/examples/numeral/compact.tsx | 14 ++-
apps/docs/src/examples/numeral/formats.tsx | 22 +++-
apps/docs/src/examples/numeral/precision.tsx | 14 ++-
.../src/examples/overview/intent-colours.tsx | 93 +++++++++++++++
.../src/examples/text-field/adornments.tsx | 19 +++-
apps/docs/src/examples/text-field/basic.tsx | 8 +-
.../docs/src/examples/text-field/required.tsx | 5 +-
apps/docs/src/examples/text-field/sizes.tsx | 18 ++-
apps/docs/src/examples/text/alignment.tsx | 18 ++-
apps/docs/src/examples/text/transform.tsx | 8 +-
apps/docs/src/examples/text/truncation.tsx | 15 ++-
apps/docs/src/examples/text/typography.tsx | 16 ++-
.../examples/theming/identity-and-mode.tsx | 97 ++++++++++++++++
.../examples/theming/semantic-variables.tsx | 60 ++++++++--
apps/docs/src/lib/source.ts | 2 +-
apps/docs/src/lib/story-wrapper.tsx | 38 ++++---
apps/docs/src/routes/{docs => }/$.tsx | 18 +--
apps/docs/src/routes/index.tsx | 26 -----
apps/docs/src/routes/playground/index.tsx | 2 +-
apps/docs/src/routes/{docs => }/{$}[.]md.ts | 8 +-
apps/docs/vite.config.ts | 2 +-
84 files changed, 1994 insertions(+), 900 deletions(-)
delete mode 100644 apps/docs/content/docs/getting-started.mdx
create mode 100644 apps/docs/content/docs/overview/color-mode.mdx
create mode 100644 apps/docs/content/docs/overview/color.mdx
create mode 100644 apps/docs/content/docs/overview/getting-started.mdx
create mode 100644 apps/docs/content/docs/overview/layout.mdx
create mode 100644 apps/docs/content/docs/overview/meta.json
create mode 100644 apps/docs/content/docs/overview/styling.mdx
create mode 100644 apps/docs/content/docs/overview/theme.mdx
create mode 100644 apps/docs/content/docs/overview/typography.mdx
create mode 100644 apps/docs/src/examples/button/variants.tsx
create mode 100644 apps/docs/src/examples/overview/intent-colours.tsx
create mode 100644 apps/docs/src/examples/theming/identity-and-mode.tsx
rename apps/docs/src/routes/{docs => }/$.tsx (83%)
delete mode 100644 apps/docs/src/routes/index.tsx
rename apps/docs/src/routes/{docs => }/{$}[.]md.ts (61%)
diff --git a/apps/docs/content/docs/components/actions/button.mdx b/apps/docs/content/docs/components/actions/button.mdx
index 2f14a3ee..1b8c3d07 100644
--- a/apps/docs/content/docs/components/actions/button.mdx
+++ b/apps/docs/content/docs/components/actions/button.mdx
@@ -1,97 +1,103 @@
---
title: Button
-description: Action button with semantic tone and appearance options.
+description: A labelled control for actions in an interface.
---
-`Button` expects the Luke UI theme class at the app or root level. See
-[Getting Started](/docs/getting-started).
+`Button` triggers an action, such as saving a form, opening a dialog, or deleting an item. Use
+[`Link`](/components/actions/link) when the interaction takes someone to another URL or route.
```tsx
-
+import { Button } from '@luke-ui/react/button';
+
+;
```
-## Best practices
+## Appearance
+
+Use `appearance` to set a button's visual weight. `solid` is the default. Choose `subtle` or `ghost`
+for secondary actions that should recede beside the main action. Appearance sets emphasis; it does
+not communicate intent.
-| Guidance | Practices |
-| -------- | -------------------------------------------------------------------------------------------------------------- |
-| Do | Use `tone="accent"` for the main action. Use a subtle or ghost appearance for secondary actions. |
-| Do | Write a label that names the action, such as "Save changes" or "Delete account". Avoid vague labels like "OK". |
-| Do | Set `isPending` while an action is in flight so the user can see that work is still happening. |
-| Don't | Use `Button` for navigation. If the control only moves the user to another page, use `Link`. |
+
-## Tone and appearance
+## Tone
-Tone communicates intent: `neutral` (default), `accent`, or `danger`. Appearance controls emphasis:
-`solid` (default), `subtle`, or `ghost`. Every tone supports every appearance.
+Use `tone` to communicate intent. `neutral` is the default, `accent` identifies a primary action,
+and `danger` marks a destructive one. Tone and appearance are independent, so every combination is
+available.
## Size
-`Button` has two sizes: `medium` (default) and `small`.
+`medium` is the default size. Use `small` in dense toolbars, tables, and other compact interfaces.
+Keep related controls at the same size.
-## Icons
+## Block layout
-Use `startIcon` and `endIcon` to place an icon before or after the label. The icon inherits the
-button size, so the icon does not need its own `size` prop.
+Set `isBlock` when the button should fill the inline size of its container. This is useful for a
+primary action at the end of a narrow form or panel.
```tsx
-import { Icon } from '@luke-ui/react/icon';
-
-}>Add item;
+
```
-## Disabled
+## Icons
-Disabled buttons cannot be focused or pressed.
+Use `startIcon` or `endIcon` with a visible label. `Button` supplies the icon size, so the nested
+`Icon` does not need its own `size` prop. Use [`IconButton`](/components/actions/icon-button) only
+when the icon is familiar without a text label.
```tsx
-
+import { Icon } from '@luke-ui/react/icon';
+
+}>Add item;
```
-## Pending
+## Pending and disabled states
-Set `isPending` while an action is in flight. A spinner replaces the label. The button remains
-focusable and reports its pending state, but cannot be pressed again. It uses the same muted visual
-treatment as a disabled button while keeping the spinner visible.
+Set `isPending` while an action is running. The button remains focusable, ignores further presses,
+and overlays its label with a spinner. Update the visible label when the status matters, for example
+from “Save” to “Saving”.
+
+Set `isDisabled` only when the action is unavailable. Disabled buttons cannot receive focus or be
+pressed.
-## Full width
-
-Set `isBlock` to make the button fill the inline size of its container.
-
-```tsx
-
-```
-
## Accessibility
-`Button` wraps its children in `Text`, so visible text usually provides the accessible name. You
-normally do not need `aria-label`.
+The visible label provides the accessible name, so most buttons do not need `aria-label`. Write a
+label that describes the action, such as “Save changes” or “Delete account”, rather than a vague
+label such as “OK”.
-The pending spinner is `aria-hidden` and does not announce busy state to screen readers. If screen
-reader users need to hear the pending state, change the label text itself, for example to "Saving",
-while `isPending` is set.
+The spinner is hidden from assistive technology. Change the label when pending status needs to be
+announced.
-## Primitive
+## Button primitive
-Use the [button primitive](/docs/components/primitives/button) when you need full control over
-children, custom loading states, render-prop children, or non-standard content.
+`Button` is the composed component for application UI. It wraps its label for truncation, provides
+icon slots, and supplies the pending spinner. Use the
+[button primitive](/components/primitives/button) when building a custom control and you need to own
+the child layout or loading treatment.
## Props
diff --git a/apps/docs/content/docs/components/actions/icon-button.mdx b/apps/docs/content/docs/components/actions/icon-button.mdx
index c8173473..6a04934c 100644
--- a/apps/docs/content/docs/components/actions/icon-button.mdx
+++ b/apps/docs/content/docs/components/actions/icon-button.mdx
@@ -1,61 +1,56 @@
---
title: Icon Button
-description: Compact icon-only action button with an accessible label.
+description: A compact icon-only control for an action.
---
-Use `IconButton` for compact actions where an icon can carry the visible UI. Always provide an
-accessible label.
+`IconButton` triggers a familiar compact action, such as closing a panel, deleting a row, or adding
+an item. Give every icon button an accessible name because it has no visible label.
```tsx
-
+import { IconButton } from '@luke-ui/react/icon-button';
+
+;
```
-## Best practices
+## Accessibility
+
+Provide `aria-label` or `aria-labelledby` for every icon button. Name the action, not the icon.
+
+```tsx
+
+```
-| Guidance | Practices |
-| -------- | ---------------------------------------------------------------------------- |
-| Do | Name the action in `aria-label`, such as "Delete row". Do not name the icon. |
-| Do | Use `tone="danger"` for destructive icon actions such as delete. |
+Use a labelled [`Button`](/components/actions/button) when the icon is unfamiliar or visible text
+would make the action clearer.
-## Tone and appearance
+## Appearance
-`IconButton` shares Button's appearance API. Tone can be `neutral` (default), `accent`, or `danger`.
-Appearance can be `solid` (default), `subtle`, or `ghost`.
+`IconButton` uses the same `tone` and `appearance` options as
+[`Button`](/components/actions/button). Use `danger` for a destructive action. Choose `subtle` or
+`ghost` when a secondary control needs less emphasis.
## Size
+`medium` is the default. Use `small` for compact toolbars and dense row actions. Keep controls in a
+group at the same size.
+
-## Pending
-
-Set `isPending` while an action is in flight. The button remains focusable but cannot be pressed
-again. It uses the disabled visual treatment and replaces the icon with a busy cue.
-
-## Accessibility
-
-`IconButton` has no visible text label. Always provide `aria-label` or `aria-labelledby`. Without
-one, screen readers have no way to announce the button's purpose.
-
-```tsx
-
-```
-
-## When to use vs Button
-
-Use `IconButton` when the icon alone communicates the action and space is tight, such as in
-toolbars, table row actions, or close buttons.
+## Pending and disabled states
-Use `Button` when you need a visible text label. Labels are clearer for unfamiliar users.
+Set `isPending` while an action is running. The control remains focusable, ignores further presses,
+and replaces its icon with a busy cue. Set `isDisabled` when the action is unavailable. Disabled
+controls cannot receive focus or be pressed.
## Props
diff --git a/apps/docs/content/docs/components/actions/link.mdx b/apps/docs/content/docs/components/actions/link.mdx
index 0191956e..7e771d87 100644
--- a/apps/docs/content/docs/components/actions/link.mdx
+++ b/apps/docs/content/docs/components/actions/link.mdx
@@ -1,65 +1,57 @@
---
title: Link
-description: Link component for inline and standalone navigation.
+description: A styled link for inline and standalone navigation.
---
-`Link` expects the Luke UI theme class at the app or root level. See
-[Getting Started](/docs/getting-started).
+`Link` navigates to another URL or route. Use [`Button`](/components/actions/button) for an action
+such as saving, submitting, or opening a dialog.
```tsx
-Help center
-```
-
-## Best practices
+import { Link } from '@luke-ui/react/link';
-| Guidance | Practices |
-| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
-| Do | Use the default `accent` tone for links that should draw attention. |
-| Do | Use `isStandalone` for links that are not part of a sentence, such as card links and nav items. Leave it `false` for inline links. |
+Help centre;
+```
## Tone
-`Link` has two tones: `accent` (default) and `neutral`. Use `neutral` when the surrounding content
-should have more emphasis.
+`accent` is the default tone and draws attention to the destination. Use `neutral` when a link sits
+within content that needs more visual emphasis.
-## Standalone
+## Inline and standalone links
-Use `isStandalone` when a link stands on its own. Leave it `false` for links inside paragraph text.
+Use the default link inside a sentence. Its underline remains visible so people can recognise it in
+running text.
-- `isStandalone={true}`: no underline until hover or press and a structural target of at least 24 ×
- 24 CSS pixels.
-- `isStandalone={false}`: underlined inline link style that retains the target-size exception for
- links within prose.
+Set `isStandalone` for a link outside prose, such as a card link or navigation item. Standalone
+links show their underline on hover or press and have a 24 × 24 CSS-pixel structural target.
-## Disabled
+## Disabled links
+
+Set `isDisabled` only when a destination is temporarily unavailable. A disabled link cannot receive
+focus or be activated. Explain why nearby when someone needs that information.
## Accessibility
-Screen readers announce a disabled link as unavailable, but not why. Put the reason in nearby
-visible text instead of relying on disabled state alone.
-
-## When to use vs Button
-
-Use `Link` to navigate to a new URL or route. Use `Button` for in-page actions, such as saving,
-submitting, or opening a dialog.
+Write link text that names the destination and makes sense outside its surrounding sentence. Avoid
+generic labels such as “Click here”.
## Props
diff --git a/apps/docs/content/docs/components/feedback/loading-skeleton.mdx b/apps/docs/content/docs/components/feedback/loading-skeleton.mdx
index 489bfa5a..4bd1f11c 100644
--- a/apps/docs/content/docs/components/feedback/loading-skeleton.mdx
+++ b/apps/docs/content/docs/components/feedback/loading-skeleton.mdx
@@ -1,91 +1,81 @@
---
title: Loading Skeleton
-description: Loading placeholder that keeps the same footprint as the final content.
+description: Replaces content with a placeholder that keeps its shape while it loads.
---
-`LoadingSkeleton` expects the Luke UI theme class at the app or root level. See
-[Getting Started](/docs/getting-started).
-
-Use it when loading content should keep the same footprint as the loaded state. Text renders as an
-inline skeleton. Elements keep their layout while the skeleton surface is painted over them.
+Use `LoadingSkeleton` when content is loading but its layout is known. Wrap the content that will
+appear so the placeholder occupies the same space.
-All mounted skeletons use the same pulse timing, even when they mount at different times.
-
-## Best practices
-
-| Guidance | Practices |
-| -------- | ------------------------------------------------------------------------------------------------------------------- |
-| Do | Wrap real content so the skeleton matches the final size exactly. |
-| Don't | Use `LoadingSkeleton` for content whose final size is unknown. Size mismatch causes layout shift when loading ends. |
-
-## Loading state
-
-`isLoading` defaults to `true`. Pass `isLoading={false}` when content is ready.
-
-```tsx
-
-
-
-```
+## With text
-## Multi-line text
+Wrap text itself, rather than its parent. The placeholder then follows each line of text. If you
+wrap the parent element, the skeleton covers the whole block instead.
-Wrap text directly when the copy spans more than one line. Each line gets its own skeleton shape.
+This matters most for copy that wraps across several lines. `LoadingSkeleton` renders non-element
+children inline, so it follows the text without changing the line layout.
-## Element type
+## With components
-`LoadingSkeleton` renders a `span` by default. Use `elementType` when the surrounding markup needs
-another element.
+Wrap an element to keep its dimensions while the skeleton covers it. Use this for controls, avatars,
+and other content with a fixed shape.
-## LoadingSkeletonProvider
-
-Use `LoadingSkeletonProvider` when one loading state controls a group of skeletons. The provider
-value overrides descendant `isLoading` props.
+Set `radius` when the direct child has square corners but a visible descendant is rounded. This is
+useful when a `TextField` is the child because its input control is rounded inside the field
+wrapper.
-## Border radius
+## Loading state
+
+`isLoading` is `true` by default. Set it to `false` when the content is ready. The component then
+returns its children without a wrapper.
-Use `borderRadius` when the wrapped child has no radius, but the visible control inside it does.
+```tsx
+
+
+
+```
+
+Use `LoadingSkeletonProvider` when one loading state controls a section. Its value overrides
+`isLoading` on every descendant skeleton, so a section changes together.
-## Custom dimensions
+## Element type
-Wrap an element with explicit dimensions when you need a placeholder for a fixed shape, such as an
-avatar.
+`LoadingSkeleton` renders a `span` by default. Set `elementType` when the parent requires another
+element, such as an `li` inside a list.
## Accessibility
-While loading, skeleton content is hidden from assistive technology and cannot be focused or
-clicked. `LoadingSkeleton` sets `aria-hidden`, `inert`, `tabIndex={-1}`, and disables pointer
-events.
+While loading, the content is hidden from assistive technology, removed from the tab order, and
+cannot receive pointer input. It becomes available again when `isLoading` is `false`.
## Props
diff --git a/apps/docs/content/docs/components/feedback/loading-spinner.mdx b/apps/docs/content/docs/components/feedback/loading-spinner.mdx
index d67101f9..e0777b5b 100644
--- a/apps/docs/content/docs/components/feedback/loading-spinner.mdx
+++ b/apps/docs/content/docs/components/feedback/loading-spinner.mdx
@@ -1,50 +1,56 @@
---
title: Loading Spinner
-description: Spinner for indeterminate and determinate progress.
+description: Animated indicator for work that is still in progress.
---
-`LoadingSpinner` expects the Luke UI theme class at the app or root level. See
-[Getting Started](/docs/getting-started).
+Use `LoadingSpinner` when work is in progress. Omit `value` when you cannot report completion.
-All mounted indeterminate spinners rotate and pulse in sync, even when they mount at different
-times.
+Indeterminate spinners rotate and pulse in sync, including spinners that mount at different times.
-## Progress mode
+## Progress
-Omit `value` for indeterminate progress. Pass `value` for determinate progress.
+Pass `value` when you can report progress. `minValue` and `maxValue` set the range and default to
+`0` and `100`. Values outside that range are clamped before the indicator is drawn.
## Size
+Use `size` to match the surrounding control or content. The default is `medium`. A surrounding icon
+size context can also set the size.
+
-## Color
+## Colour
+
+Use `color` to apply a semantic content colour. When you omit it, the spinner inherits the
+surrounding text colour.
## Accessibility
-`aria-label` defaults to `"pending"` when omitted. Override it with what is loading, such as
-"Loading profile", for a clearer announcement.
+The spinner has `progressbar` semantics. Its accessible name defaults to `pending`. Provide an
+`aria-label` that names the work, such as `Loading profile`. A determinate spinner also exposes its
+current, minimum, and maximum values.
## Props
diff --git a/apps/docs/content/docs/components/forms/combobox-field.mdx b/apps/docs/content/docs/components/forms/combobox-field.mdx
index 74c7be4f..e7088571 100644
--- a/apps/docs/content/docs/components/forms/combobox-field.mdx
+++ b/apps/docs/content/docs/components/forms/combobox-field.mdx
@@ -1,138 +1,110 @@
---
title: Combobox Field
-description: Single-select combobox field with label, validation, and async options.
+description: Searchable single-select field with label, validation, and option list.
---
-Use `ComboboxField` for a single-select combobox with label, description, error, popover, listbox,
-and optional async loading built in.
+Use `ComboboxField` when people need to search for or choose one option from a list. It combines an
+input, label, description, validation message, popover, and listbox.
-## Best practices
+## Items and selection
-| Guidance | Practices |
-| -------- | ------------------------------------------------------------------------------------------------------- |
-| Do | Use `defaultItems` for a static list. Use `items` with `loadingState` when options load asynchronously. |
-| Don't | Use `ComboboxField` for multi-select. It is single-select only in v1. |
+Use `defaultItems` for a static collection. Use `items` with `loadingState` when results load or
+filter asynchronously. `ComboboxField` supports one selected value. Use another component for
+multiple selection.
-## API shape
+## Select and clear an option
-- Root props follow React Aria `ComboBox` naming.
-- Composed convenience props include `label`, `description`, `errorMessage`, `necessityIndicator`,
- `size`, and `placeholder`.
-- `children` renders items from `items` or `defaultItems`.
-- `listBoxProps` and `loadMoreItem` are lower-level escape hatches.
-
-## Selection indicators
-
-The selected option shows a checkmark in the listbox. When the control has a selection, it shows a
-clear button before the trigger. Pressing the clear button clears the selection and input text.
-
-The clear button is omitted when the field is disabled or read-only.
+Pass static children or a render function to create options. The listbox marks the selected option.
+When there is a selection, the field shows a clear button before the trigger. Clearing removes the
+selection and input text. The clear button is unavailable for disabled and read-only fields.
```tsx
-
+
{(item) => {item.label}}
```
## Required fields
-Use `isRequired` with `necessityIndicator` to show that the field is mandatory.
+Set `isRequired` to make a selection mandatory. Use `necessityIndicator` to choose how it appears
+beside the label.
## Validation
-Pass field-level validation through React Aria `Form`. Use `errorMessage` to render the validation
-message.
+Use `errorMessage` to render a validation message. Pass server errors through React Aria `Form`.
```tsx
import { Form } from 'react-aria-components';
;
```
-## Grouped options
+## Groups
-Use `ComboboxSection` to group related options inside the listbox.
+Use `ComboboxSection` to group related static options.
-## Async options
+## Async results
-Pass `loadingState` for built-in loading and empty states. Control the option collection with
-`items`.
+Pass `items` and `loadingState` when results load or filter asynchronously. If the list is empty
+while loading or filtering, the field shows a loading indicator. An empty completed result shows “No
+results”.
```tsx
{(item) => {item.label}}
```
-## Infinite scroll
-
-Use `onLoadMore` for automatic sentinel-based loading, or `loadMoreItem` when you need full control
-over the load-more row.
-
-```tsx
-
- {(item) => {item.label}}
-
-```
+Pass `onLoadMore` for incremental results. It adds the built-in load-more row and spinner. Use
+`loadMoreItem` when that row needs custom content. `listBoxProps` and `popoverProps` configure the
+underlying listbox and popover. `menuWidth` sets the popover width.
-## Mobile tray
+## Mobile behaviour
-Below the `small` breakpoint (640px) the popover renders as a bottom tray instead of a positioned
-popover, following
-[Adobe Spectrum's combobox pattern](https://react-aria.adobe.com/blog/building-a-combobox). The tray
-spans the full viewport width, slides up from the bottom edge on open and back down on close, and
-keeps clear of the on-screen keyboard using the
-[Visual Viewport API](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API). A scrim
-dims the page behind the tray, and a grab-handle affordance marks its top edge. In browsers that
-support `calc-size()`, the tray keeps a usable minimum height even when the viewport is cramped,
-while still hugging shorter lists instead of leaving empty space.
+Below the `small` breakpoint (640px), options appear in a bottom tray instead of a positioned
+popover. The tray follows the visual viewport so it stays above the on-screen keyboard. The tray
+motion and desktop popover fade respect reduced-motion preferences.
## Accessibility
-The tray's slide transition (and the desktop popover's fade) is disabled for users who request
-`prefers-reduced-motion: reduce`.
+Provide a visible `label` where possible. The field passes React Aria combobox interactions and form
+semantics to its input, trigger, listbox, and validation message.
## Primitive
-Use the [combobox primitives](/docs/components/primitives/combobox) when you need a custom combobox
+Use the [combobox primitives](/components/primitives/combobox) when you need a custom combobox
layout.
## Props
diff --git a/apps/docs/content/docs/components/forms/text-field.mdx b/apps/docs/content/docs/components/forms/text-field.mdx
index 0680127c..222eeaa8 100644
--- a/apps/docs/content/docs/components/forms/text-field.mdx
+++ b/apps/docs/content/docs/components/forms/text-field.mdx
@@ -1,93 +1,85 @@
---
title: Text Field
-description: Single-line text input with label, validation, and adornments.
+description: Captures a single line of text with a label, validation, and optional adornments.
---
-Use `TextField` for a single text input with label, description, validation, and optional adornments
-built in.
+Use `TextField` to collect a single line of text. It combines an input with a label, description,
+and validation message.
-## Best practices
+## Labels and placeholders
-| Guidance | Practices |
-| -------- | --------------------------------------------------------------------------------------------------------------------------- |
-| Do | Use `label` for every field where possible. It works better with assistive technology and autofill than `aria-label` alone. |
-| Don't | Use `placeholder` as a label substitute. It disappears after typing and often fails colour contrast requirements. |
+Give the field a visible `label` where possible. Use `placeholder` for an example value or input
+format, not as the label. A placeholder disappears while the person types and is not a reliable
+accessible name.
+
+When a nearby heading already names the field, omit `label` and use `aria-label` or
+`aria-labelledby` instead.
+
+```tsx
+
+```
## Required fields
-Use `isRequired` and `necessityIndicator` to communicate mandatory fields. `'icon'` renders a visual
-required marker. `'label'` appends "(required)" to the label text.
+Set `isRequired` for required fields. `necessityIndicator="icon"` adds a visual marker.
+`necessityIndicator="label"` appends “(required)” to the label.
## Validation
-Pass field-level validation through React Aria `Form`. Use `errorMessage` to render the validation
-message.
+Use `errorMessage` to render a validation message. For client validation, use React Aria validation
+props. For server validation, pass errors through React Aria `Form`.
```tsx
import { Form } from 'react-aria-components';
;
```
## Adornments
-Use `adornmentStart` and `adornmentEnd` to place non-editable content inside the input chrome.
-Adornments accept any `ReactNode`. If an adornment is interactive, you are responsible for its
-semantics.
+Use `adornmentStart` and `adornmentEnd` for content inside the input, such as a search icon,
+currency code, or URL prefix. Both accept any `ReactNode`. Give interactive adornments an accessible
+name and keyboard behaviour.
## Size
-`size` controls height and typography. The HTML numeric `` attribute is intentionally
-omitted because `size` is reserved for the design-system variant.
-
-| Value | Description |
-| ---------- | --------------------- |
-| `'small'` | Compact input height. |
-| `'medium'` | Default input height. |
+Use `size` to set the input height and typography. Use `small` in compact layouts. `medium` is the
+default. The HTML numeric `size` attribute is unavailable because `size` is the Luke UI variant.
-## Accessibility
-
-When visual context already communicates purpose, you may omit `label` and provide an accessible
-name with `aria-label` or `aria-labelledby` on the field.
-
-```tsx
-
-```
-
## Primitive
-Use the [TextInput primitive](/docs/components/primitives/text-input) when you need the input
-without the label, description, and error slots that `TextField` provides.
+Use the [TextInput primitive](/components/primitives/text-input) when you need an input without the
+label, description, and error slots.
## Props
diff --git a/apps/docs/content/docs/components/layout/box.mdx b/apps/docs/content/docs/components/layout/box.mdx
index 1a782945..bfa1c24a 100644
--- a/apps/docs/content/docs/components/layout/box.mdx
+++ b/apps/docs/content/docs/components/layout/box.mdx
@@ -1,10 +1,11 @@
---
title: Box
-description: Responsive layout container backed by Luke UI Sprinkles.
+description: A div with responsive Luke UI layout properties.
---
-`Box` is a `div` by default. Use its responsive layout, spacing, sizing, positioning, overflow,
-flex, and grid-child props to build layout without adding styling props to other components.
+`Box` is a `div` that accepts Luke UI Sprinkles properties. Use it to place content in a layout, add
+token spacing, constrain an element's size, or control a flex or grid child. Use a normal `div` when
+no supported layout property is needed.
-Spacing props use `0` or the semantic space steps `100`, `200`, `300`, `400`, `600`, `800`, `1000`,
-`1200`, and `1600`. Responsive objects use the `xsmall`, `small`, `medium`, `large`, `xlarge`, and
-`xxlarge` breakpoints.
+## Layout properties
-## Custom div component
+Box supports display, logical margin and padding, inline and block sizing, positioning, overflow,
+gap, flex, and grid-child properties. It does not set grid container properties. Keep grid-template
+rules in the application CSS or a component that owns that layout.
-Use `render` with a compatible custom `div` component, such as a motion or presentational wrapper.
-Spread the provided props onto the actual `div` so the generated class, inline variables, ref,
-accessibility attributes, and event handlers are preserved. The callback does not change Box's DOM
-element contract to another element type.
+Use logical properties such as `paddingInline`, `marginBlockStart`, and `maxInlineSize`. They adapt
+to the writing direction without left and right variants.
+
+```tsx
+
+ {children}
+
+```
+
+## Responsive values
+
+Pass a direct value when it does not change. Use an object keyed by breakpoint when it does. Values
+cascade upward from `xsmall`, so only specify the changes.
+
+```tsx
+
+ {children}
+
+```
+
+The breakpoints are `xsmall` (base), `small` (640px), `medium` (768px), `large` (1024px), `xlarge`
+(1280px), and `xxlarge` (1536px).
+
+## Spacing values
+
+Padding, gap, and margin accept `0` and the semantic steps `100`, `200`, `300`, `400`, `600`, `800`,
+`1000`, `1200`, and `1600`. Margin also accepts `auto`. Sizing, inset, flex-basis, order, and
+grid-placement properties accept their CSS values.
+
+## Render a custom div
+
+Use `render` for a compatible custom `div`, such as a motion wrapper. Spread the supplied props on
+the final element so it receives Box's class name, inline style, ref, accessibility attributes, and
+event handlers. `render` does not make Box polymorphic. It must return a `div`.
```tsx
}>
@@ -29,8 +64,21 @@ element contract to another element type.
```
-Semantic colour and typography are deliberately not Box props. Use component APIs or public semantic
-variables from `@luke-ui/react/theme` for sanctioned custom styling.
+## Visual styles
+
+Box only covers layout. Use a component API for visual states and typography. When a custom element
+needs a colour, radius, or depth value, use the public variables from `@luke-ui/react/theme`.
+
+```tsx
+import { vars } from '@luke-ui/react/theme';
+
+
+ Account summary
+;
+```
## Props
diff --git a/apps/docs/content/docs/components/primitives/button.mdx b/apps/docs/content/docs/components/primitives/button.mdx
index 993788af..87e996d4 100644
--- a/apps/docs/content/docs/components/primitives/button.mdx
+++ b/apps/docs/content/docs/components/primitives/button.mdx
@@ -1,10 +1,11 @@
---
title: Button primitive
-description: Lower-level button primitive for custom button composition.
+description: Lower-level button styles and behaviour for custom composed controls.
---
-Use the button primitive when you need Luke UI button styling without the composed `Button` children
-wrapper, icon props, or pending-label behaviour.
+Use the button primitive to build a custom button pattern. It provides Luke UI button styles and
+React Aria button behaviour, while leaving child layout and loading treatment to you. For ordinary
+application actions, use [`Button`](/components/actions/button).
```tsx
import { Button } from '@luke-ui/react/button/primitive';
@@ -14,21 +15,30 @@ import { Button } from '@luke-ui/react/button/primitive';
;
```
-## When to use
+## Composition
-Use `@luke-ui/react/button/primitive` when you need full control over children layout, render-prop
-children, custom loading states, or non-standard button content.
+Use the primitive when a composed control needs custom children, render-prop children, or its own
+loading layout. The composed `Button` adds a truncating label, start and end icon slots, and a
+pending spinner. The primitive does not.
-Use [`Button`](/docs/components/actions/button) for most app actions.
+## Appearance and size
-## Behaviour
+Use `tone` and `appearance` to set visual emphasis. Use `size` to match nearby controls. Set
+`isBlock` when the button should take up its container's inline size. The primitive renders a native
+`
Back to Home
diff --git a/apps/docs/src/components/theme-controls.browser.test.tsx b/apps/docs/src/components/theme-controls.browser.test.tsx
index bb4fa143..ecc75ce7 100644
--- a/apps/docs/src/components/theme-controls.browser.test.tsx
+++ b/apps/docs/src/components/theme-controls.browser.test.tsx
@@ -96,6 +96,20 @@ test('bridges dark mode into the Luke UI root and example canvas', async () => {
expect(getComputedStyle(exampleCanvas).backgroundColor).not.toBe(lightBackground);
});
+test('leaves full-bleed story surfaces unframed', () => {
+ renderTheme(
+
+ Full-bleed example content
+ ,
+ );
+
+ const exampleContent = page.getByText('Full-bleed example content').element();
+ const storyRoot = exampleContent.parentElement;
+ if (!storyRoot) throw new Error('Expected a full-bleed story root');
+
+ expect(storyRoot).not.toHaveAttribute('style');
+});
+
test('keeps inherited docs shell text readable in dark mode', async () => {
renderTheme(
<>
diff --git a/apps/docs/src/examples/box/responsive-layout.tsx b/apps/docs/src/examples/box/responsive-layout.tsx
index d596f93d..80e4eea4 100644
--- a/apps/docs/src/examples/box/responsive-layout.tsx
+++ b/apps/docs/src/examples/box/responsive-layout.tsx
@@ -1,17 +1,40 @@
import { Box } from '@luke-ui/react/box';
+import { Text } from '@luke-ui/react/text';
import { vars } from '@luke-ui/react/theme';
export default function ResponsiveLayout() {
return (
- First item
- Second item
+
+
+ Quarterly product update
+
+
+ A summary for the team and stakeholders.
+
+
+
+
+ Published
+
+
+ 24 July 2026
+
+
);
}
diff --git a/apps/docs/src/examples/button/pending.tsx b/apps/docs/src/examples/button/pending.tsx
index 529cbfe5..2e5b4f81 100644
--- a/apps/docs/src/examples/button/pending.tsx
+++ b/apps/docs/src/examples/button/pending.tsx
@@ -1,9 +1,15 @@
+import { Box } from '@luke-ui/react/box';
import { Button } from '@luke-ui/react/button';
export default function Pending() {
return (
-
- Saving
-
+
+
+ Save changes
+
+
+ Save changes
+
+
);
}
diff --git a/apps/docs/src/examples/button/sizes.tsx b/apps/docs/src/examples/button/sizes.tsx
index 55781e32..0494b9b3 100644
--- a/apps/docs/src/examples/button/sizes.tsx
+++ b/apps/docs/src/examples/button/sizes.tsx
@@ -1,10 +1,15 @@
+import { Box } from '@luke-ui/react/box';
import { Button } from '@luke-ui/react/button';
export default function Sizes() {
return (
-
+
);
}
diff --git a/apps/docs/src/examples/heading/automatic-leveling.tsx b/apps/docs/src/examples/heading/automatic-leveling.tsx
index 289e9bfd..6a1b8466 100644
--- a/apps/docs/src/examples/heading/automatic-leveling.tsx
+++ b/apps/docs/src/examples/heading/automatic-leveling.tsx
@@ -4,11 +4,11 @@ import { HeadingLevels } from '@luke-ui/react/heading-context';
export default function AutomaticLeveling() {
return (
- h1
+ Product roadmap
- h2 nested automatically
+ Objectives
- h3 nested again
+ First quarter
diff --git a/apps/docs/src/examples/heading/typography.tsx b/apps/docs/src/examples/heading/typography.tsx
index 6c2470ff..52c0f59a 100644
--- a/apps/docs/src/examples/heading/typography.tsx
+++ b/apps/docs/src/examples/heading/typography.tsx
@@ -1,14 +1,18 @@
+import { Box } from '@luke-ui/react/box';
import { Heading } from '@luke-ui/react/heading';
export default function Typography() {
return (
-
-
- Informative heading
+
+
+ The quick brown fox jumps over the lazy dog
-
- Display-sized heading
+
+ The quick brown fox jumps over the lazy dog
-
+
+ The quick brown fox jumps over the lazy dog
+
+
);
}
diff --git a/apps/docs/src/examples/icon-button/size.tsx b/apps/docs/src/examples/icon-button/size.tsx
index eb176648..099f3a63 100644
--- a/apps/docs/src/examples/icon-button/size.tsx
+++ b/apps/docs/src/examples/icon-button/size.tsx
@@ -1,10 +1,11 @@
+import { Box } from '@luke-ui/react/box';
import { IconButton } from '@luke-ui/react/icon-button';
export default function Size() {
return (
-
+
);
}
diff --git a/apps/docs/src/examples/loading-skeleton/text.tsx b/apps/docs/src/examples/loading-skeleton/text.tsx
index 621d5775..5b9030e6 100644
--- a/apps/docs/src/examples/loading-skeleton/text.tsx
+++ b/apps/docs/src/examples/loading-skeleton/text.tsx
@@ -1,3 +1,4 @@
+import { Box } from '@luke-ui/react/box';
import { LoadingSkeleton } from '@luke-ui/react/loading-skeleton';
import { Text } from '@luke-ui/react/text';
import { useState } from 'react';
@@ -6,20 +7,27 @@ export default function TextSkeleton() {
const [isLoading, setIsLoading] = useState(true);
return (
-
+
- A short paragraph of placeholder copy that wraps across two lines.
+ Your application uses a shared type scale, colour system, and component library.
-
- setIsLoading(event.target.checked)}
- type="checkbox"
- />
- Loading
+
+
+ Your application uses a shared type scale, colour system, and component library.
+
+
+
+
+ setIsLoading(event.target.checked)}
+ type="checkbox"
+ />
+ Show placeholders
+
-
+
+
+
);
}
diff --git a/apps/docs/src/examples/text-field/basic.tsx b/apps/docs/src/examples/text-field/basic.tsx
index ec82f24d..1e3936fa 100644
--- a/apps/docs/src/examples/text-field/basic.tsx
+++ b/apps/docs/src/examples/text-field/basic.tsx
@@ -3,10 +3,10 @@ import { TextField } from '@luke-ui/react/text-field';
export default function Basic() {
return (
);
}
diff --git a/apps/docs/src/examples/text-field/required.tsx b/apps/docs/src/examples/text-field/required.tsx
index 6273004b..1ad7ce3a 100644
--- a/apps/docs/src/examples/text-field/required.tsx
+++ b/apps/docs/src/examples/text-field/required.tsx
@@ -1,10 +1,11 @@
+import { Box } from '@luke-ui/react/box';
import { TextField } from '@luke-ui/react/text-field';
export default function Required() {
return (
-
+
-
+
);
}
diff --git a/apps/docs/src/examples/text-field/sizes.tsx b/apps/docs/src/examples/text-field/sizes.tsx
index 35789690..e2146eac 100644
--- a/apps/docs/src/examples/text-field/sizes.tsx
+++ b/apps/docs/src/examples/text-field/sizes.tsx
@@ -1,10 +1,20 @@
+import { Box } from '@luke-ui/react/box';
import { TextField } from '@luke-ui/react/text-field';
export default function Sizes() {
return (
-
-
-
-
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/docs/src/examples/text/alignment.tsx b/apps/docs/src/examples/text/alignment.tsx
index 07e295ea..a6db3cca 100644
--- a/apps/docs/src/examples/text/alignment.tsx
+++ b/apps/docs/src/examples/text/alignment.tsx
@@ -1,9 +1,21 @@
+import { Box } from '@luke-ui/react/box';
import { Text } from '@luke-ui/react/text';
export default function Alignment() {
return (
-
- 12121.21
-
+
+
+ Start aligned
+
+
+ Centre aligned
+
+
+ End aligned
+
+
+ 12,121.21
+
+
);
}
diff --git a/apps/docs/src/examples/text/transform.tsx b/apps/docs/src/examples/text/transform.tsx
index f77b5698..a05c431d 100644
--- a/apps/docs/src/examples/text/transform.tsx
+++ b/apps/docs/src/examples/text/transform.tsx
@@ -1,9 +1,11 @@
+import { Box } from '@luke-ui/react/box';
import { Text } from '@luke-ui/react/text';
export default function Transform() {
return (
-
- Emphasized text
-
+
+ Project settings
+ View all activity
+
);
}
diff --git a/apps/docs/src/examples/text/truncation.tsx b/apps/docs/src/examples/text/truncation.tsx
index f9c14f4c..ee5b6fd4 100644
--- a/apps/docs/src/examples/text/truncation.tsx
+++ b/apps/docs/src/examples/text/truncation.tsx
@@ -1,11 +1,16 @@
+import { Box } from '@luke-ui/react/box';
import { Text } from '@luke-ui/react/text';
export default function Truncation() {
return (
-
- Long content that should be line clamped to two lines.
- Long content truncated to one line.
- Untrimmed text spacing
-
+
+
+ The weekly product update includes changes to reports, permissions, and saved views.
+
+
+ The weekly product update includes changes to reports, permissions, and saved views.
+
+ Text with its full line box
+
);
}
diff --git a/apps/docs/src/examples/text/typography.tsx b/apps/docs/src/examples/text/typography.tsx
index e8cc7e67..2e3d211f 100644
--- a/apps/docs/src/examples/text/typography.tsx
+++ b/apps/docs/src/examples/text/typography.tsx
@@ -1,14 +1,18 @@
+import { Box } from '@luke-ui/react/box';
import { Text } from '@luke-ui/react/text';
export default function Typography() {
return (
-
-
- Large heading treatment
+
+ The quick brown fox jumps over the lazy dog.
+ The quick brown fox jumps over the lazy dog.
+ The quick brown fox jumps over the lazy dog.
+
+ The quick brown fox jumps over the lazy dog.
-
- Important danger text
+
+ Supporting text uses a secondary colour role.
-
+
);
}
diff --git a/apps/docs/src/examples/theming/identity-and-mode.tsx b/apps/docs/src/examples/theming/identity-and-mode.tsx
new file mode 100644
index 00000000..ff9f3dac
--- /dev/null
+++ b/apps/docs/src/examples/theming/identity-and-mode.tsx
@@ -0,0 +1,97 @@
+import { Box } from '@luke-ui/react/box';
+import { Button } from '@luke-ui/react/button';
+import { Text } from '@luke-ui/react/text';
+import { themeRootClassName, vars } from '@luke-ui/react/theme';
+import { paperThemeClassName, tactileThemeClassName } from '@luke-ui/react/themes';
+import { cx } from '@luke-ui/react/utils';
+import { useState } from 'react';
+
+type ColorMode = 'dark' | 'light';
+type Identity = 'paper' | 'tactile';
+
+export default function IdentityAndModeExample() {
+ const [colorMode, setColorMode] = useState('light');
+ const [identity, setIdentity] = useState('tactile');
+ const identityClassName = identity === 'tactile' ? tactileThemeClassName : paperThemeClassName;
+
+ return (
+
+
+
+ Identity
+
+
+ {(['tactile', 'paper'] as const).map((option) => (
+ setIdentity(option)}
+ tone="accent"
+ >
+ {option === 'tactile' ? 'Tactile' : 'Paper'}
+
+ ))}
+
+
+
+
+ Colour mode
+
+
+ {(['light', 'dark'] as const).map((option) => (
+ setColorMode(option)}
+ tone="accent"
+ >
+ {option === 'light' ? 'Light' : 'Dark'}
+
+ ))}
+
+
+
+
+
+
+ Product updates
+
+
+ {identity === 'tactile' ? 'Tactile material' : 'Paper material'} in {colorMode} mode
+
+
+
+ View all
+
+
+
+
+ A new release is ready for review. The same components respond to this local theme
+ boundary.
+
+
+
+
+ );
+}
diff --git a/apps/docs/src/examples/theming/semantic-variables.tsx b/apps/docs/src/examples/theming/semantic-variables.tsx
index de83a6e8..56b8998f 100644
--- a/apps/docs/src/examples/theming/semantic-variables.tsx
+++ b/apps/docs/src/examples/theming/semantic-variables.tsx
@@ -1,27 +1,63 @@
+import { Box } from '@luke-ui/react/box';
+import { Text } from '@luke-ui/react/text';
import { vars } from '@luke-ui/react/theme';
-import type { PropsWithChildren } from 'react';
export default function SemanticVariablesExample() {
return (
-
- This surface uses public semantic variables from the active theme and colour mode.
-
+
+
+
+
);
}
-function CustomSurface({ children }: PropsWithChildren) {
+function PublicationStatus({ mode }: { mode: 'light' | 'dark' }) {
return (
-