Components#
Every component imports from onejs-ui and is styled by the active theme. Controlled inputs follow a value + onChange(value) pattern, where onChange receives a plain value (a boolean, number, or string), not an event.
import { Button, Card, Checkbox, Slider /* … */ } from "onejs-ui"Layout#
Card#
A surface container. variant="raised" lifts it above the page with surfaceRaised; variant="surface" (default) sits flush.
<Card variant="raised" style={{ width: 360 }}>
{/* content */}
</Card>Stack, HStack, VStack#
Flex containers with a gap. UI Toolkit has no gap, so spacing is inserted as small spacer elements between children, which works for any child but adds one element per gap. HStack/VStack are Stack with direction preset to row/column.
<VStack gap={16}>
<HStack gap={8}>
<Button>Save</Button>
<Button intent="ghost">Cancel</Button>
</HStack>
</VStack>| Prop | Type | Default |
|---|---|---|
direction | "row" \| "column" | "column" |
gap | number (px) | 0 |
align | "start" \| "center" \| "end" \| "stretch" | none |
justify | "start" \| "center" \| "end" \| "between" \| "around" | none |
wrap | boolean | false |
Divider#
A 1px separator line in the border color.
<Divider />
<Divider orientation="vertical" />Spacer#
Empty space inside a flex container. With size it's fixed; without, it flexes to fill.
<HStack>
<Text>Left</Text>
<Spacer />
<Text>Right</Text>
</HStack>Typography#
Text#
Body text with a tone and size scale.
<Text>Default body text</Text>
<Text tone="muted" size="sm">Secondary detail</Text>tone:"default" | "muted" | "subtle"size:"sm" | "md" | "lg" | "xl"
Heading#
Section headings at three levels.
<Heading level={1}>Settings</Heading>
<Heading level={3}>Audio</Heading>level:1 | 2 | 3
Form controls#
Button#
intent sets the visual style; size sets the size step. Content is passed as children.
<Button onClick={save}>Primary</Button>
<Button intent="secondary" onClick={cancel}>Secondary</Button>
<Button intent="ghost" size="sm">Ghost</Button>
<Button intent="danger" onClick={remove}>Delete</Button>intent:"primary" | "secondary" | "ghost" | "danger"(default"primary")size:"sm" | "md" | "lg"(default"md")onClick:(e) => void
Checkbox#
A labeled checkbox on the native UI Toolkit toggle.
const [agree, setAgree] = useState(false)
<Checkbox label="I agree" value={agree} onChange={setAgree} />label:stringvalue:boolean·onChange:(checked: boolean) => void
Switch#
A toggle switch; the label renders to the left.
const [on, setOn] = useState(true)
<Switch label="Notifications" value={on} onChange={setOn} />value:boolean·onChange:(checked: boolean) => void
Input#
A text field on the native UI Toolkit TextField, a proper focus target with full keyboard/IME behavior.
const [name, setName] = useState("")
<Input value={name} onChange={setName} />value:string·onChange:(value: string) => void- Other
TextFieldprops (e.g. placeholder) pass through.
Slider#
A slider on the native UI Toolkit Slider, with pointer drag and keyboard steps.
const [vol, setVol] = useState(60)
<Slider value={vol} min={0} max={100} onChange={setVol} />value:number·onChange:(value: number) => voidmin/max: range bounds (lowValue/highValuealso work).
RadioGroup#
A single-choice group. It takes the same options as Select, and value is the selected option's value string.
const [plan, setPlan] = useState("pro")
<RadioGroup
value={plan}
options={[
{ label: "Free", value: "free" },
{ label: "Pro", value: "pro" },
{ label: "Team", value: "team" },
]}
onChange={setPlan}
/>options:{ label: string; value: string; disabled?: boolean }[]value:string·onChange:(value: string) => void- Omit
valueto let the group own its selection, starting fromdefaultValue. - The older
choices={["Free", "Pro"]}with a numeric index still works, but is deprecated.
Select#
A dropdown of options. value is the selected option's value string.
const [fruit, setFruit] = useState("apple")
<Select
value={fruit}
options={[
{ label: "Apple", value: "apple" },
{ label: "Orange", value: "orange" },
]}
onChange={setFruit}
/>options:{ label: string; value: string; disabled?: boolean }[]value:string·onChange:(value: string) => voidplaceholder(default"Select…"),disabled
Feedback#
Badge#
A small status pill.
<Badge intent="success">Active</Badge>
<Badge intent="danger">Error</Badge>intent:"neutral" | "primary" | "success" | "warning" | "danger" | "info"(default"neutral"). Toast uses the same set.
Toast#
Transient notifications. Wrap your app in <ToastProvider> once, then call useToast() (which returns a toast function) from anywhere.
import { ToastProvider, useToast } from "onejs-ui"
function SaveButton() {
const toast = useToast()
return (
<Button onClick={() => toast({ message: "Saved", intent: "success" })}>
Save
</Button>
)
}
// at the root:
<ToastProvider placement="bottom-right">
<App />
</ToastProvider>toast(options):{ message, title?, intent?, duration? }intent: the same set as Badge (default"neutral"). The oldertonestill works, with"default"meaning"neutral".duration: ms before auto-dismiss;0keeps it open. Default4000.
Overlays#
All overlays render into a portal and fade in and out. The anchored ones (Tooltip, Popover, DropdownMenu) take a placement such as "bottom-start" or "top" and flip to stay on screen.
Every overlay, Select's menu included, is opened and closed the same way. Leave it alone and it owns its open state, starting from defaultOpen. Pass open to control it, and onOpenChange(open) to hear what it asks for: a trigger asks to toggle (a Tooltip's child, by hovering), and Escape, a press outside or a chosen item asks to close.
const [open, setOpen] = useState(false)
<Popover open={open} onOpenChange={setOpen} trigger={<Button>Options</Button>}>
<Text>Popover content</Text>
</Popover>Tooltip#
A label shown while the pointer hovers its child (default placement is "top"). It does not open on keyboard or gamepad focus by itself; pass open to show it from elsewhere, such as while its control has focus.
<Tooltip label="Delete permanently">
<Button intent="danger">Delete</Button>
</Tooltip>Popover#
A floating panel anchored to a trigger, which toggles it. It closes on Escape or a press outside.
<Popover trigger={<Button>Options</Button>} placement="bottom">
<Text>Popover content</Text>
</Popover>DropdownMenu#
A menu of actions anchored to a trigger.
import { DropdownMenu, MenuItem, MenuSeparator } from "onejs-ui"
<DropdownMenu trigger={<Button>Actions</Button>}>
<MenuItem onSelect={duplicate}>Duplicate</MenuItem>
<MenuItem onSelect={rename}>Rename</MenuItem>
<MenuSeparator />
<MenuItem intent="danger" onSelect={remove}>Delete</MenuItem>
</DropdownMenu>MenuItem: onSelect, disabled, intent ("neutral" or "danger"). The menu closes after a selection.
Dialog#
A centered modal with a backdrop and a focus trap. It has no trigger of its own, so it is usually controlled.
const [open, setOpen] = useState(false)
<Dialog open={open} onOpenChange={setOpen} title="Delete file?">
<Text tone="muted">This can't be undone.</Text>
<HStack gap={8}>
<Button intent="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button intent="danger" onClick={confirm}>Delete</Button>
</HStack>
</Dialog>open:boolean·onOpenChange:(open: boolean) => void, called withfalseon Escape or a backdrop press.onClosestill works, but is deprecated.title,dismissOnEscape(defaulttrue),dismissOnScrimClick(defaulttrue),className.
Drawer#
A panel that slides in from an edge. Same control model as Dialog.
const [open, setOpen] = useState(false)
<Drawer open={open} onOpenChange={setOpen} side="right" title="Filters">
{/* content */}
</Drawer>side:"left" | "right" | "top" | "bottom"(default"right")open,onOpenChange,title,dismissOnEscape,dismissOnScrimClick.