@solidrt/components 0.0.63 → 0.0.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/AGENTS.md +18 -14
  2. package/README.md +126 -21
  3. package/docs/badge.md +1 -1
  4. package/docs/checkbox.md +1 -1
  5. package/docs/context-menu.md +1 -1
  6. package/docs/item.md +1 -1
  7. package/docs/light.md +12 -0
  8. package/docs/motion.md +13 -0
  9. package/docs/press.md +19 -0
  10. package/docs/progress-bar.md +1 -1
  11. package/docs/radio.md +1 -1
  12. package/docs/segmented-control.md +1 -1
  13. package/docs/select.md +1 -1
  14. package/docs/slider.md +1 -1
  15. package/docs/surface.md +19 -0
  16. package/docs/switch.md +1 -1
  17. package/docs/theme.md +34 -6
  18. package/docs/tooltip.md +1 -1
  19. package/docs/types.md +1 -1
  20. package/docs/window.md +2 -0
  21. package/examples/lit.tsx +182 -0
  22. package/package.json +2 -2
  23. package/src/badge.tsx +25 -6
  24. package/src/button.tsx +44 -29
  25. package/src/card.tsx +76 -20
  26. package/src/checkbox.tsx +23 -19
  27. package/src/context-menu.tsx +21 -9
  28. package/src/editor-field.tsx +12 -7
  29. package/src/focus-nav.ts +17 -6
  30. package/src/glow.ts +35 -0
  31. package/src/index.ts +16 -1
  32. package/src/item.tsx +35 -13
  33. package/src/light.ts +274 -0
  34. package/src/motion.tsx +13 -1
  35. package/src/nav-shell.tsx +19 -10
  36. package/src/press.ts +2 -1
  37. package/src/pressable.tsx +6 -4
  38. package/src/progress-bar.tsx +25 -7
  39. package/src/radio.tsx +34 -22
  40. package/src/segmented-control.tsx +104 -23
  41. package/src/select.tsx +40 -34
  42. package/src/slider.tsx +74 -22
  43. package/src/surface.tsx +287 -0
  44. package/src/switch.tsx +48 -26
  45. package/src/theme.ts +201 -40
  46. package/src/tooltip.tsx +18 -14
  47. package/src/types.ts +66 -2
  48. package/src/window.tsx +26 -3
package/AGENTS.md CHANGED
@@ -20,8 +20,8 @@ Most components group props into two objects, plus top-level event handlers:
20
20
  position. For `Text`, also the font fields (fontSize, fontWeight, ...).
21
21
  Changing these relayouts.
22
22
  - `style={{...}}` - paint only, never affects layout: `backgroundColor`,
23
- `borderColor`, `borderWidth`, `borderRadius`, `color` (Text), and the
24
- transform `x`/`y`/`rotate`/`scale`.
23
+ `borderColor`, `borderWidth`, `borderRadius`, `color` (Text), `glow`, and
24
+ the transform `x`/`y`/`rotate`/`scale`.
25
25
  - Event handlers (`onPointerDown`, `onKeyDown`, ...) are top-level props, NOT
26
26
  inside `layout`/`style`.
27
27
 
@@ -80,35 +80,39 @@ One bullet per module, generated from the first paragraph of its docs/ file.
80
80
  - `Dismissible` - Swipe-to-dismiss. The content follows the finger sideways; a swipe that qualifies (enough travel and speed, within 30 degrees of the axis, core's swipe recognizer) carries it out of the box at its own speed and reports `onDismiss` with the direction, a drag that stops short springs back. `direction` narrows which way dismisses (`"left"`, `"right"`, default both); a drag the other way is not started, and a vertical drag is left to an enclosing ScrollView. The recognizer takes the pointer at its movement slop, so a pressable row still presses on a tap and retracts once the drag is one. Mouse and touch alike.
81
81
  - `Carousel` - A pager. The children are the pages, each one box wide, laid side by side and moved with the finger: a horizontal swipe (core's swipe recognizer) turns the page, a drag that stops short snaps to the nearest one, and the page settles under a spring. Only horizontal drags are taken, so a vertical ScrollView around or inside it scrolls as before. `index` controls the page (pair it with `onChange`); without it the carousel keeps its own page and still reports turns.
82
82
  - `Pressable` - A pressable box: `onPress` fires on a primary-button press released over the box; a drag out of the box (or a non-primary button) does not fire it, and a drag back in restores the pressed state. `children` and `style` may each be a function of the live `{ pressed, hovered, focused, pending }` state, so the box restyles on press/hover without extra signals - read the state inside the prop or child expression, never eagerly into a local.
83
+ - `createPress` - The press recognizer the package's own controls are built on, for a custom control that wants its own root view instead of wrapping `Pressable`: `onPress` fires on a primary-button down followed by an up over the node, a drag out retracts the pressed state and a drag back in restores it, nested recognizers resolve innermost-first through core's arena, and the node's `ref` registers `onPress` as its focus-navigation `select` action so a remote or keyboard activates it while it holds focus. Spread `handlers` on the root view, attach `ref`, and read `pressed()`, `hovered()`, `focused()` and `pending()` (or `state()`, the live object the `Pressable` render prop receives) inside the props that style on them. Options are read at event time, so a component's reactive props object can be passed as is.
83
84
  - `Button` - A themed press target over `Pressable`: a padded, centered box with a label. A press shrinks it slightly on a quick spring and tints it with `overlayPressed`; hover tints with `overlayHover` (non-touch policies). `variant` picks the visual role - `primary` (accent fill, the default), `secondary`, `ghost` (no fill until hover), `danger` (destructive) - with fill, tints, and label color from the matching theme tokens; no variant draws a border. `size` (`sm`/`md`/`lg`) pins a minimum width so a row of buttons lines up (a longer label still expands past it); omitted, the button sizes to its content. A string or number child renders as the themed label; any other child renders as-is (an icon, a row, ...).
84
85
  - `createFocusNav` - Focus navigation for pointer-free control (TV remote, keyboard, gamepad), moving real focus across the elements declaring `focusable`. It consumes three UI actions from an input map and reads no device: `navigate` (vec2) moves spatially, picking the nearest candidate in the pressed direction by on-screen boxes; `cycle` (axis) walks visual reading order - rows top to bottom, left to right - wrapping at the ends; `select` (button) activates the focused control; a held direction repeats. Created bare, the nav binds the standard set itself (`uiBindings`: arrows, Tab and Shift+Tab, Enter, Space and the remote center key on the keyboard; dpad, left stick and south on every pad) on a map of its own, reachable as `nav.input` for rebinding; given `input`, it consumes the app's map instead. Spread `nav.handlers` on the window. Nothing is focused until the first navigation press; pointer input works unchanged throughout. Every interactive control (Button, Item, TextInput, RichTextEditor, Checkbox, Radio, Switch, Select and its options, SegmentedControl segments, Slider) is a candidate unless disabled, and draws the theme's `ring` color at `borderWidth.focus` while focused under the `focusRing` policy; the Slider steps its value with the arrow keys.
85
- - `Switch` - An on/off toggle: the track fills with `primary` when on and `surfaceAlt` when off (a fade), and the thumb springs across - the `knob` transition entry retimes that travel. Controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Built on `Pressable`, so `disabled` takes no pointer events. `style` overrides the track colors and radius.
86
- - `Checkbox` - A checkbox: filled with `primary` and a drawn checkmark when checked, an empty bordered box otherwise - the fill fades and the mark pops in and out. Controlled via `checked`/`onChange`, or uncontrolled via `defaultChecked`. The mark is the `theme.icons.check` slot when a theme sets one. `style` overrides the box colors, border, and radius.
87
- - `RadioGroup / Radio` - A single-selection pair: `RadioGroup` owns the selected value (controlled via `value`/`onChange`, or uncontrolled via `defaultValue`) and shares it with its `Radio` children; each `Radio` is a ring with an inner dot when selected - the ring color fades and the dot pops in and out. A string/number child of `Radio` renders as a themed label beside the ring; anything else as-is. `disabled` on the group disables every option, on a `Radio` just that one.
88
- - `Slider` - A horizontal slider: the groove fills up to the thumb, and pressing or dragging the track sets the value from the pointer position. Controlled via `value`/`onChange` (fires while dragging), or uncontrolled via `defaultValue` (defaults to `min`). `min`/`max` default to 0/100; `step` snaps to an increment, omitted the value is continuous. The drag keeps tracking when the pointer drifts off the track, and an enclosing ScrollView never takes it over.
86
+ - `Switch` - An on/off toggle: the track fills with `primary` when on and `surfaceAlt` when off (a fade), and the thumb springs across - the `knob` transition entry retimes that travel. Controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Built on `Pressable`, so `disabled` takes no pointer events. The thumb is `theme.color.thumb` (else `onPrimary`), track and thumb follow `radius.full` through `pill` (square in an all-square theme), and the on track glows with the theme's accent glow. `style` overrides the track colors and radius.
87
+ - `Checkbox` - A checkbox: filled with `primary` and a drawn checkmark when checked, an empty bordered box otherwise - the fill fades and the mark pops in and out. Controlled via `checked`/`onChange`, or uncontrolled via `defaultChecked`. The mark is the `theme.icons.check` slot when a theme sets one. The checked fill glows with the theme's accent glow. `style` overrides the box colors, border, and radius.
88
+ - `RadioGroup / Radio` - A single-selection pair: `RadioGroup` owns the selected value (controlled via `value`/`onChange`, or uncontrolled via `defaultValue`) and shares it with its `Radio` children; each `Radio` is a ring with an inner dot when selected - the ring color fades and the dot pops in and out. Ring and dot follow `radius.full` through `pill` (square in an all-square theme), and the dot glows with the theme's accent glow. A string/number child of `Radio` renders as a themed label beside the ring; anything else as-is. `disabled` on the group disables every option, on a `Radio` just that one.
89
+ - `Slider` - A horizontal slider: the groove fills up to the thumb, and pressing or dragging the track sets the value from the pointer position. Controlled via `value`/`onChange` (fires while dragging), or uncontrolled via `defaultValue` (defaults to `min`). `min`/`max` default to 0/100; `step` snaps to an increment, omitted the value is continuous. The drag keeps tracking when the pointer drifts off the track, and an enclosing ScrollView never takes it over. The groove is `surfaceAlt`, the fill `primary` (glowing with the theme's accent glow) and the thumb `theme.color.thumb` (else `primary`); `style` and `theme.components.slider` restyle them as ProgressBar's do: `backgroundColor` the groove, `color` the fill, `borderRadius` the groove's ends (round through `pill`, square under `radius.full: 0`), `borderColor`/`borderWidth` a box around the groove.
89
90
  - `Card` - A themed surface container: a padded column box with a `surface` fill, a subtle `border` stroke, and rounded corners, recoloring live on a theme switch. Pass a `title` for a heading, or lay out the content yourself; override paint via `style`, spacing/sizing via `layout`.
90
91
  - `Item` - A list row: `startContent` (icon, avatar, checkbox), a `label` with an optional `description` under it, and `endContent` (badge, timestamp, action) pushed to the end. String/number label and description render as themed body and muted body text; anything else as-is. The dense-data workhorse: rows compose with `<For>` inside a plain column view or `ScrollView` - there is no List wrapper, because a column IS the list. Paddings and gaps are density-scaled, so a `<Density>` region compacts rows wholesale.
91
92
  - `Field` - A form row: `label` above the control, the control itself (`children`, rendered as-is), and a help or error line below. `error` renders in the danger color and replaces `description` while set. It draws no chrome and does not reach into the control - error styling of the input itself stays the input's `style` prop, no hidden magic. The message line only occupies space while there is one; reserve the space with a constant `description` if the form must not jump when an error appears.
92
93
  - `Divider` - A thin rule in the theme `border` color. It stretches across its container on the cross axis: full width inside a column, full height inside a row (pass `orientation="vertical"`). `thickness` defaults to 1px; add spacing with `layout` margins, and override the color via `style.backgroundColor`.
93
- - `Badge` - A small rounded pill for counts, labels, and status. `variant` picks the role: `primary` (accent fill, the default), `neutral` (subtle surface), `danger`. A string/number child renders as the themed label, anything else as-is (an icon, a dot, ...). Override the fill via `style.backgroundColor` and the label color via `style.color`.
94
+ - `Badge` - A small rounded pill for counts, labels, and status. `variant` picks the role: `primary` (accent fill, the default), `neutral` (subtle surface), `danger`. A string/number child renders as the themed label, anything else as-is (an icon, a dot, ...). Override the fill via `style.backgroundColor` and the label color via `style.color`; `borderColor`/`borderWidth` box it. The accent variants glow with the theme's accent glow.
94
95
  - `Spinner` - An indeterminate spinner: a 270-degree arc that rotates continuously, driven by core `onFrame`, so it participates in demand-driven rendering and stops when unmounted. `size` (diameter, default 24), `thickness` (default 3), `speed` (revolutions per second, default 1). Color comes from the theme `primary`; override via `style.color`.
95
- - `ProgressBar` - A horizontal progress bar: determinate when given a `value` in `[0, 1]` (the fill grows from the left, gliding to each new value - the `fill` transition entry retimes it), indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track is `surfaceAlt`, fill is `primary`; override via `style.backgroundColor` (track) and `style.color` (fill).
96
+ - `ProgressBar` - A horizontal progress bar: determinate when given a `value` in `[0, 1]` (the fill grows from the left, gliding to each new value - the `fill` transition entry retimes it), indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track is `surfaceAlt`, fill is `primary`; override via `style.backgroundColor` (track) and `style.color` (fill), and box the track with `borderColor`/`borderWidth`. The ends follow `radius.full` through `pill`, and the fill glows with the theme's accent glow.
96
97
  - `Portal` - Renders its child somewhere other than its lexical position: by default at the window root, so overlays (modals, menus, tooltips) escape the clipping and stacking of their surrounding layout; `mount` targets another node captured from a `ref` instead. A thin JSX wrapper over core `createPortal`. The child should be a single element with `position="absolute"`, since it is inserted into the window's flex root. Portals cannot mount during the app's initial render, so gate them behind a signal that starts false.
97
98
  - `Modal` - A centered overlay rendered at the window root via core `createPortal`: it fills the window with a dimming backdrop (theme `scrim`; override via `backdropColor`, `"transparent"` for no dim) and centers `children` on top, the whole overlay fading in at mount and out on removal (an exiting modal takes no hits). Control visibility by mounting/unmounting it, e.g. `<Show when={open()}>`; the gating signal must start false since portals cannot mount during the initial render. Pressing the backdrop calls `onClose` (unless `dismissable` is false), pressing the content does not, and while mounted the modal traps `createFocusNav` inside itself.
98
- - `Tooltip` - A hover-only affordance: under the `desktop`/`hybrid` interaction policies, resting a mouse pointer on the wrapped content shows a bubble near it after `delay` (default 500ms). Under the `touch` policy it never shows, so tooltip content must stay non-essential. The bubble is portal-mounted at the window root, clamped to the window edges, takes no pointer events, fades in and out, and hides on leave and on press. A string/number `content` renders as themed body text; anything else as-is. `placement` picks the side (`"top"`, the default, or `"bottom"`).
99
- - `Select` - A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside closes without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
100
- - `SegmentedControl` - A single-choice row of equal-width segments joined flush: only the control's outermost corners are rounded, hairline dividers separate the segments, and the active segment is one `primary` indicator that springs between segments on a selection change - the `indicator` transition entry retimes it. Hovered segments tint with the theme `overlayHover` under non-touch interaction policies. `options` is an `Option[]`; controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Override the inactive fill via `style.backgroundColor` and the outer radius via `style.borderRadius`.
101
- - `ContextMenu` - Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside closes without selecting.
99
+ - `Tooltip` - A hover-only affordance: under the `desktop`/`hybrid` interaction policies, resting a mouse pointer on the wrapped content shows a bubble near it after `delay` (default 500ms). Under the `touch` policy it never shows, so tooltip content must stay non-essential. The bubble is portal-mounted at the window root, clamped to the window edges, takes no pointer events, fades in and out, hides on leave and on press, and glows with the theme's overlay glow. A string/number `content` renders as themed body text; anything else as-is. `placement` picks the side (`"top"`, the default, or `"bottom"`).
100
+ - `Select` - A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside closes without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, the dropdown glows with the theme's overlay glow, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
101
+ - `SegmentedControl` - A single-choice row of equal-width segments joined flush: only the control's outermost corners are rounded, hairline dividers separate the segments, and the active segment is one `primary` indicator that springs between segments on a selection change - the `indicator` transition entry retimes it. Hovered segments tint with the theme `overlayHover` under non-touch interaction policies. `options` is an `Option[]`; controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Override the inactive fill via `style.backgroundColor` and the outer radius via `style.borderRadius`, and box the control with `borderColor`/`borderWidth`. The indicator glows with the theme's accent glow.
102
+ - `ContextMenu` - Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside closes without selecting.
102
103
  - `NavShell` - An app shell that arranges primary navigation around the content per `policy.navigation`: bottom tabs under it (`bottomTabs`), a narrow rail (`rail`), or a wide sidebar (`sidebar`) beside it. The content is a single stable node; switching arrangement only flips the shell's flex direction and remounts the stateless nav strip, so page state survives a resize across a breakpoint. `items` is a `NavItem[]` (`{ value, label, icon? }`; the icon renders above the label in tabs/rail, beside it in the sidebar); controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Safe areas are the caller's concern: wrap the shell in `SafeArea`. With `@solidrt/router` the shell is the tabs route's component, `value`/`onChange` wired to `useTabs()` (`active()`/`select(path)`) and `<Outlet />` as the content; the router then keeps a stack per tab and the tabs mounted.
103
104
  - `SplitView` - A list-detail container driven by `policy.layout`: `twoPane` shows the `list` pane (width `listWidth`, default `theme.size.splitViewList`) beside the `detail` pane, `singlePane` shows one at a time per `showDetail`. Keep pane state (selection, scroll) in the app, not in the panes: crossing a breakpoint re-arranges and can remount them. It draws no chrome and adds no padding; a back affordance in the single-pane detail is the app's to render (fork on `policy.layout`).
104
105
  - `QrCode` - Renders a QR code for `data` out of primitives: same-color modules in a row collapse into one box, drawn on a light quiet-zone panel; the grid recomputes only when `data` or `level` changes. It paints black on white by default (not the theme) so it stays scannable through a theme switch; override `color`/`background` only if the contrast still holds. `moduleSize` (default 6) is pixels per module, `margin` (default 16) the quiet zone (keep it non-zero), `level` the error correction (`L`/`M`/`Q`/`H`, default `M`: higher tolerates more damage but caps data length sooner), `radius` the panel's corner radius.
105
106
  - `Icon` - A thin themed wrapper over the core `parseSvg` primitive. `src` is a whole SVG document as a string; the component parses it once (memoized), maps the draws to `<d-path>` in a square `designSize`-fitted box (`size`, default 24) and, for monochrome icons that stroke/fill with `currentColor`, recolors it via `color` (default the theme text color). It carries no icon set and no name registry, so any `currentColor` SVG works (Lucide, Feather, Heroicons, ...) and only the icons you import are bundled. Multi-color documents keep their own fills. For a non-square box, use `parseSvg` directly.
106
- - `Theming` - Appearance (colors, spacing, border, font roles) comes from one shared, reactive theme backed by a Solid store: reads are tracked, so switching the theme at runtime recolors the live UI without remounting. Two presets ship, `darkTheme` and `lightTheme` (default dark); `setTheme(preset)` switches, `setTheme(partial)` merges an override one level deep per category. Custom themes are authored with `defineTheme`.
107
+ - `Surface` - The one way a component draws a face: a role's material (`theme.material`) under the theme light (`theme.light`) at an elevation (`theme.elevation`), stacked as layers that fill the view it is rendered in - a contact shadow, the glow, the fill (tinted by the light, with the sheen gradient along it, carrying the key shadow the elevation casts), the hover/pressed tint, the bevel and the outline. Every themed component draws its chrome through it, so a theme's light, materials and elevation restyle the whole library at once; under the stock presets (flat materials, zero elevation) a face is a plain fill, the tint and the outline, the look the package has always had. A custom control uses it the same way: render it as the first child of the laid-out view it dresses, pick a `role` (never a material), give it the base `fill`, the `radius`, the press state for the tint and the sink, and an `outline` for its border or focus ring.
108
+ - `lightFrom` - The light helpers for an app that moves the theme light. `theme.light.direction` is the way the light travels in screen space (x right, y down, z into the screen); `lightFrom(px, py)` turns a point on the unit disk - where the light comes from, a light pad's coordinates - into that direction, and `lightSource(light)` is its inverse, so a pad shows the current light. `lightAxis(direction)` is the quantized, interned axis every face derives its paint from: equal steps return the same object, which is why a dragged light costs a face a re-derivation only when the axis steps.
109
+ - `PressFeedback` - The hover/pressed tint every pressable face draws over its own fill: one always-mounted detached rect in `overlayHover` while hovered and `overlayPressed` while pressed, at alpha 0 otherwise, fading at the theme's feedback speed. Surface draws it as its tint layer; it is exported for a control that draws its own rows over a face it does not own (the option rows of a Select or ContextMenu).
110
+ - `Theming` - Appearance (colors, spacing, border, font roles) comes from one shared, reactive theme backed by a Solid store: reads are tracked, so switching the theme at runtime recolors the live UI without remounting. Four presets ship, `darkTheme` and `lightTheme` (the flat look, default dark) and `litDarkTheme` and `litLightTheme` (the same palette under a light, see below); `setTheme(preset)` switches, `setTheme(partial)` merges an override one level deep per category. A resolved theme carries every key of every category (unset ones as `undefined`), so switching to one replaces the previous theme outright, per-component overrides and icon slots included; a partial keeps whatever it does not name. Custom themes are authored with `defineTheme`.
107
111
  - `Policies` - Theme answers "how does it look"; policies answer "how does it behave". `policy` is a second reactive layer derived from the platform facts in `@solidrt/core` (`capabilities`, `env`), so components adapt to touch vs. desktop, window size, and display without every app wiring that logic itself. Reads are reactive like `theme`: a window resize or the first mouse move on a touch-capable device updates every consuming component live.
108
112
  - `Density` - `<Density value="compact">` overrides the density policy for its subtree: every density-scaled metric below - `space()`, control sizes (Checkbox, Switch, Radio, Slider), `Item` and `Button` paddings - resolves this value instead of the global `policy.density`. Regions nest; the nearest wins. Use it to tighten a toolbar, a data table, or a sidebar without per-child props.
109
113
  - `Typography helpers` - `typeStyle(variant)` resolves a theme type-scale role (`caption`/`label`/`body`/`title`/`heading`) to font props ready to spread onto a `<text>` or `d-text`: `fontSize` carries `policy.textScale`, and `fontWeight` carries the low-DPI weight compensation. Reactive when called inside a tracked scope, like any theme/policy read. `Text` applies it for you; reach for the helpers when building custom text out of core primitives.
110
114
  - `Spacing` - `space(token)` is density-scaled spacing: a `theme.spacing` token (`sm`/`md`/`lg`/`xl`) multiplied by the density scale of the nearest `<Density>` region (falling back to the global policy) and rounded to whole pixels. Use it for gaps and paddings that should tighten under `compact`/`dense` density; read `theme.spacing` directly only for distances that must not move with density. Reactive when called inside a tracked scope.
111
- - `Layout and style` - Most components group their props into two objects, split by one rule: `layout` properties feed the layout engine (flexbox/grid, sizing, padding, margin, position - the core `LayoutProps` set) and changing them triggers a relayout; `style` properties are paint-only and never affect layout: `color`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, `opacity`, and the transform (`x`, `y`, `scale`, `rotate`, `rotateX`/`rotateY` with `perspective`, `originX`/`originY`, `clipRadius`). Event handlers (`onPointerDown`, `onKeyDown`, ...) are top-level props, never inside `layout` or `style`.
115
+ - `Layout and style` - Most components group their props into two objects, split by one rule: `layout` properties feed the layout engine (flexbox/grid, sizing, padding, margin, position - the core `LayoutProps` set) and changing them triggers a relayout; `style` properties are paint-only and never affect layout: `color`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, `opacity`, `glow` (a halo on the component's accent part; `null` clears the theme's), `material` (layers merged over the role's material for the component's faces) and `elevation` (the level its raised face stands at), see Theming, and the transform (`x`, `y`, `scale`, `rotate`, `rotateX`/`rotateY` with `perspective`, `originX`/`originY`, `clipRadius`). Event handlers (`onPointerDown`, `onKeyDown`, ...) are top-level props, never inside `layout` or `style`.
112
116
  <!-- END GENERATED: exports -->
113
117
 
114
118
  ## Internals worth knowing
package/README.md CHANGED
@@ -16,7 +16,7 @@ Per-component prose lives in `docs/`, one file per module; the props are the typ
16
16
 
17
17
  ## Theming
18
18
 
19
- Appearance (colors, spacing, border, font roles) comes from one shared, reactive theme backed by a Solid store: reads are tracked, so switching the theme at runtime recolors the live UI without remounting. Two presets ship, `darkTheme` and `lightTheme` (default dark); `setTheme(preset)` switches, `setTheme(partial)` merges an override one level deep per category. Custom themes are authored with `defineTheme`.
19
+ Appearance (colors, spacing, border, font roles) comes from one shared, reactive theme backed by a Solid store: reads are tracked, so switching the theme at runtime recolors the live UI without remounting. Four presets ship, `darkTheme` and `lightTheme` (the flat look, default dark) and `litDarkTheme` and `litLightTheme` (the same palette under a light, see below); `setTheme(preset)` switches, `setTheme(partial)` merges an override one level deep per category. A resolved theme carries every key of every category (unset ones as `undefined`), so switching to one replaces the previous theme outright, per-component overrides and icon slots included; a partial keeps whatever it does not name. Custom themes are authored with `defineTheme`.
20
20
 
21
21
  ```jsx
22
22
  import { setTheme, darkTheme, lightTheme } from "@solidrt/components"
@@ -49,7 +49,7 @@ The type scale derives from `text.base` (the body size, default 14) and `text.ra
49
49
 
50
50
  ### Tokens
51
51
 
52
- The color tokens are `background` (window fill), `surface` (control/card fill), `surfaceAlt` (subtle raised/track fill), `text`, `textMuted`, `border`, `primary`/`onPrimary`, `secondary`/`onSecondary` (lower-emphasis accent), `danger` (validation/destructive), `scrim` (modal dim), `ring` (the focus ring; defaults to `text` so it stays visible on primary fills), and the feedback pair `overlayHover`/`overlayPressed`: translucent tints components draw OVER a control's own fill, so one token pair gives hover/pressed feedback on every fill color, including caller-set ones. Non-color tokens are `spacing`, `radius`, `borderWidth` (`sm` for borders, `focus` for the ring), `size` (app-wide default extents: `navRail` 72, `navSidebar` 220, `splitViewList` 320, `menuMinWidth` 120, `slider` 200; each overridable per instance through its layout or prop), and `text` (the type scale: `caption`/`label`/`body`/`title`/`heading` roles, each `{ size, lineHeight, weight }`, plus `fontFamily` and `monoFamily` for code).
52
+ The color tokens are `background` (window fill), `surface` (control/card fill), `surfaceAlt` (subtle raised/track fill), `text`, `textMuted`, `border`, `primary`/`onPrimary`, `secondary`/`onSecondary` (lower-emphasis accent), `danger` (validation/destructive), `scrim` (modal dim), `ring` (the focus ring; defaults to `text` so it stays visible on primary fills), `thumb` (optional: the cap riding a track, the Switch knob and the Slider thumb; unset they are `onPrimary` and `primary`, so a palette whose `onPrimary` is dark sets a light cap here), and the feedback pair `overlayHover`/`overlayPressed`: translucent tints components draw OVER a control's own fill, so one token pair gives hover/pressed feedback on every fill color, including caller-set ones. Non-color tokens are `spacing`, `radius`, `borderWidth` (`sm` for borders, `focus` for the ring), `size` (app-wide default extents: `navRail` 72, `navSidebar` 220, `splitViewList` 320, `menuMinWidth` 120, `slider` 200; each overridable per instance through its layout or prop), and `text` (the type scale: `caption`/`label`/`body`/`title`/`heading` roles, each `{ size, lineHeight, weight }`, plus `fontFamily` and `monoFamily` for code).
53
53
 
54
54
  ### Spacing
55
55
 
@@ -57,7 +57,7 @@ Spacing is one base unit: `spacing` in a theme definition is a number (default 4
57
57
 
58
58
  ### Radius
59
59
 
60
- Corner radius is set once: `radius` in a theme definition is a single number, the control radius (default 8), and the scale derives from it: `md` is the base (Button, TextInput, RichTextEditor, Select, SegmentedControl, QrCode), `sm` half of it (Checkbox, Item, NavShell items, Select and ContextMenu popups, Tooltip), `lg` one and a half (Card), and `full` the pill (Badge). Set `radius: 0` for a square theme, `radius: 12` for a soft one; buttons and inputs always match. Shapes derived from a control's own height (Switch, Slider, ProgressBar, Radio) are not on the scale. Pass an object (`radius: { sm, md, lg, full }`, any subset) to pin individual steps instead.
60
+ Corner radius is set once: `radius` in a theme definition is a single number, the control radius (default 8), and the scale derives from it: `md` is the base (Button, TextInput, RichTextEditor, Select, SegmentedControl, QrCode), `sm` half of it (Checkbox, Item, NavShell items, Select and ContextMenu popups, Tooltip), `lg` one and a half (Card), and `full` the pill (Badge). Set `radius: 0` for a square theme, `radius: 12` for a soft one; buttons and inputs always match. Shapes derived from a control's own height (Switch, Slider, ProgressBar, Radio) take `full` too, through `pill(size)`: half the size, capped by `radius.full`, so `full: 0` squares every pill and circle in the library (`radius: { sm: 0, md: 0, lg: 0, full: 0 }` is the all-square theme) while the default 9999 leaves them round. `pill` is exported for a custom control with a round part. Pass an object (`radius: { sm, md, lg, full }`, any subset) to pin individual steps instead.
61
61
 
62
62
  ```jsx
63
63
  setTheme({ radius: 4 }) // sm 2, md 4, lg 6
@@ -65,21 +65,49 @@ setTheme({ radius: 4 }) // sm 2, md 4, lg 6
65
65
 
66
66
  ### Motion
67
67
 
68
- `motion` holds the three durations (ms) every built-in component transition draws from, so one theme edit retimes the whole package: `fast` (default 100) is press/hover feedback, `base` (150) the color and opacity fades - state changes, the theme cross-fade (a `setTheme` fades every themed color rather than snapping), popup enter/exit - and `slow` (250) the travel of a control's moving parts (switch knob, segmented indicator, progress fill). `policy.motion` gates whether these play at all; a per-instance `transition` prop overrides them per property.
68
+ `motion` holds the three durations (ms) every built-in component transition draws from, so one theme edit retimes the whole package: `fast` (default 100) is press/hover feedback, `base` (150) the color and opacity fades - state changes, the theme cross-fade (a `setTheme` fades every themed color rather than snapping), popup enter/exit - and `slow` (250) the travel of a control's moving parts (switch knob, segmented indicator, progress fill) and of a face's shadows as it sinks or lifts. `policy.motion` gates whether these play at all; a per-instance `transition` prop overrides them per property.
69
69
 
70
70
  ```jsx
71
71
  setTheme({ motion: { base: 250, slow: 400 } }) // a slower, calmer app
72
72
  ```
73
73
 
74
+ ### Light, materials and elevation
75
+
76
+ Every component draws its faces through `Surface`, and three theme sections decide what a face looks like. `light` is the theme's one light in screen space (`direction` [x right, y down, z into the screen], a `color`, and `ambient`, the fill on the shaded side): shadows fall along it, the sheen and the bevel run from its lit edge to the shaded one, and faces take on its color (a white light leaves every base color as it is). `material` assigns each role its layers: `surface` (Card, Item, the NavShell strips), `control` (a secondary Button, the fields, the Select trigger, the tracks and grooves, the Switch knob and the Slider thumb), `accent` (a primary or danger Button, Switch on, a checked Checkbox, the Radio dot, the Slider and ProgressBar fills, the SegmentedControl indicator, a Badge) and `overlay` (the Tooltip bubble, the Select dropdown, the ContextMenu menu, the sheets). A `Material` is `{ sheen?, bevel?, glass?, glow? }`: `sheen` (0..1) a fill gradient along the light, `bevel` (0..1) a one-pixel stroke from the light color to black, `glass` a frosted backdrop (`{ blur, tint? }`, a deliberate overlay, never a surface material: every glass face captures the pixels under it each frame), `glow` a halo (`{ radius, color? }`; without a color a part glows in its own fill, so a danger button glows red, and a fill with no color to take casts nothing; a pressable face widens it while hovered or pressed). `elevation` maps the four levels (`flat`, `raised`, `floating`, `overlay`) to heights: a face at height h casts a key shadow h px along the light and blurred 2.5 h px, plus a tight contact shadow; a press sinks the face (the key shadow fades toward the contact shadow) on the native shadow transition, and `shadow` (`{ color, strength }`) is what they are cast in.
77
+
78
+ The stock presets are flat: every material `{}` and every height 0, so nothing casts or shines and the components look as they always have. `litDarkTheme` and `litLightTheme` are the same palette under the light: a sheen and a bevel per role, `raised` 2, `floating` 6, `overlay` 14, a light cap for the Switch knob and Slider thumb, and shadows 1.7 times stronger on dark, where they otherwise vanish. Card and Button stand raised by default (a ghost or disabled button flat), the Switch knob, the Slider thumb and the SegmentedControl indicator too; Item, Badge and the Checkbox sit flat whatever the theme, since a screen holds dozens of them and a blurred shadow per row is what a scrolling list cannot afford on a tablet or a TV (measured in the runtime's notes); popups float. Per component, `theme.components.<name>.material` merges layers over the role's, `.elevation` sets the level and `.glow` the halo alone, and an instance's `style` does the same over that.
79
+
80
+ ```jsx
81
+ setTheme(litDarkTheme) // the lit look
82
+ setTheme({ light: { ...theme.light, direction: [-0.4, 1, 0.8] } }) // light from the right
83
+ setTheme({ material: { accent: { sheen: 1, bevel: 0.9, glow: { radius: 14 } } } }) // glowing accents
84
+ setTheme({ components: { item: { elevation: "raised" } } }) // raised rows, deliberately
85
+ ```
86
+
87
+ ### Window finish
88
+
89
+ `finish` is the theme's window finish: a core window shader declaration (`program`, `params`, and the optional `textures`, `previous`, `vertexCount`) that `Window` declares on the window as it is, so one theme can carry a vignette, scanlines or a color grade. `Window` adds one uniform, `uScale`, the display scale, for effects counted in logical pixels. Strength and layer switches are the theme's own `params`; a theme without a `program` has no finish. An app with a slider updates a param by spreading the current ones, because `setTheme` merges one level deep and a partial replaces the whole `params` object it names: `setTheme({ finish: { params: { ...theme.finish.params, uStrength: v } } })`. The theme's module links the program itself, once, at init (the client brings the GPU up before the bundle runs); the package ships no finish, and the stock presets have none.
90
+
91
+ ```jsx
92
+ import { compileShader, linkProgram, destroyShader, glsl } from "@solidrt/core/gpu"
93
+
94
+ let fs = compileShader("fragment", glsl`...`, { header: true }) // reads uSource, uScale and its own params
95
+ let vs = compileShader("vertex", glsl`...`) // the covering triangle
96
+ let program = linkProgram(vs, fs, { label: "crt-finish" })
97
+ destroyShader(vs); destroyShader(fs)
98
+
99
+ export let crtTheme = defineTheme({ color: { /* ... */ }, finish: { program, params: { uStrength: 0.4, uScan: 1 } } })
100
+ ```
101
+
74
102
  ### Per-component overrides
75
103
 
76
- `theme.components` restyles a component everywhere without wrapping it: a `StyleProps` object per component name, merged between the component's themed defaults and each instance's `style` prop (instance style still wins).
104
+ `theme.components` restyles a component everywhere without wrapping it: a `StyleProps` object per component name, merged between the component's themed defaults and each instance's `style` prop (instance style still wins). Every themed component draws the `borderColor`/`borderWidth` it is given (Item rows, the SegmentedControl, Badge, the Slider and ProgressBar grooves included), so one theme boxes the whole library; `material`, `elevation` and `glow` restyle its faces (see Light, materials and elevation).
77
105
 
78
106
  ```jsx
79
107
  setTheme({ components: { button: { borderRadius: 999 } } }) // pill buttons app-wide
80
108
  ```
81
109
 
82
- Keys: `button`, `card`, `badge`, `switch`, `checkbox`, `radio`, `item`, `select`, `segmentedControl`, `textInput`, `richTextEditor`, `tooltip`, `divider`, `progressBar`, `spinner`.
110
+ Keys: `button`, `card`, `badge`, `switch`, `checkbox`, `radio`, `slider`, `item`, `select`, `segmentedControl`, `textInput`, `richTextEditor`, `tooltip`, `divider`, `progressBar`, `spinner`, `contextMenu`, `navShell`.
83
111
 
84
112
  ### Icon slots
85
113
 
@@ -91,7 +119,7 @@ import ChevronDown from "lucide-static/icons/chevron-down.svg"
91
119
  setTheme({ icons: { chevronDown: ChevronDown } })
92
120
  ```
93
121
 
94
- API: `theme`, `setTheme`, `defineTheme`, `darkTheme`, `lightTheme`, `Theme`, `ThemeDefinition`, `ThemeColor`, `ThemedComponent`, `TextStyle`, `TextVariant` - typed and commented in [src/theme.ts](./src/theme.ts).
122
+ API: `theme`, `setTheme`, `defineTheme`, `darkTheme`, `lightTheme`, `litDarkTheme`, `litLightTheme`, `pill`, `Theme`, `ThemeDefinition`, `ThemeColor`, `ThemeValue`, `Light`, `ShadowTone`, `ThemedComponent`, `TextStyle`, `TextVariant` - typed and commented in [src/theme.ts](./src/theme.ts).
95
123
 
96
124
  ## Policies
97
125
 
@@ -119,7 +147,7 @@ API: `policy`, `setPolicy`, `setPolicyResolver`, `defaultPolicyResolver`, `Polic
119
147
 
120
148
  ## Layout and style
121
149
 
122
- Most components group their props into two objects, split by one rule: `layout` properties feed the layout engine (flexbox/grid, sizing, padding, margin, position - the core `LayoutProps` set) and changing them triggers a relayout; `style` properties are paint-only and never affect layout: `color`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, `opacity`, and the transform (`x`, `y`, `scale`, `rotate`, `rotateX`/`rotateY` with `perspective`, `originX`/`originY`, `clipRadius`). Event handlers (`onPointerDown`, `onKeyDown`, ...) are top-level props, never inside `layout` or `style`.
150
+ Most components group their props into two objects, split by one rule: `layout` properties feed the layout engine (flexbox/grid, sizing, padding, margin, position - the core `LayoutProps` set) and changing them triggers a relayout; `style` properties are paint-only and never affect layout: `color`, `backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`, `opacity`, `glow` (a halo on the component's accent part; `null` clears the theme's), `material` (layers merged over the role's material for the component's faces) and `elevation` (the level its raised face stands at), see Theming, and the transform (`x`, `y`, `scale`, `rotate`, `rotateX`/`rotateY` with `perspective`, `originX`/`originY`, `clipRadius`). Event handlers (`onPointerDown`, `onKeyDown`, ...) are top-level props, never inside `layout` or `style`.
123
151
 
124
152
  `StyleProps` is that paint set. `TextLayoutProps` extends `LayoutProps` with the font fields (`fontFamily`, `fontSize`, `lineHeight`, `fontStyle`, `fontWeight`, `textAlign`, `maxLines`) because text shaping affects measurement; note `lineHeight` is a multiplier of `fontSize` (the theme uses 1.3-1.6), not a pixel value. `Option` (`{ value, label }`) is the shared shape of the single-choice controls (`Select`, `SegmentedControl`): shared shapes go through this module so components never import a sibling.
125
153
 
@@ -129,7 +157,7 @@ Controls with a moving part of their own name it as an extra entry: `Switch` `kn
129
157
 
130
158
  The components also ship built-in motion with no props at all: state and theme colors fade, a press shrinks the free-standing controls on a quick spring and fades the overlay tints, marks (checkmark, radio dot) pop in and out, moving parts travel on springs, and the overlays (`Modal`, `Tooltip`, the `Select`/`ContextMenu` popups) fade in and out. Timing comes from `theme.motion` (`fast`/`base`/`slow`), and `policy.motion` gates it: `reduced` keeps the fades but snaps everything that moves, `none` snaps it all. A caller's `transition` entry overrides the built-in for that property, and `transition={null}` suppresses a component's built-ins outright.
131
159
 
132
- API: `StyleProps`, `FontProps`, `TextLayoutProps`, `EditorLayoutProps`, `Option`, `TransitionProps`, `ComponentTransition`, `TransitionViewProp`, `TransitionStyleProp`, `TransitionScrollProp` - typed and commented in [src/types.ts](./src/types.ts).
160
+ API: `StyleProps`, `Glow`, `Glass`, `Material`, `MaterialRole`, `ElevationLevel`, `FontProps`, `TextLayoutProps`, `EditorLayoutProps`, `Option`, `TransitionProps`, `ComponentTransition`, `TransitionViewProp`, `TransitionStyleProp`, `TransitionScrollProp` - typed and commented in [src/types.ts](./src/types.ts).
133
161
 
134
162
  ## Typography helpers
135
163
 
@@ -167,6 +195,8 @@ function App() {
167
195
  }
168
196
  ```
169
197
 
198
+ The window takes the theme's finish (see Theming) whenever the theme has one; a theme without a finish declares no shader at all. `shader` is the window's own finish, a core window shader declaration forwarded as-is; set, it replaces the theme's, and `shader={null}` opts out of it. A window shader runs past the point snapshots read, so a finish is checked with `srt render`, not `get_snapshot`.
199
+
170
200
  API: `Window`, `WindowProps` - typed and commented in [src/window.tsx](./src/window.tsx).
171
201
 
172
202
  ### View
@@ -410,6 +440,28 @@ The mouse cursor is the pointer hand over the box (`cursor` overrides it). `disa
410
440
 
411
441
  API: `Pressable`, `PressableProps`, `PressState` - typed and commented in [src/pressable.tsx](./src/pressable.tsx).
412
442
 
443
+ ### createPress
444
+
445
+ The press recognizer the package's own controls are built on, for a custom control that wants its own root view instead of wrapping `Pressable`: `onPress` fires on a primary-button down followed by an up over the node, a drag out retracts the pressed state and a drag back in restores it, nested recognizers resolve innermost-first through core's arena, and the node's `ref` registers `onPress` as its focus-navigation `select` action so a remote or keyboard activates it while it holds focus. Spread `handlers` on the root view, attach `ref`, and read `pressed()`, `hovered()`, `focused()` and `pending()` (or `state()`, the live object the `Pressable` render prop receives) inside the props that style on them. Options are read at event time, so a component's reactive props object can be passed as is.
446
+
447
+ ```jsx
448
+ import { createPress, theme } from "@solidrt/components"
449
+
450
+ function Chip(props) {
451
+ let press = createPress(props)
452
+ return (
453
+ <view ref={press.ref} {...press.handlers} padding={8} focusable cursor="pointer">
454
+ <d-rect color={press.pressed() ? theme.color.overlayPressed : theme.color.surfaceAlt} radius={theme.radius.full} />
455
+ <text color={theme.color.text}>{props.label}</text>
456
+ </view>
457
+ )
458
+ }
459
+ ```
460
+
461
+ `registerNavAction` (from `createFocusNav`'s module) is the piece underneath: a control that activates without `createPress` registers its own `select` action with it.
462
+
463
+ API: `createPress`, `PressOptions` - typed and commented in [src/press.ts](./src/press.ts).
464
+
413
465
  ### Button
414
466
 
415
467
  A themed press target over `Pressable`: a padded, centered box with a label. A press shrinks it slightly on a quick spring and tints it with `overlayPressed`; hover tints with `overlayHover` (non-touch policies). `variant` picks the visual role - `primary` (accent fill, the default), `secondary`, `ghost` (no fill until hover), `danger` (destructive) - with fill, tints, and label color from the matching theme tokens; no variant draws a border. `size` (`sm`/`md`/`lg`) pins a minimum width so a row of buttons lines up (a longer label still expands past it); omitted, the button sizes to its content. A string or number child renders as the themed label; any other child renders as-is (an icon, a row, ...).
@@ -466,11 +518,11 @@ When the focused control disappears (an action replacing it, a screen change), f
466
518
 
467
519
  An open `Modal` traps navigation inside itself with no extra wiring (topmost wins when stacked); pass `scope: () => nodeOrNull` to trap into some other subtree instead. `move`/`tab`/`activate` are exposed for custom triggers.
468
520
 
469
- API: `createFocusNav`, `uiActions`, `uiBindings`, `FocusNavOptions`, `UiActions`, `UiDevices` - typed and commented in [src/focus-nav.ts](./src/focus-nav.ts).
521
+ API: `createFocusNav`, `registerNavAction`, `uiActions`, `uiBindings`, `FocusNavOptions`, `UiActions`, `UiDevices` - typed and commented in [src/focus-nav.ts](./src/focus-nav.ts).
470
522
 
471
523
  ### Switch
472
524
 
473
- An on/off toggle: the track fills with `primary` when on and `surfaceAlt` when off (a fade), and the thumb springs across - the `knob` transition entry retimes that travel. Controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Built on `Pressable`, so `disabled` takes no pointer events. `style` overrides the track colors and radius.
525
+ An on/off toggle: the track fills with `primary` when on and `surfaceAlt` when off (a fade), and the thumb springs across - the `knob` transition entry retimes that travel. Controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Built on `Pressable`, so `disabled` takes no pointer events. The thumb is `theme.color.thumb` (else `onPrimary`), track and thumb follow `radius.full` through `pill` (square in an all-square theme), and the on track glows with the theme's accent glow. `style` overrides the track colors and radius.
474
526
 
475
527
  ```jsx
476
528
  import { Switch } from "@solidrt/components"
@@ -486,7 +538,7 @@ API: `Switch`, `SwitchProps` - typed and commented in [src/switch.tsx](./src/swi
486
538
 
487
539
  ### Checkbox
488
540
 
489
- A checkbox: filled with `primary` and a drawn checkmark when checked, an empty bordered box otherwise - the fill fades and the mark pops in and out. Controlled via `checked`/`onChange`, or uncontrolled via `defaultChecked`. The mark is the `theme.icons.check` slot when a theme sets one. `style` overrides the box colors, border, and radius.
541
+ A checkbox: filled with `primary` and a drawn checkmark when checked, an empty bordered box otherwise - the fill fades and the mark pops in and out. Controlled via `checked`/`onChange`, or uncontrolled via `defaultChecked`. The mark is the `theme.icons.check` slot when a theme sets one. The checked fill glows with the theme's accent glow. `style` overrides the box colors, border, and radius.
490
542
 
491
543
  ```jsx
492
544
  import { Checkbox } from "@solidrt/components"
@@ -498,7 +550,7 @@ API: `Checkbox`, `CheckboxProps` - typed and commented in [src/checkbox.tsx](./s
498
550
 
499
551
  ### RadioGroup / Radio
500
552
 
501
- A single-selection pair: `RadioGroup` owns the selected value (controlled via `value`/`onChange`, or uncontrolled via `defaultValue`) and shares it with its `Radio` children; each `Radio` is a ring with an inner dot when selected - the ring color fades and the dot pops in and out. A string/number child of `Radio` renders as a themed label beside the ring; anything else as-is. `disabled` on the group disables every option, on a `Radio` just that one.
553
+ A single-selection pair: `RadioGroup` owns the selected value (controlled via `value`/`onChange`, or uncontrolled via `defaultValue`) and shares it with its `Radio` children; each `Radio` is a ring with an inner dot when selected - the ring color fades and the dot pops in and out. Ring and dot follow `radius.full` through `pill` (square in an all-square theme), and the dot glows with the theme's accent glow. A string/number child of `Radio` renders as a themed label beside the ring; anything else as-is. `disabled` on the group disables every option, on a `Radio` just that one.
502
554
 
503
555
  ```jsx
504
556
  import { RadioGroup, Radio } from "@solidrt/components"
@@ -514,7 +566,7 @@ API: `RadioGroup`, `Radio`, `RadioGroupProps`, `RadioProps` - typed and commente
514
566
 
515
567
  ### Slider
516
568
 
517
- A horizontal slider: the groove fills up to the thumb, and pressing or dragging the track sets the value from the pointer position. Controlled via `value`/`onChange` (fires while dragging), or uncontrolled via `defaultValue` (defaults to `min`). `min`/`max` default to 0/100; `step` snaps to an increment, omitted the value is continuous. The drag keeps tracking when the pointer drifts off the track, and an enclosing ScrollView never takes it over.
569
+ A horizontal slider: the groove fills up to the thumb, and pressing or dragging the track sets the value from the pointer position. Controlled via `value`/`onChange` (fires while dragging), or uncontrolled via `defaultValue` (defaults to `min`). `min`/`max` default to 0/100; `step` snaps to an increment, omitted the value is continuous. The drag keeps tracking when the pointer drifts off the track, and an enclosing ScrollView never takes it over. The groove is `surfaceAlt`, the fill `primary` (glowing with the theme's accent glow) and the thumb `theme.color.thumb` (else `primary`); `style` and `theme.components.slider` restyle them as ProgressBar's do: `backgroundColor` the groove, `color` the fill, `borderRadius` the groove's ends (round through `pill`, square under `radius.full: 0`), `borderColor`/`borderWidth` a box around the groove.
518
570
 
519
571
  ```jsx
520
572
  import { Slider } from "@solidrt/components"
@@ -562,7 +614,7 @@ import { For } from "@solidrt/core"
562
614
  </view>
563
615
  ```
564
616
 
565
- With `onPress` the row is interactive: hover/pressed overlay tints (no scale - rows sit flush in a list), focusable for spatial navigation, Enter/remote activation, and a focus ring under the `focusRing` policy. An async `onPress` (returning a promise) is not re-fired until it settles. Without `onPress` the row attaches no press recognizer, so controls inside it (a Switch in a settings row) and enclosing pressables receive pointer events untouched; interactivity is decided at mount. `selected` fills the row with `surfaceAlt`; `disabled` dims the row and takes no pointer events. Separate rows with `Divider` where needed.
617
+ With `onPress` the row is interactive: hover/pressed overlay tints (no scale - rows sit flush in a list), focusable for spatial navigation, Enter/remote activation, and a focus ring under the `focusRing` policy. An async `onPress` (returning a promise) is not re-fired until it settles. Without `onPress` the row attaches no press recognizer, so controls inside it (a Switch in a settings row) and enclosing pressables receive pointer events untouched; interactivity is decided at mount. `selected` fills the row with `surfaceAlt`; `disabled` dims the row and takes no pointer events. Separate rows with `Divider` where needed, or box them with `borderColor`/`borderWidth` (per instance, or every row through `theme.components.item`).
566
618
 
567
619
  API: `Item`, `ItemProps` - typed and commented in [src/item.tsx](./src/item.tsx).
568
620
 
@@ -595,7 +647,7 @@ API: `Divider`, `DividerProps` - typed and commented in [src/divider.tsx](./src/
595
647
 
596
648
  ### Badge
597
649
 
598
- A small rounded pill for counts, labels, and status. `variant` picks the role: `primary` (accent fill, the default), `neutral` (subtle surface), `danger`. A string/number child renders as the themed label, anything else as-is (an icon, a dot, ...). Override the fill via `style.backgroundColor` and the label color via `style.color`.
650
+ A small rounded pill for counts, labels, and status. `variant` picks the role: `primary` (accent fill, the default), `neutral` (subtle surface), `danger`. A string/number child renders as the themed label, anything else as-is (an icon, a dot, ...). Override the fill via `style.backgroundColor` and the label color via `style.color`; `borderColor`/`borderWidth` box it. The accent variants glow with the theme's accent glow.
599
651
 
600
652
  ```jsx
601
653
  import { Badge } from "@solidrt/components"
@@ -621,7 +673,7 @@ API: `Spinner`, `SpinnerProps` - typed and commented in [src/spinner.tsx](./src/
621
673
 
622
674
  ### ProgressBar
623
675
 
624
- A horizontal progress bar: determinate when given a `value` in `[0, 1]` (the fill grows from the left, gliding to each new value - the `fill` transition entry retimes it), indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track is `surfaceAlt`, fill is `primary`; override via `style.backgroundColor` (track) and `style.color` (fill).
676
+ A horizontal progress bar: determinate when given a `value` in `[0, 1]` (the fill grows from the left, gliding to each new value - the `fill` transition entry retimes it), indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track is `surfaceAlt`, fill is `primary`; override via `style.backgroundColor` (track) and `style.color` (fill), and box the track with `borderColor`/`borderWidth`. The ends follow `radius.full` through `pill`, and the fill glows with the theme's accent glow.
625
677
 
626
678
  ```jsx
627
679
  import { ProgressBar } from "@solidrt/components"
@@ -670,7 +722,7 @@ API: `Modal`, `ModalProps` - typed and commented in [src/modal.tsx](./src/modal.
670
722
 
671
723
  ### Tooltip
672
724
 
673
- A hover-only affordance: under the `desktop`/`hybrid` interaction policies, resting a mouse pointer on the wrapped content shows a bubble near it after `delay` (default 500ms). Under the `touch` policy it never shows, so tooltip content must stay non-essential. The bubble is portal-mounted at the window root, clamped to the window edges, takes no pointer events, fades in and out, and hides on leave and on press. A string/number `content` renders as themed body text; anything else as-is. `placement` picks the side (`"top"`, the default, or `"bottom"`).
725
+ A hover-only affordance: under the `desktop`/`hybrid` interaction policies, resting a mouse pointer on the wrapped content shows a bubble near it after `delay` (default 500ms). Under the `touch` policy it never shows, so tooltip content must stay non-essential. The bubble is portal-mounted at the window root, clamped to the window edges, takes no pointer events, fades in and out, hides on leave and on press, and glows with the theme's overlay glow. A string/number `content` renders as themed body text; anything else as-is. `placement` picks the side (`"top"`, the default, or `"bottom"`).
674
726
 
675
727
  ```jsx
676
728
  import { Tooltip, Button } from "@solidrt/components"
@@ -684,7 +736,7 @@ API: `Tooltip`, `TooltipProps` - typed and commented in [src/tooltip.tsx](./src/
684
736
 
685
737
  ### Select
686
738
 
687
- A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside closes without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
739
+ A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside closes without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, the dropdown glows with the theme's overlay glow, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
688
740
 
689
741
  ```jsx
690
742
  import { Select } from "@solidrt/components"
@@ -702,7 +754,7 @@ API: `Select`, `SelectProps` - typed and commented in [src/select.tsx](./src/sel
702
754
 
703
755
  ### SegmentedControl
704
756
 
705
- A single-choice row of equal-width segments joined flush: only the control's outermost corners are rounded, hairline dividers separate the segments, and the active segment is one `primary` indicator that springs between segments on a selection change - the `indicator` transition entry retimes it. Hovered segments tint with the theme `overlayHover` under non-touch interaction policies. `options` is an `Option[]`; controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Override the inactive fill via `style.backgroundColor` and the outer radius via `style.borderRadius`.
757
+ A single-choice row of equal-width segments joined flush: only the control's outermost corners are rounded, hairline dividers separate the segments, and the active segment is one `primary` indicator that springs between segments on a selection change - the `indicator` transition entry retimes it. Hovered segments tint with the theme `overlayHover` under non-touch interaction policies. `options` is an `Option[]`; controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Override the inactive fill via `style.backgroundColor` and the outer radius via `style.borderRadius`, and box the control with `borderColor`/`borderWidth`. The indicator glows with the theme's accent glow.
706
758
 
707
759
  ```jsx
708
760
  import { SegmentedControl } from "@solidrt/components"
@@ -718,7 +770,7 @@ API: `SegmentedControl`, `SegmentedControlProps` - typed and commented in [src/s
718
770
 
719
771
  ### ContextMenu
720
772
 
721
- Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside closes without selecting.
773
+ Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside closes without selecting.
722
774
 
723
775
  ```jsx
724
776
  import { ContextMenu } from "@solidrt/components"
@@ -803,6 +855,59 @@ import House from "lucide-static/icons/house.svg"
803
855
 
804
856
  API: `Icon`, `IconProps` - typed and commented in [src/icon.tsx](./src/icon.tsx).
805
857
 
858
+ ### Surface
859
+
860
+ The one way a component draws a face: a role's material (`theme.material`) under the theme light (`theme.light`) at an elevation (`theme.elevation`), stacked as layers that fill the view it is rendered in - a contact shadow, the glow, the fill (tinted by the light, with the sheen gradient along it, carrying the key shadow the elevation casts), the hover/pressed tint, the bevel and the outline. Every themed component draws its chrome through it, so a theme's light, materials and elevation restyle the whole library at once; under the stock presets (flat materials, zero elevation) a face is a plain fill, the tint and the outline, the look the package has always had. A custom control uses it the same way: render it as the first child of the laid-out view it dresses, pick a `role` (never a material), give it the base `fill`, the `radius`, the press state for the tint and the sink, and an `outline` for its border or focus ring.
861
+
862
+ ```jsx
863
+ import { Surface, createPress, theme } from "@solidrt/components"
864
+
865
+ function Tile(props) {
866
+ let press = createPress(props)
867
+ return (
868
+ <view ref={press.ref} {...press.handlers} padding={16} cursor="pointer" focusable>
869
+ <Surface role="surface" elevation="raised" radius={theme.radius.lg} pressed={press.pressed()} hovered={press.hovered()} />
870
+ <text color={theme.color.text}>{props.label}</text>
871
+ </view>
872
+ )
873
+ }
874
+ ```
875
+
876
+ A pressed face sinks (its key shadow fades toward the contact shadow, its lit edge moves to the far side) on the native shadow transition; a `sunken` face is a well or a groove (shaded like a pressed face, casting nothing); `lifted` with `liftTo` retargets the key shadow to a higher level (the Card hover lift); `chrome={false}` keeps only the tint (a ghost button at rest). `material` merges layers over the role's, `glow` overrides that layer alone. Shadows cast from opaque casters hidden under the fill, since a transparent caster's blurred shadow composites in a save layer of its own, which a tiled GPU pays for per shadow; only a glass material (a frosted backdrop, the overlay role's option) casts from a transparent one. `facePaint` gives the same derivation as plain paint values, for a part a control draws itself (a slider fill, a segmented indicator); `roleFill`, `roleMaterial` and `surfaceSinks` are the pieces it is built from.
877
+
878
+ API: `Surface`, `facePaint`, `roleFill`, `roleMaterial`, `surfaceSinks`, `SurfaceProps`, `FacePaint` - typed and commented in [src/surface.tsx](./src/surface.tsx).
879
+
880
+ ### lightFrom
881
+
882
+ The light helpers for an app that moves the theme light. `theme.light.direction` is the way the light travels in screen space (x right, y down, z into the screen); `lightFrom(px, py)` turns a point on the unit disk - where the light comes from, a light pad's coordinates - into that direction, and `lightSource(light)` is its inverse, so a pad shows the current light. `lightAxis(direction)` is the quantized, interned axis every face derives its paint from: equal steps return the same object, which is why a dragged light costs a face a re-derivation only when the axis steps.
883
+
884
+ ```jsx
885
+ import { lightFrom, setTheme, theme } from "@solidrt/components"
886
+
887
+ // A pad: the pointer's position over it, -1..1 per axis, becomes the light.
888
+ setTheme({ light: { ...theme.light, direction: lightFrom(px, py) } })
889
+ ```
890
+
891
+ The light is data for event-rate changes (a drag, a preset switch, a time of day), not a per-frame animation: every lit face re-derives its paint on each move.
892
+
893
+ API: `lightFrom`, `lightSource`, `lightAxis`, `LightAxis` - typed and commented in [src/light.ts](./src/light.ts).
894
+
895
+ ### PressFeedback
896
+
897
+ The hover/pressed tint every pressable face draws over its own fill: one always-mounted detached rect in `overlayHover` while hovered and `overlayPressed` while pressed, at alpha 0 otherwise, fading at the theme's feedback speed. Surface draws it as its tint layer; it is exported for a control that draws its own rows over a face it does not own (the option rows of a Select or ContextMenu).
898
+
899
+ ```jsx
900
+ import { PressFeedback, createPress } from "@solidrt/components"
901
+
902
+ let press = createPress(props)
903
+ <view ref={press.ref} {...press.handlers} padding={8}>
904
+ <PressFeedback pressed={press.pressed()} hovered={press.hovered()} radius={6} />
905
+ <text>{props.label}</text>
906
+ </view>
907
+ ```
908
+
909
+ API: `PressFeedback` - typed and commented in [src/motion.tsx](./src/motion.tsx).
910
+
806
911
  ### Density
807
912
 
808
913
  `<Density value="compact">` overrides the density policy for its subtree: every density-scaled metric below - `space()`, control sizes (Checkbox, Switch, Radio, Slider), `Item` and `Button` paddings - resolves this value instead of the global `policy.density`. Regions nest; the nearest wins. Use it to tighten a toolbar, a data table, or a sidebar without per-child props.
package/docs/badge.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Badge
2
2
 
3
- A small rounded pill for counts, labels, and status. `variant` picks the role: `primary` (accent fill, the default), `neutral` (subtle surface), `danger`. A string/number child renders as the themed label, anything else as-is (an icon, a dot, ...). Override the fill via `style.backgroundColor` and the label color via `style.color`.
3
+ A small rounded pill for counts, labels, and status. `variant` picks the role: `primary` (accent fill, the default), `neutral` (subtle surface), `danger`. A string/number child renders as the themed label, anything else as-is (an icon, a dot, ...). Override the fill via `style.backgroundColor` and the label color via `style.color`; `borderColor`/`borderWidth` box it. The accent variants glow with the theme's accent glow.
4
4
 
5
5
  ```jsx
6
6
  import { Badge } from "@solidrt/components"
package/docs/checkbox.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Checkbox
2
2
 
3
- A checkbox: filled with `primary` and a drawn checkmark when checked, an empty bordered box otherwise - the fill fades and the mark pops in and out. Controlled via `checked`/`onChange`, or uncontrolled via `defaultChecked`. The mark is the `theme.icons.check` slot when a theme sets one. `style` overrides the box colors, border, and radius.
3
+ A checkbox: filled with `primary` and a drawn checkmark when checked, an empty bordered box otherwise - the fill fades and the mark pops in and out. Controlled via `checked`/`onChange`, or uncontrolled via `defaultChecked`. The mark is the `theme.icons.check` slot when a theme sets one. The checked fill glows with the theme's accent glow. `style` overrides the box colors, border, and radius.
4
4
 
5
5
  ```jsx
6
6
  import { Checkbox } from "@solidrt/components"
@@ -1,6 +1,6 @@
1
1
  # ContextMenu
2
2
 
3
- Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside closes without selecting.
3
+ Secondary actions on the wrapped content. The opening gesture follows the physical pointer: right-click for a mouse, long-press (500 ms, cancelled by finger travel; core's long-press recognizer) for touch and pen. The long-press wins the finger at its timer: a pressable inside retracts and does not fire on the lift, and a scroll that started first keeps the finger. The presentation forks on the interaction policy: `touch` gets a bottom sheet over a scrim, `desktop`/`hybrid` an anchored menu at the pointer that flips up near the bottom edge. Both presentations fade in and out, and the anchored menu glows with the theme's overlay glow. `items` is a `ContextMenuItem[]` (`{ label, onSelect?, disabled? }`); pressing outside closes without selecting.
4
4
 
5
5
  ```jsx
6
6
  import { ContextMenu } from "@solidrt/components"
package/docs/item.md CHANGED
@@ -22,4 +22,4 @@ import { For } from "@solidrt/core"
22
22
  </view>
23
23
  ```
24
24
 
25
- With `onPress` the row is interactive: hover/pressed overlay tints (no scale - rows sit flush in a list), focusable for spatial navigation, Enter/remote activation, and a focus ring under the `focusRing` policy. An async `onPress` (returning a promise) is not re-fired until it settles. Without `onPress` the row attaches no press recognizer, so controls inside it (a Switch in a settings row) and enclosing pressables receive pointer events untouched; interactivity is decided at mount. `selected` fills the row with `surfaceAlt`; `disabled` dims the row and takes no pointer events. Separate rows with `Divider` where needed.
25
+ With `onPress` the row is interactive: hover/pressed overlay tints (no scale - rows sit flush in a list), focusable for spatial navigation, Enter/remote activation, and a focus ring under the `focusRing` policy. An async `onPress` (returning a promise) is not re-fired until it settles. Without `onPress` the row attaches no press recognizer, so controls inside it (a Switch in a settings row) and enclosing pressables receive pointer events untouched; interactivity is decided at mount. `selected` fills the row with `surfaceAlt`; `disabled` dims the row and takes no pointer events. Separate rows with `Divider` where needed, or box them with `borderColor`/`borderWidth` (per instance, or every row through `theme.components.item`).
package/docs/light.md ADDED
@@ -0,0 +1,12 @@
1
+ # lightFrom
2
+
3
+ The light helpers for an app that moves the theme light. `theme.light.direction` is the way the light travels in screen space (x right, y down, z into the screen); `lightFrom(px, py)` turns a point on the unit disk - where the light comes from, a light pad's coordinates - into that direction, and `lightSource(light)` is its inverse, so a pad shows the current light. `lightAxis(direction)` is the quantized, interned axis every face derives its paint from: equal steps return the same object, which is why a dragged light costs a face a re-derivation only when the axis steps.
4
+
5
+ ```jsx
6
+ import { lightFrom, setTheme, theme } from "@solidrt/components"
7
+
8
+ // A pad: the pointer's position over it, -1..1 per axis, becomes the light.
9
+ setTheme({ light: { ...theme.light, direction: lightFrom(px, py) } })
10
+ ```
11
+
12
+ The light is data for event-rate changes (a drag, a preset switch, a time of day), not a per-frame animation: every lit face re-derives its paint on each move.
package/docs/motion.md ADDED
@@ -0,0 +1,13 @@
1
+ # PressFeedback
2
+
3
+ The hover/pressed tint every pressable face draws over its own fill: one always-mounted detached rect in `overlayHover` while hovered and `overlayPressed` while pressed, at alpha 0 otherwise, fading at the theme's feedback speed. Surface draws it as its tint layer; it is exported for a control that draws its own rows over a face it does not own (the option rows of a Select or ContextMenu).
4
+
5
+ ```jsx
6
+ import { PressFeedback, createPress } from "@solidrt/components"
7
+
8
+ let press = createPress(props)
9
+ <view ref={press.ref} {...press.handlers} padding={8}>
10
+ <PressFeedback pressed={press.pressed()} hovered={press.hovered()} radius={6} />
11
+ <text>{props.label}</text>
12
+ </view>
13
+ ```
package/docs/press.md ADDED
@@ -0,0 +1,19 @@
1
+ # createPress
2
+
3
+ The press recognizer the package's own controls are built on, for a custom control that wants its own root view instead of wrapping `Pressable`: `onPress` fires on a primary-button down followed by an up over the node, a drag out retracts the pressed state and a drag back in restores it, nested recognizers resolve innermost-first through core's arena, and the node's `ref` registers `onPress` as its focus-navigation `select` action so a remote or keyboard activates it while it holds focus. Spread `handlers` on the root view, attach `ref`, and read `pressed()`, `hovered()`, `focused()` and `pending()` (or `state()`, the live object the `Pressable` render prop receives) inside the props that style on them. Options are read at event time, so a component's reactive props object can be passed as is.
4
+
5
+ ```jsx
6
+ import { createPress, theme } from "@solidrt/components"
7
+
8
+ function Chip(props) {
9
+ let press = createPress(props)
10
+ return (
11
+ <view ref={press.ref} {...press.handlers} padding={8} focusable cursor="pointer">
12
+ <d-rect color={press.pressed() ? theme.color.overlayPressed : theme.color.surfaceAlt} radius={theme.radius.full} />
13
+ <text color={theme.color.text}>{props.label}</text>
14
+ </view>
15
+ )
16
+ }
17
+ ```
18
+
19
+ `registerNavAction` (from `createFocusNav`'s module) is the piece underneath: a control that activates without `createPress` registers its own `select` action with it.
@@ -1,6 +1,6 @@
1
1
  # ProgressBar
2
2
 
3
- A horizontal progress bar: determinate when given a `value` in `[0, 1]` (the fill grows from the left, gliding to each new value - the `fill` transition entry retimes it), indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track is `surfaceAlt`, fill is `primary`; override via `style.backgroundColor` (track) and `style.color` (fill).
3
+ A horizontal progress bar: determinate when given a `value` in `[0, 1]` (the fill grows from the left, gliding to each new value - the `fill` transition entry retimes it), indeterminate when `value` is undefined (a short segment slides back and forth, driven by core `onFrame`). Track is `surfaceAlt`, fill is `primary`; override via `style.backgroundColor` (track) and `style.color` (fill), and box the track with `borderColor`/`borderWidth`. The ends follow `radius.full` through `pill`, and the fill glows with the theme's accent glow.
4
4
 
5
5
  ```jsx
6
6
  import { ProgressBar } from "@solidrt/components"
package/docs/radio.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RadioGroup / Radio
2
2
 
3
- A single-selection pair: `RadioGroup` owns the selected value (controlled via `value`/`onChange`, or uncontrolled via `defaultValue`) and shares it with its `Radio` children; each `Radio` is a ring with an inner dot when selected - the ring color fades and the dot pops in and out. A string/number child of `Radio` renders as a themed label beside the ring; anything else as-is. `disabled` on the group disables every option, on a `Radio` just that one.
3
+ A single-selection pair: `RadioGroup` owns the selected value (controlled via `value`/`onChange`, or uncontrolled via `defaultValue`) and shares it with its `Radio` children; each `Radio` is a ring with an inner dot when selected - the ring color fades and the dot pops in and out. Ring and dot follow `radius.full` through `pill` (square in an all-square theme), and the dot glows with the theme's accent glow. A string/number child of `Radio` renders as a themed label beside the ring; anything else as-is. `disabled` on the group disables every option, on a `Radio` just that one.
4
4
 
5
5
  ```jsx
6
6
  import { RadioGroup, Radio } from "@solidrt/components"
@@ -1,6 +1,6 @@
1
1
  # SegmentedControl
2
2
 
3
- A single-choice row of equal-width segments joined flush: only the control's outermost corners are rounded, hairline dividers separate the segments, and the active segment is one `primary` indicator that springs between segments on a selection change - the `indicator` transition entry retimes it. Hovered segments tint with the theme `overlayHover` under non-touch interaction policies. `options` is an `Option[]`; controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Override the inactive fill via `style.backgroundColor` and the outer radius via `style.borderRadius`.
3
+ A single-choice row of equal-width segments joined flush: only the control's outermost corners are rounded, hairline dividers separate the segments, and the active segment is one `primary` indicator that springs between segments on a selection change - the `indicator` transition entry retimes it. Hovered segments tint with the theme `overlayHover` under non-touch interaction policies. `options` is an `Option[]`; controlled via `value`/`onChange`, or uncontrolled via `defaultValue`. Override the inactive fill via `style.backgroundColor` and the outer radius via `style.borderRadius`, and box the control with `borderColor`/`borderWidth`. The indicator glows with the theme's accent glow.
4
4
 
5
5
  ```jsx
6
6
  import { SegmentedControl } from "@solidrt/components"
package/docs/select.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Select
2
2
 
3
- A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside closes without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
3
+ A single-choice picker whose presentation forks on the interaction policy: `desktop`/`hybrid` opens an anchored dropdown under the trigger (flipping above when there is no room), `touch` opens a bottom sheet over a scrim. Same contract either way: `options` is an `Option[]` (`{ value, label }`), controlled via `value`/`onChange` or uncontrolled via `defaultValue`; pressing outside closes without a change. `placeholder` shows in the trigger while nothing is selected. Both presentations fade in and out, the dropdown glows with the theme's overlay glow, and the trigger's chevron flips while open. The option list is not scrollable yet, so keep it short. The chevron is the `theme.icons.chevronDown` slot when a theme sets one.
4
4
 
5
5
  ```jsx
6
6
  import { Select } from "@solidrt/components"
package/docs/slider.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Slider
2
2
 
3
- A horizontal slider: the groove fills up to the thumb, and pressing or dragging the track sets the value from the pointer position. Controlled via `value`/`onChange` (fires while dragging), or uncontrolled via `defaultValue` (defaults to `min`). `min`/`max` default to 0/100; `step` snaps to an increment, omitted the value is continuous. The drag keeps tracking when the pointer drifts off the track, and an enclosing ScrollView never takes it over.
3
+ A horizontal slider: the groove fills up to the thumb, and pressing or dragging the track sets the value from the pointer position. Controlled via `value`/`onChange` (fires while dragging), or uncontrolled via `defaultValue` (defaults to `min`). `min`/`max` default to 0/100; `step` snaps to an increment, omitted the value is continuous. The drag keeps tracking when the pointer drifts off the track, and an enclosing ScrollView never takes it over. The groove is `surfaceAlt`, the fill `primary` (glowing with the theme's accent glow) and the thumb `theme.color.thumb` (else `primary`); `style` and `theme.components.slider` restyle them as ProgressBar's do: `backgroundColor` the groove, `color` the fill, `borderRadius` the groove's ends (round through `pill`, square under `radius.full: 0`), `borderColor`/`borderWidth` a box around the groove.
4
4
 
5
5
  ```jsx
6
6
  import { Slider } from "@solidrt/components"