Events#

Handle user interactions with React-style event props.

Click Events#

<Button
    text="Click me"
    onClick={(e) => {
        console.log("Clicked at", e.x, e.y)
    }}
/>

Pointer Events#

Track pointer (mouse/touch) interactions:

<View
    style={{ width: 200, height: 200, backgroundColor: "#333" }}
    onPointerDown={(e) => console.log("Down", e.x, e.y)}
    onPointerUp={(e) => console.log("Up")}
    onPointerMove={(e) => console.log("Move", e.x, e.y)}
    onPointerEnter={(e) => console.log("Enter")}
    onPointerLeave={(e) => console.log("Leave")}
>
    <Label text="Hover and click me" />
</View>

Focus Events#

<TextField
    placeholder="Type here..."
    onFocus={(e) => console.log("Focused")}
    onBlur={(e) => console.log("Lost focus")}
/>

Change Events#

<TextField value={text} onChange={(e) => setText(e.value)} />
<Toggle value={on} onChange={(e) => setOn(e.value)} label="Enable" />
<Slider value={vol} lowValue={0} highValue={100} onChange={(e) => setVol(e.value)} />

Coordinates and Field Names#

Two things about the event object catch out anyone arriving from the web, and neither fails loudly. A handler typed as any accepts a misspelled field and quietly hands you undefined, so the symptom is a control that does nothing rather than an error.

Pointer positions are panel coordinates. e.x and e.y are measured from the top left of the panel, not from the element your handler sits on. There is no localX or localY. For a position relative to your own element, subtract its worldBound:

function Canvas() {
    const ref = useRef(null)

    return (
        <View
            ref={ref}
            style={{ width: 300, height: 300 }}
            onPointerDown={(e) => {
                const wb = ref.current.worldBound
                const localX = e.x - wb.x
                const localY = e.y - wb.y
            }}
        />
    )
}

Change events carry value. Not newValue, and not target.value.

Web habitOneJS
e.localX, e.localYe.x - ref.current.worldBound.x, same for y
e.clientX, e.clientYe.x, e.y
e.newValuee.value
e.target.valuee.value
e.target as an elementan integer C# handle: compare it against ref.current.__csHandle

If you also read the pointer through the input module, keep the two apart. input.mouse.position is in Unity screen coordinates, with the origin at the bottom left and y increasing upward; event coordinates are panel coordinates, with the origin at the top left and y increasing downward. Pick one space per piece of hit testing rather than mixing them.

Keyboard Events#

<TextField
    onKeyDown={(e) => {
        if (e.keyCode === CS.UnityEngine.KeyCode.Return) {
            console.log("Enter pressed!")
        }
    }}
/>

Event Propagation#

Events bubble up through the component tree. Use stopPropagation() to prevent this:

<View onClick={() => console.log("Parent")}>
    <Button
        text="Click"
        onClick={(e) => {
            e.stopPropagation()
            console.log("Only this handler runs")
        }}
    />
</View>

Suppressing native behavior#

stopPropagation() only stops OneJS's own (JavaScript-side) bubbling. To stop the native UI Toolkit behavior of a control underneath your handler (for example a ScrollView's scroll), call preventDefault():

// Keep the ScrollView from scrolling while the pointer is over this element
<ScrollView>
    <View onWheel={(e) => e.preventDefault()}>
        ...
    </View>
</ScrollView>

preventDefault() works on pointer (onPointerDown/onPointerMove/onPointerUp), onClick, and onWheel handlers. Both methods are lowercase (e.preventDefault(), e.stopPropagation()). The PascalCase C# names are not available on the event object.

Pointer Capture#

Lock pointer events to a specific element. Essential for reliable dragging. Without capture, fast pointer movement can leave the element's bounds and drop events.

useExtensions(CS.UnityEngine.UIElements.PointerCaptureHelper)

function Draggable({ children }) {
    const ref = useRef(null)
    const [pos, setPos] = useState({ x: 0, y: 0 })
    const startPos = useRef({ x: 0, y: 0 })

    return (
        <View
            ref={ref}
            style={{ position: "absolute", left: pos.x, top: pos.y }}
            onPointerDown={(e) => {
                ref.current.CapturePointer(e.pointerId)
                startPos.current = { x: e.x - pos.x, y: e.y - pos.y }
            }}
            onPointerMove={(e) => {
                if (ref.current.HasPointerCapture(e.pointerId)) {
                    setPos({
                        x: e.x - startPos.current.x,
                        y: e.y - startPos.current.y,
                    })
                }
            }}
            onPointerUp={(e) => ref.current.ReleasePointer(e.pointerId)}
        >
            {children}
        </View>
    )
}

useExtensions registers C# extension methods so they can be called on elements. See C# Interop: Extension Methods for details.

Other Events#

// Geometry: detect size/position changes
<View onGeometryChanged={(e) => console.log(e.newRect)} />

// Transitions: monitor USS animations
<View
    onTransitionEnd={(e) => console.log("Done:", e.styleProperty)}
/>

// Navigation: gamepad/keyboard UI navigation
<View
    onNavigationMove={(e) => console.log("Direction:", e.direction)}
    onNavigationSubmit={(e) => console.log("Submit")}
/>

// Mouse-specific (prefer pointer events for cross-device support)
<View
    onMouseDown={(e) => console.log("Mouse", e.button)}
    onWheel={(e) => console.log("Scroll", e.deltaY)}
/>

Event Object Properties#

Pointer / Mouse Event#

PropertyTypeDescription
typestringEvent type name
xnumberX position in panel coordinates
ynumberY position in panel coordinates
buttonnumberMouse button (0=left, 1=middle, 2=right)
pointerIdnumberUnique pointer identifier

Every synthetic event also carries target and currentTarget (C# handles, not elements) plus preventDefault(), stopPropagation(), defaultPrevented, and propagationStopped.

Wheel Event#

PropertyTypeDescription
typestringEvent type name
deltaXnumberHorizontal scroll amount
deltaYnumberVertical scroll amount (positive = down)

Change Event#

PropertyTypeDescription
typestringEvent type name
valueTNew value (type depends on control)

Event Mapping Reference#

Click & Pointer#

React PropUI Toolkit Event
onClickClickEvent
onPointerDownPointerDownEvent
onPointerUpPointerUpEvent
onPointerMovePointerMoveEvent
onPointerEnterPointerEnterEvent
onPointerLeavePointerLeaveEvent
onPointerCancelPointerCancelEvent
onPointerCapturePointerCaptureEvent
onPointerCaptureOutPointerCaptureOutEvent
onPointerStationaryPointerStationaryEvent

Mouse#

React PropUI Toolkit Event
onMouseDownMouseDownEvent
onMouseUpMouseUpEvent
onMouseMoveMouseMoveEvent
onMouseEnterMouseEnterEvent
onMouseLeaveMouseLeaveEvent
onMouseOverMouseOverEvent
onMouseOutMouseOutEvent
onWheelWheelEvent
onContextClickContextClickEvent

Focus & Input#

React PropUI Toolkit Event
onFocusFocusEvent
onBlurBlurEvent
onFocusInFocusInEvent
onFocusOutFocusOutEvent
onChangeChangeEvent<T>
onInputInputEvent
onKeyDownKeyDownEvent
onKeyUpKeyUpEvent

Drag & Drop#

React PropUI Toolkit Event
onDragEnterDragEnterEvent
onDragLeaveDragLeaveEvent
onDragUpdatedDragUpdatedEvent
onDragPerformDragPerformEvent
onDragExitedDragExitedEvent

Layout & Transitions#

React PropUI Toolkit Event
onGeometryChangedGeometryChangedEvent
onTransitionRunTransitionRunEvent
onTransitionStartTransitionStartEvent
onTransitionEndTransitionEndEvent
onTransitionCancelTransitionCancelEvent
React PropUI Toolkit Event
onNavigationMoveNavigationMoveEvent
onNavigationSubmitNavigationSubmitEvent
onNavigationCancelNavigationCancelEvent
onTooltipTooltipEvent

Custom Rendering#

For custom drawing with onGenerateVisualContent, see the Vector Drawing guide.