onejs-ui#

onejs-ui is a themeable React component library for OneJS — built for Unity UI Toolkit, GPU-rendered, and gamepad/keyboard-native. It's a separate package on top of onejs-react, not a web library ported over.

You get ~20 components (layout, typography, form controls, overlays), light/dark theming you can swap instantly, and a real focus-visible keyboard/gamepad focus ring that's wired up for you.

Why a separate library?#

onejs-react gives you the primitives (View, Label, Button, Toggle, ...) that map 1:1 to UI Toolkit elements. onejs-ui is the layer above: opinionated, themed components you compose into real screens — a settings panel, a HUD, a dialog — without restyling primitives by hand each time.

  • Game-engine first. Designed for UI Toolkit and controller/keyboard navigation. Web-only patterns (data grids, breadcrumbs, calendars) are intentionally out of scope.
  • Instant theming. Components style themselves through USS classes that read --ojs-* custom properties. Swapping a theme recompiles one small variables sheet and Unity re-resolves the cascade natively — no React re-render.
  • Native-backed controls. Checkbox, Switch, Slider, Input, and Radio are built on the matching UI Toolkit controls, so they're genuine focus targets with full keyboard/IME behavior.
  • Cheap by construction. Most components are pure-TSX composites over onejs-react, styled by embedded CSS Modules. No custom C# required.

Requirements#

  • OneJS v3 runtime. The focus ring's reliability depends on the runtime's tick-based focuschange signal. Use a recent OneJS build that includes it; with an older runtime the ring degrades gracefully to nav-event-driven only.
  • Peer dependencies: react (18 or 19) and onejs-react.

Install#

npm install onejs-ui

onejs-ui ships raw TS/TSX — there's no build step. Your app's esbuild bundles it, the same model as onejs-react.

Quickstart#

Wrap your app in <ThemeProvider>. That applies the theme and initializes the focus-visible manager, so everything inside gets themed components with working focus rings — no extra wiring.

import { useState } from "react"
import { render } from "onejs-react"
import {
    ThemeProvider, Card, Heading, Text, Button, Checkbox, HStack,
    useTheme, darkTheme, lightTheme,
} from "onejs-ui"

function App() {
    const { tokens, setTheme } = useTheme()
    const [agree, setAgree] = useState(false)
    return (
        <Card variant="raised" style={{ width: 360 }}>
            <Heading level={1}>onejs-ui</Heading>
            <Text tone="muted">Themed via USS variables.</Text>
            <Checkbox label="I agree" value={agree} onChange={setAgree} />
            <HStack gap={8}>
                <Button onClick={() => {}}>Primary</Button>
                <Button intent="secondary" onClick={() => setTheme(tokens === darkTheme ? lightTheme : darkTheme)}>
                    Toggle theme
                </Button>
            </HStack>
        </Card>
    )
}

render(
    <ThemeProvider theme={darkTheme}>
        <App />
    </ThemeProvider>,
    __root,
)

What's included#

GroupComponents
LayoutCard, Stack / HStack / VStack, Divider, Spacer
TypographyText, Heading
Form controlsButton, Checkbox, Switch, Input, Slider, RadioGroup, Select
FeedbackBadge, Toast
OverlaysPopover, Dialog, DropdownMenu, Drawer, Tooltip
FoundationsThemeProvider, FocusScope, Overlay, motion (usePresence)

Next steps#

  • Theming — tokens, custom themes, and live theme swaps.
  • Focus & navigation — the focus-visible model and FocusScope.
  • Components — the full reference with usage for every component.