React 19 renderer for OneJS (Unity UI Toolkit)
npm install onejs-reactReact 19 reconciler for Unity's UI Toolkit.
| File | Purpose |
|------|---------|
| src/host-config.ts | React reconciler implementation (createInstance, commitUpdate, etc.) |
| src/renderer.ts | Entry point: render(element, container) |
| src/components.tsx | Component wrappers: View, Text, Label, Button, TextField, etc. |
| src/screen.tsx | Responsive design: ScreenProvider, useBreakpoint, useScreenSize, useResponsive |
| src/types.ts | TypeScript type definitions (includes Vector Drawing types) |
| src/index.ts | Package exports |
| Component | UI Toolkit Element | Description |
|-----------|-------------------|-------------|
| View | VisualElement | Container element |
| Text | TextElement | Primary text display |
| Label | Label | Form labels, semantic labeling |
| Button | Button | Interactive button |
| TextField | TextField | Text input |
| Toggle | Toggle | Checkbox/toggle |
| Slider | Slider | Numeric slider |
| ScrollView | ScrollView | Scrollable container |
| Image | Image | Image display |
| ListView | ListView | Virtualized list |
Raw text in JSX (e.g., ) creates a TextElement, providing semantic distinction from explicit components.
``tsx
import { render, View, Text, Label, Button } from 'onejs-react';
function App() {
return (
);
}
render(
`
OneJS has multiple type sources. Here's when to use each:
Import types from onejs-react for refs and component props:
`tsx
import { View, Button, VisualElement, ButtonElement } from "onejs-react"
function MyComponent() {
const viewRef = useRef
const buttonRef = useRef
useEffect(() => {
buttonRef.current?.Focus()
}, [])
return (
)
}
`
For creating elements outside React, use unity-types:
`tsx
import { Button } from "UnityEngine.UIElements"
const btn = new Button()
btn.text = "Dynamic Button"
__root.Add(btn)
`
The render() function accepts any RenderContainer:
`tsx
import { render, RenderContainer } from "onejs-react"
// __root is provided by the runtime
render(
`
``
RenderContainer (minimal: __csHandle, __csType)
└── VisualElement (full API: style, hierarchy, events)
├── TextElement (+ text property)
│ ├── LabelElement
│ └── ButtonElement
├── TextFieldElement (+ value, isPasswordField, etc.)
├── ToggleElement (+ value: boolean)
├── SliderElement (+ value, lowValue, highValue)
└── ScrollViewElement (+ scrollOffset, ScrollTo)
- Element types: Use ojs- prefix internally (e.g., ojs-view, ojs-button) to avoid conflicts with HTML typespadding
- Style shorthands: /margin are expanded to individual properties (UI Toolkit requirement)__eventAPI
- Style cleanup: When props change, removed style properties are cleared (not just new ones applied)
- className updates: Selective add/remove of classes (not full clear + reapply)
- Event handlers: Registered via from QuickJSBootstrap.js{ element, type, props, eventHandlers: Map, appliedStyleKeys: Set }
- Instance structure:
`bash`
npm run typecheck # TypeScript check (no build output - consumed directly by App)
npm test # Run test suite
npm run test:watch # Run tests in watch mode
Test suite uses Vitest with mocked Unity CS globals. Tests are in src/__tests__/:
| File | Coverage |
|------|----------|
| host-config.test.ts | Instance creation, style/className management, events, children |renderer.test.tsx
| | Integration tests: render(), unmount(), React state, effects |components.test.tsx
| | Component wrappers, prop passing, event mapping |mocks.ts
| | Mock implementations of Unity UI Toolkit classes |setup.ts
| | Global test setup for CS, __eventAPI |
OneJS exposes Unity's Painter2D API for GPU-accelerated vector graphics. Any element can render custom vector content via onGenerateVisualContent.
`tsx
import { View, render } from "onejs-react"
function Circle() {
return (
onGenerateVisualContent={(mgc) => {
const p = mgc.painter2D
// Draw a filled circle
p.fillColor = new CS.UnityEngine.Color(1, 0, 0, 1) // Red
p.BeginPath()
p.Arc(
new CS.UnityEngine.Vector2(100, 100), // center
80, // radius
CS.UnityEngine.UIElements.Angle.Degrees(0),
CS.UnityEngine.UIElements.Angle.Degrees(360),
CS.UnityEngine.UIElements.ArcDirection.Clockwise
)
p.Fill(CS.UnityEngine.UIElements.FillRule.NonZero)
}}
/>
)
}
`
Path operations:
- BeginPath() - Start a new pathClosePath()
- - Close the current subpathMoveTo(point)
- - Move to point without drawingLineTo(point)
- - Draw line to pointArc(center, radius, startAngle, endAngle, direction)
- - Draw arcArcTo(p1, p2, radius)
- - Draw arc tangent to two linesBezierCurveTo(cp1, cp2, end)
- - Cubic bezier curveQuadraticCurveTo(cp, end)
- - Quadratic bezier curve
Rendering:
- Fill(fillRule) - Fill the current pathStroke()
- - Stroke the current path
Properties:
- fillColor - Fill color (Unity Color)strokeColor
- - Stroke color (Unity Color)lineWidth
- - Stroke width in pixelslineCap
- - Line cap style (Butt, Round, Square)lineJoin
- - Line join style (Miter, Round, Bevel)
Use MarkDirtyRepaint() to trigger a repaint when drawing state changes:
`tsx
function AnimatedCircle() {
const ref = useRef
const [radius, setRadius] = useState(50)
useEffect(() => {
// Trigger repaint when radius changes
ref.current?.MarkDirtyRepaint()
}, [radius])
return (
style={{ width: 200, height: 200 }}
onGenerateVisualContent={(mgc) => {
const p = mgc.painter2D
p.fillColor = new CS.UnityEngine.Color(0, 0.5, 1, 1)
p.BeginPath()
p.Arc(
new CS.UnityEngine.Vector2(100, 100),
radius,
CS.UnityEngine.UIElements.Angle.Degrees(0),
CS.UnityEngine.UIElements.Angle.Degrees(360),
CS.UnityEngine.UIElements.ArcDirection.Clockwise
)
p.Fill(CS.UnityEngine.UIElements.FillRule.NonZero)
}}
/>
)
}
`
| Feature | Unity Painter2D | HTML5 Canvas |
|---------|-----------------|--------------|
| Transforms | Manual point calculation | Built-in translate/rotate/scale |
| Gradients | Limited (strokeGradient) | Full linear/radial/conic |
| State Stack | Not built-in | save()/restore() |
| Text | Via MeshGenerationContext.DrawText() | fillText/strokeText |
| Shadows | Not available | shadowBlur, shadowColor |
| Clipping | Via nested VisualElements | clip() path-based |
The following types are re-exported from unity-types:
`typescript`
type Vector2 = CS.UnityEngine.Vector2
type Color = CS.UnityEngine.Color
type Angle = CS.UnityEngine.UIElements.Angle
type ArcDirection = CS.UnityEngine.UIElements.ArcDirection
type Painter2D = CS.UnityEngine.UIElements.Painter2D
type MeshGenerationContext = CS.UnityEngine.UIElements.MeshGenerationContext
type GenerateVisualContentCallback = (context: MeshGenerationContext) => void
- react-reconciler@0.31.x (React 19 compatible)vitest
- (dev) - Test runnerreact@18.x || 19.x`
- Peer: