Foundations
Composition and customisation
Choose a Luke UI component, primitive, or token for custom interface patterns.
Start with a semantic component when its purpose matches the task. It handles the intended behaviour, accessibility, and visual treatment together. Compose from a documented primitive only when a component's props do not fit.
Start with the component API
Button includes its standard label, icon slots, and pending treatment. Use the button primitive
when you need a different child layout or loading treatment while keeping Luke UI button behaviour
and styling.
Each primitive page documents the structure and accessibility responsibilities for that primitive: the label, description, state, and control relationships it expects. Preserve or author those relationships when you compose one. Do not depend on React Aria contexts, slots, or Luke UI implementation details as an application API.
import { Button } from '@luke-ui/react/primitives/button';
export function SaveShortcutButton() {
return (
<Button prominence="high">
<span>Save changes</span>
<span aria-hidden>⌘S</span>
</Button>
);
}Build a custom composition
Choose the primitive that owns the behaviour you need. Then compose its documented public parts.
Start from the control's semantic root, such as TextInputRoot or ComboboxRoot. The root connects
the label, description, and error inside it to the control. Inside the root, use Field for the
standard label, description, and error arrangement. This example adds a prefix with the
TextInput primitives. Use the combobox primitives when
ComboboxField does not fit your control, popover, or loading UI.
Use primitives when your layout owns a control's visible label, such as a settings row with the
label beside a switch. Place FieldLabel and FieldDescription inside the control's root. The root
names and describes the control from them, with no ids to pass. The
Switch primitive shows this layout.
Give the custom component the same accessibility care as a normal component. Preserve its documented structure. Provide an accessible name. Keep keyboard and focus behaviour intact. Use a normal React element for surrounding content when no primitive is needed.
Style from interaction state
Interactive primitives keep fixed semantics. Prefer state-aware className and style over root
replacement when you only need visual feedback. This example scales the button from press and hover
state without replacing the <button>:
Do not turn a button into a link. Use Link or IconLink, or style an owned element with
buttonRecipe.
Own a layout or typography root
elementType and renderRoot are opt-in. Layout primitives such as Box, and typography such as
Text and Heading, expose them when choosing or owning the root is a real composition need. Use
elementType for a supported semantic or structural element. Use renderRoot when a callback must
own the element and its DOM attributes. Do not combine them. Interactive controls generally omit
both.
Read the Box documentation for the supported elements and the renderRoot
callback's resolved props.
Style the new pattern
Use public semantic variables from @luke-ui/react/theme for custom surfaces, typography, colour,
radius, and shadow. They follow the active theme and colour mode. Read
Styling for when to use vars, and the
token reference for the contract.
Read the primitive documentation before you build a custom component from a specific primitive.