Skip to content

Form primitives

Native <input type="range"> and <input type="checkbox"> work but look like the browser put them there, not like the OS did. The library ships themed wrappers (<Slider> and <Toggle>) that match the rest of the desktop. They’re the primitives the built-in Settings panel uses, exported so consumers can use them in their own app windows.

Open Settings to see Slider and Toggle in their native habitat Open in new tab ↗
import { Slider, Toggle } from "@react-ui-os/desktop";
<Slider
value={radius}
min={0}
max={24}
step={2}
onChange={setRadius}
label="Corner radius"
unit="px"
/>
PropTypeDefaultDescription
valuenumbernoneCurrent value.
minnumbernoneLower bound.
maxnumbernoneUpper bound.
stepnumber1Tick size.
onChange(next: number) => voidnoneFired on every change. Receives the new numeric value.
labelstringnoneRendered above the track. Pair with aria-label if absent.
unitstringnoneAppended to the right-side readout (px, ms, %).
hideValuebooleanfalseHide the numeric readout.
accentstringthemeTrack-fill color override.
disabledbooleanfalseGreys out + blocks interaction.
ariaLabelstringnoneScreen-reader label when no visible label.

Built on a real native <input type="range"> so arrow keys, Page Up/Down, Home/End, and screen-reader value announcements all work out of the box. The styling is layered over the input via per-browser pseudo-selectors so the muscle memory + accessibility don’t go anywhere.

<Toggle
checked={parallax}
onChange={setParallax}
label="Wallpaper parallax"
description="Cursor-driven shift on the wallpaper layer"
/>
PropTypeDefaultDescription
checkedbooleannoneCurrent state.
onChange(next: boolean) => voidnoneFired on click.
labelstringnoneRendered to the left of the switch.
descriptionstringnoneHelper line under the label.
accentstringtheme”On” color override.
disabledbooleanfalseGreys out + blocks interaction.
ariaLabelstringnoneScreen-reader label when no visible label.

The visible button carries role="switch" + aria-checked so screen readers announce the state correctly. Animation comes from the same easing token the rest of the library uses.

The built-in Settings panel renders one Slider per range field and one Toggle per toggle field in the active theme’s customizable schema. Swapping a hand-written native input for these is a one-line change in your own surfaces:

// Before
<input type="range" value={v} onChange={(e) => setV(+e.target.value)} />
// After
<Slider value={v} min={0} max={100} onChange={setV} unit="%" />
  • Slider exposes aria-valuetext with the formatted value (including unit). Screen readers read “30 px” rather than “30”.
  • Toggle is a <button role="switch">: VoiceOver, NVDA, and JAWS all announce it as an actual switch.
  • Both honor disabled and visually communicate the state.
  • Focus rings on the Slider thumb use the theme accent for visibility.