Headless Primitives
Unstyled component logic with full ARIA compliance and keyboard navigation. Zero styling opinions.
What Are Primitives?
Each primitive is a struct implementing the Component trait from stratum-core. Primitives handle all state, ARIA attributes, keyboard interactions, and focus management. They emit RenderOutput which framework adapters translate to actual DOM elements.
Primitives have zero styles and zero framework dependencies. They work with any Rust frontend framework.
The Component Trait
stratum-core
pub trait Component: Sized + 'static {
type Props: Props;
type State: State;
fn initial_state(props: &Self::Props) -> Self::State;
fn render(props: &Self::Props, state: &Self::State) -> RenderOutput;
fn on_event(props: &Self::Props, state: &mut Self::State, event: ComponentEvent) -> EventResult;
}All 23 Primitives
| Primitive | ARIA Role | Keyboard |
|---|---|---|
| Pressable | button | Enter, Space |
| Checkbox | checkbox | Space |
| RadioGroup | radiogroup + radio | Arrow keys, Space |
| Switch | switch | Space, Enter |
| Disclosure | button + region | Enter, Space |
| Dialog | dialog | Escape, Tab trap |
| AlertDialog | alertdialog | Escape, Tab trap |
| Tooltip | tooltip | Escape |
| Popover | dialog | Escape |
| Tabs | tablist + tab + tabpanel | Arrow, Home, End |
| Accordion | region | Enter, Space, Arrow, Home, End |
| Menu | menu + menuitem | Arrow, Enter, Space, Escape, Type-ahead |
| Select | combobox + listbox | Arrow, Enter, Escape, Space |
| TextInput | textbox | Standard text input |
| TextArea | textbox | Standard text input |
| Toast | status / alert | Escape |
| Progress | progressbar | — |
| Form | form | — |
| Toggle | button (aria-pressed) | Enter, Space |
| Separator | separator / none | — |
| Collapsible | region | Enter, Space |
| Portal | — | — |
| FocusScope | — | Tab trap |
| VisuallyHidden | — | — |
Controlled vs Uncontrolled
Every stateful primitive supports both patterns. In uncontrolled mode, the primitive manages its own state. In controlled mode, the consumer provides the state via props and receives change callbacks.
controlled.rs
// Uncontrolled — primitive manages state
<Disclosure default_open=false>...</Disclosure>
// Controlled — consumer manages state
<Disclosure open=is_open on_open_change=set_open>
...</Disclosure>