Environment-neutral React components and the visual language of PhreshOS.
Appearance · React SDK · Source
React UI owns reusable visual contracts that accept the corresponding PhreshOS System values directly. The Desktop and Programs compose those primitives instead of reimplementing material, spacing, color, radius, or interaction behavior.
The package does not depend on a Client or Server runtime and does not own authoritative application state. React owns runtime-neutral state adaptation; applications own composition.
| Package manager | Command |
|---|---|
| npm | npm install @phreshos/react-ui |
| pnpm | pnpm add @phreshos/react-ui |
| Bun | bun add @phreshos/react-ui |
| Yarn | yarn add @phreshos/react-ui |
React and React DOM are peer dependencies because the application and React UI must share one React runtime.
import { Button, Surface } from "@phreshos/react-ui"
<Surface>
<Button>Continue</Button>
</Surface>Without a provider, components use React UI's defaultAppearance and reactively
follow the browser's visual preferences. UIProvider supplies the Appearance,
Preferences, and direction explicitly established for a subtree. Each omitted
property inherits independently from the nearest provider. A provider with no
properties introduces no boundary or behavior. A complete System Appearance can
be passed directly; React UI uses its visual fields and ignores the rest.
React UI is neutral between left-to-right and right-to-left interfaces. An
explicit UIProvider direction creates a boundary that participates in native
DOM inheritance without creating a layout box. When direction is omitted, React
UI follows <html dir> for JavaScript behavior and portals.
<UIProvider direction="rtl" preferences={{ theme: "dark" }}>
<Application />
</UIProvider>Every component is built from one primitive, Surface, and one Material. A
Surface is where the Material sits relative to its surroundings:
| Depth | Meaning | Treatment |
|---|---|---|
raised (default) |
Something you act on | A lit rim with a slight inward spill, and the outer Appearance shadow |
flat |
A region level with its surroundings | A softer rim |
recessed |
Something that holds a value you enter | A slightly deeper paint and a dim rim |
Every Surface shares one Material edge: its rim catches the same light along its top and bottom, so neither edge reads as a bevel, and only faintly along its sides, and a very thin dark hairline separates it from whatever it sits on. Buttons, selected tabs, thumbs, windows, and overlays rise. Text fields, tracks, rails, and wells recess. How deep a recess sinks and how much light the rim receives follow the lightness of the canvas continuously, so every canvas receives a matching treatment.
<Surface>Raised</Surface>
<Surface depth="flat">Level</Surface>
<Surface depth="recessed">Holds a value</Surface>Surface renders a div by default; as selects another element or
component. A host applies the className, style, and children it receives
on one element and forwards its ref to it. Consumer classes and styles always
take precedence over Surface's own paint.
color accepts an Appearance color (primary), an Appearance color at one
level (primary:soft), or a direct CSS color. Levels move a color toward the
canvas (subtle, soft) or toward the content color (strong, intense), so
each level keeps its meaning in both Themes.
default is a neutral paint close to the canvas; primary is the identity
color. A component's omitted color follows its meaning: neutral controls use
default, fields recess the background, and selection, progress, and on-states
use primary. Every owned paint remains customizable through color.
Content on a paint uses whichever Appearance content color reads more clearly on its resting paint, by perceived lightness contrast. Hover and press move the same base toward the content color; a pressed raised Surface settles onto its surroundings. Focus rings in the owner's color, or the identity color when the owner is neutral. A focused or invalid value holder also claims its hairline. There are no separate state colors.
MaterialOptions defines opacity, backdrop, grain, grainAmount,
distortion, and saturation. Every Surface-based component accepts them
through material. Rendering is progressive: none paints the resolved color
only; basic (default) adds grain and the edge while remaining solid;
extended applies Material opacity; full adds backdrop, saturation, and
distortion. A MaterialOptions object selects full rendering with those
overrides. Backdrop effects are omitted whenever the final paint is opaque.
shadow groups x, y, blur, spread, and opacity for raised Surfaces;
false removes it.
radius accepts a size level, full, pixels, or CSS. Two rules derive every
default from the Appearance radius:
- Every Surface carries the Appearance radius. Shells and containers such as Window, Panel and its content, Dialog, and Popover carry it as it is. A control scales it with its height, so the medium control height carries it exactly and every size keeps the same shape, wherever it sits: list Items and the selected Tab are controls.
- Shapes whose form is their meaning, such as Radio, Switch, Slider, progress rails, and scrollbar thumbs, stay fully rounded.
Three rules hold across the library, so learning a few components teaches the rest.
- Everything that owns a paint accepts
color,size,radius,material,disabled,className, andstyle. - Everything that holds a value is a field:
label,description,errorMessage,invalid,required,readOnly, andvalue,defaultValue,onChange(value). - Every collection is composed of Items identified by a string
id: Select, ComboBox, ListBox, Menu, Tabs, Table, Tree, and Accordion. Actions report that identity throughonAction(id), and an Item is unavailable through its owndisabled.
<Button color="primary" onPress={install}>Install</Button>
<Input label="Name" value={name} onChange={setName} />
<Select label="Program" value={program} onChange={setProgram}>
<Select.Item id="tilo">Tilo</Select.Item>
<Select.Item id="lemo">Lemo</Select.Item>
</Select>size accepts xsmall, small, medium (default), large, or xlarge.
Spacing and radius follow Appearance; typography is inherited and applied once
at each component's root. disabled prevents activation and focus; pending
prevents activation while retaining focus.
Component families use one public path: the family name is the root and parts
that depend on it are its properties, such as Panel.Header and
Dialog.Title.
Panel is a frosted shell with an optional header and one content region. The
content takes the shell's color, raised with the extended material and the
same Appearance radius, sits half the spacing inside the shell, and pads its
content by the spacing. The header is the same row as a Window header.
<Panel>
<Panel.Header>Connection</Panel.Header>
<Panel.Content style={{ padding: 12 }}>Connected to the local System.</Panel.Content>
</Panel>Window provides one Surface with a ready header-and-content layout.
Window.Header.Identity presents the icon and truncating title,
Window.Header.Center is optional flexible space, and Window.Header.Actions
aligns compact controls at the end. Window.Content fills the remaining area
without deciding its overflow. Its controls are Buttons; callers connect them to
their own window operations, and beginMoveGesture hands an intentional header
drag to a window host.
<Window>
<Window.Header active={active}>
<Window.Header.Identity icon={icon} title={title} />
<Window.Header.Actions>
<Window.Header.Minimize onPress={minimize} />
<Window.Header.Maximize maximized={maximized} onPress={toggleMaximize} />
<Window.Header.Close onPress={close} />
</Window.Header.Actions>
</Window.Header>
<Window.Content>{children}</Window.Content>
</Window>Overlay components expose each behavioral role as a named part. Roots own open state, triggers own activation, positioned content owns its Surface, and the semantic content remains explicit.
<DropdownMenu>
<DropdownMenu.Trigger>Actions</DropdownMenu.Trigger>
<DropdownMenu.Content>
<Menu aria-label="Document actions">
<Menu.Item id="open" onAction={open}>Open</Menu.Item>
<Menu.Item id="delete" onAction={remove}>Delete</Menu.Item>
</Menu>
</DropdownMenu.Content>
</DropdownMenu>Popover presents anchored non-modal content. DropdownMenu and
ContextMenu open the same Menu contract through different interactions.
Menu is selection-free by default. Selectable menus use the same string
selection contract as the other collections, and root-level command dispatch
uses onAction(id).
Dialog blocks interaction behind it and may be dismissable, while
AlertDialog requires an explicit decision by default. Tooltip supplies a
short description on focus or hover. Portal cleanup, focus restoration,
keyboard behavior, nested ownership, and entrance and exit transactions remain
component-owned.
Text fields, Select, ComboBox, and the date fields recess the background by
default. Checkbox, Switch, and RadioGroup indicators are recessed beds that rise
in their color when selected. Slider and ProgressBar fill a recessed rail with
their color. Motion is derived from what moves, how far, and appearance.tempo (timing, useTiming); Preferences with
animations disabled make every change immediate without changing Appearance.
| Component | Value contract | Purpose |
|---|---|---|
Input |
value, defaultValue, onChange(string) |
Single-line text; supports text, email, password, search, URL, and telephone types |
Textarea |
value, defaultValue, onChange(string) |
Multiline text; four rows by default, vertically resizable |
DateField |
CalendarDate | null through value, defaultValue, and onChange |
A locale-aware date whose year, month, and day are edited as independent segments |
TimeField |
Time | null through value, defaultValue, and onChange |
A locale-aware clock time whose visible units are edited as independent segments |
Calendar |
CalendarDate | null selection |
A keyboard-navigable month view for choosing one date |
RangeCalendar |
DateRange | null selection |
A month view for choosing an inclusive start and end date |
DatePicker |
The same CalendarDate | null contract as DateField |
Segmented date entry with a calendar selection popover |
DateRangePicker |
The same DateRange | null contract as RangeCalendar |
Two segmented date fields with a shared range calendar popover |
Checkbox |
checked, defaultChecked, onChange(boolean) |
Independent selection; indeterminate represents a mixed state |
Switch |
checked, defaultChecked, onChange(boolean) |
An on/off setting |
RadioGroup / RadioGroup.Item |
Group value, defaultValue, onChange(string); Item value |
One exclusive choice; vertical by default, optionally horizontal |
Select |
value: string | null, defaultValue, onChange(string | null) |
One choice from its Select.Items |
ComboBox |
Selected value: string | null and query inputValue: string |
One searchable choice from its ComboBox.Items |
ListBox |
Single string or multiple string-array value, defaultValue, and onChange |
A visible selectable collection with composable items and sections |
Tree |
Optional selection plus controlled or uncontrolled string-array expansion | Nested hierarchical navigation with arbitrary branch depth |
Slider |
value, defaultValue, onChange(number) |
One numeric value; minValue, maxValue, step, and onChangeEnd; horizontal by default |
NumberField |
value: number | null, defaultValue, onChange(number | null) |
A number typed or stepped within minValue and maxValue, formatted by formatOptions |
SegmentedControl / SegmentedControl.Item |
value, defaultValue, onChange(string); Item id |
One of a few visible options, on the Tabs track |
ColorField, ColorSlider, ColorArea, ColorSwatchPicker |
Hex value: string, defaultValue, onChange(string) |
A color typed, moved along one or two channels, or chosen from swatches; inside a ColorPicker they edit its color |
DropZone / FileTrigger |
onDrop(File[]) / onSelect(File[]) |
Files dragged onto an area, or chosen from the system file chooser |
Use label for visible labels, or aria-label / aria-labelledby for an
accessible name without visible text. description provides associated help.
disabled prevents interaction and removes the control from keyboard focus.
name participates in native form submission. Controlled values remain owned
by the caller; omit them and use defaults for internal state and form reset.
Text fields, ComboBox, Checkbox, Switch, and RadioGroup also support readOnly: the
value cannot change, but the control remains focusable. These fields and
Select support required, invalid, errorMessage, and React Aria's native
or ARIA validation behavior. A RadioGroup Item's selection and validation belong to its
group. Slider represents a bounded number rather than a required text or
choice field, and has no read-only or validation-error mode. Select has no
read-only mode; disable it when selection must be unavailable.
Input, Textarea, DateField, TimeField, DatePicker, DateRangePicker,
Calendar, RangeCalendar, Select, and ComboBox accept radius. Other inputs
retain their intrinsic indicator shapes. className and style address the field's root;
default visual properties remain component-owned. Input and Textarea refs
target their native text controls; other refs target the root div. Checkbox,
Switch, and RadioGroup.Item additionally accept inputRef for their native input.
import { Input, Textarea, DateField, TimeField, Calendar, RangeCalendar, DatePicker, DateRangePicker, Checkbox, RadioGroup, Switch, Select, ComboBox, ListBox, Tree, Slider } from "@phreshos/react-ui"
import { parseDate, parseTime } from "@internationalized/date"
<Input label="Name" name="name" required />
<Textarea label="Description" name="description" />
<DateField label="Due date" name="dueDate" defaultValue={parseDate("2026-09-21")} />
<TimeField label="Start time" name="startTime" defaultValue={parseTime("09:30")} />
<DatePicker label="Appointment" name="appointment" defaultValue={parseDate("2026-09-21")} />
<Calendar aria-label="Release date" defaultValue={parseDate("2026-09-21")} />
<RangeCalendar aria-label="Trip dates" defaultValue={{ start: parseDate("2026-09-21"), end: parseDate("2026-09-24") }} />
<DateRangePicker label="Trip dates" defaultValue={{ start: parseDate("2026-09-21"), end: parseDate("2026-09-24") }} />
<Checkbox label="Remember this choice" name="remember" />
<Switch label="Notifications" name="notifications" defaultChecked />
<RadioGroup label="Layout" name="layout" defaultValue="grid">
<RadioGroup.Item label="Grid" value="grid" />
<RadioGroup.Item label="List" value="list" />
</RadioGroup>
<Select label="Sort" name="sort">
<Select.Item id="name">Name</Select.Item>
<Select.Item id="date">Date</Select.Item>
</Select>
<ComboBox label="Region" name="region">
<ComboBox.Item id="eu">Europe</ComboBox.Item>
<ComboBox.Item id="us">United States</ComboBox.Item>
</ComboBox>
<ListBox aria-label="Programs" defaultValue="editor">
<ListBox.Section id="productivity">
<ListBox.Header>Productivity</ListBox.Header>
<ListBox.Item id="editor">Editor</ListBox.Item>
<ListBox.Item id="terminal">Terminal</ListBox.Item>
</ListBox.Section>
</ListBox>
<Tree aria-label="Project files" defaultExpanded={["source"]}>
<Tree.Item id="source" textValue="Source">
<Tree.Content>Source</Tree.Content>
<Tree.Item id="main" textValue="main.ts">
<Tree.Content>main.ts</Tree.Content>
</Tree.Item>
</Tree.Item>
</Tree>
<Slider label="Volume" name="volume" defaultValue={50} minValue={0} maxValue={100} step={1} />ComboBox filters option labels as the user types. Selection uses value and
onChange; its query is independently controllable through inputValue and
onInputChange. Use defaultValue and defaultInputValue for uncontrolled
state.
ListBox uses single selection by default and accepts selectionMode="multiple"
for a string array or "all". Its Items require stable string identities.
Sections group options under Headers; they do not introduce expandable hierarchy.
ListBox owns collection layout, selection, focus, keyboard behavior, and item
presentation. An enclosing Surface or ScrollArea remains an explicit consumer
composition.
Tree represents hierarchy rather than grouping. Branches may contain other
branches to any depth. Expansion uses expanded, defaultExpanded, and
onExpandedChange, all expressed as string identities. Selection is disabled
by default and uses the same explicit single- or multiple-selection contract as
Table when enabled. Tree.Content supplies each row's visible content and its
accessible disclosure control; Tree.Collection recursively renders
data-backed child collections.
Each control delegates focus, keyboard, form, and selection behavior to its accessible behavioral primitive rather than recreating those systems.
Tabs organizes related peer views under one selected string identity. Its
list is a recessed track divided into equal columns, and the selected Tab rises
out of it, moving continuously between selections.
<Tabs defaultValue="overview">
<Tabs.List aria-label="Project views">
<Tabs.Tab id="overview">Overview</Tabs.Tab>
<Tabs.Tab id="activity">Activity</Tabs.Tab>
</Tabs.List>
<Tabs.Panel id="overview">Project overview</Tabs.Panel>
<Tabs.Panel id="activity">Recent activity</Tabs.Panel>
</Tabs>Each Tabs.Tab identity must match one Tabs.Panel. Use Tabs.Panels when
the panels come from a dynamic collection; static panels may remain direct
children of Tabs.
Table presents structured rows and columns with accessible navigation. Row
selection uses the same string-value contract as ListBox, while sorting reports
the requested column and direction without taking ownership of the data order.
<Table
aria-label="Processes"
selectionMode="multiple"
value={selectedProcesses}
onChange={setSelectedProcesses}
sort={sort}
onSortChange={setSort}
>
<Table.Header>
<Table.Column id="name" rowHeader sortable>Name</Table.Column>
<Table.Column id="state" sortable>State</Table.Column>
</Table.Header>
<Table.Body>
<Table.Row id="editor">
<Table.Cell>Editor</Table.Cell>
<Table.Cell>Running</Table.Cell>
</Table.Row>
</Table.Body>
</Table>Selection is disabled by default. Set selectionMode to "single" or
"multiple" to enable it. Sortable columns emit { column, direction }
through onSortChange; reorder the rows in application state and pass the
descriptor back through sort. Table.Body supports static rows, dynamic
items, and renderEmptyState.
Table owns tabular behavior and row presentation without adding a material container or scrolling boundary. Compose Surface and ScrollArea around it when the surrounding interface needs either one.
ProgressBar communicates the progress of an operation without becoming an
input. Determinate progress exposes its current value and percentage;
indeterminate progress omits the current value and presents ongoing activity.
<ProgressBar label="Uploading" value={64} />
<ProgressBar aria-label="Connecting" indeterminate />The default range is 0 through 100. formatOptions controls the generated
value text, while valueLabel supplies an explicit visible and accessible
value. It fills a recessed rail with primary unless color says otherwise.
bun install --frozen-lockfile
bun run verifyverify checks the contracts, tests the components, builds the package, and
validates its published shape.
check performs static checks, build creates distributable output, and test
runs Vitest assertions from tests/. Run build before testing built artifacts.
verify runs check, build, and test in order. Operational tooling belongs
in scripts/; tests and their fixtures belong in tests/. Verification uses
the committed dependency graph without local package substitutions.
@phreshos/coreowns the System contracts.@phreshos/reactowns runtime-neutral React state adaptation.- PhreshOS System composes the visual language into the Desktop.
- Settings presents owner-facing Appearance controls.
See CONTRIBUTING.md for the repository workflow and SECURITY.md for private vulnerability reporting.
Licensed under the MIT License. Copyright © 2026 Zohayr SLILEH.