React
Native React components with controlled and uncontrolled state, typed callbacks and ref handles. Custom elements with typed wrappers, events and element refs.
Every component is used from React through a named export from its file: ui-dial.tsx exports UiDial, hf-gauge.tsx exports HfGauge. What sits behind the export depends on the collection.
| Collections | Kind | Props | Events | Ref |
|---|---|---|---|---|
| Interaction, Pixel UI | Native React component | React props, controlled or uncontrolled | Typed callbacks with plain values | Root DOM element, or a typed handle |
| Pixel Lab, Pixel HD, Network, Service Map | Custom element with a typed wrapper | Attributes as camelCase props | on handlers receiving a typed CustomEvent | The element, typed with its methods |
Native React components
They are ordinary React 19 function components, written like shadcn/ui: Tailwind classes on your theme tokens, cn() for className, the remaining props spread onto the root element, data-slot and data-state attributes for styling. Open the file and change anything.
State
Stateful components follow the Radix naming. Pass value and a change callback to control them, or defaultValue to let them keep their own state.
import * as React from "react";
import { EffortSlider, type EffortLevel } from "@/components/lumesec/effort-slider";
export function Effort() {
const [level, setLevel] = React.useState<EffortLevel>("medium");
return <EffortSlider value={level} onValueChange={setLevel} recommended="medium" />;
}<UiToggle defaultChecked onCheckedChange={(checked) => save({ thinking: checked })}>
Extended thinking
</UiToggle>| State | Controlled | Uncontrolled | Callback |
|---|---|---|---|
| Value | value | defaultValue | onValueChange(value) |
| On or off | checked | defaultChecked | onCheckedChange(checked) |
| Open or closed | open | defaultOpen | onOpenChange(open) |
Callbacks fire for user actions. Changing a controlled prop from your code animates the component to the new value without calling the callback back. Some controls also report a second moment, such as onValueCommit when a drag ends on a dial or slider; each component page lists its callbacks.
Imperative actions
Where an action is naturally a command (show a toast, compact the context, finish thinking), the component's ref is a typed handle instead of a DOM node:
import * as React from "react";
import { UiContextMeter, type UiContextMeterHandle } from "@/components/lumesec/ui-context-meter";
export function Context() {
const meter = React.useRef<UiContextMeterHandle>(null);
return (
<>
<UiContextMeter ref={meter} defaultValue={46} capacity={200000} />
<button type="button" onClick={() => meter.current?.compact()}>
Compact
</button>
</>
);
}Each component page lists its ref under API reference. Everywhere else, ref points at the root element.
Forms
Pixel UI inputs render native form controls, or a hidden input where the control is custom, so they submit with any <form>, including server actions, and reset with it:
<form action={save}>
<PxInput name="email" type="email" label="Work email" required />
<PxPassword name="password" label="New password" minScore={3} />
<PxSelect name="region" label="Region" options={[{ label: "Frankfurt", value: "fra" }, { label: "Vienna", value: "vie" }]} />
<button type="submit">Create account</button>
</form>Server rendering
The components are client components ("use client") and render their full markup on the server, so text, labels and layout are there on first paint. Canvas layers draw after hydration, and animation loops pause while a component is off screen.
Custom elements with wrappers
The wrapper registers the element, renders it, and types its attributes and events. The element itself is a strict TypeScript class in {tag}.element.ts.
Props are attributes
Attribute names become camelCase props. Numbers can be passed as numbers, presence attributes as booleans. className, style, id, slot, aria-* and data-* pass through to the element, and children render into its slots.
import { HfSlider } from "@/components/lumesec/hf-slider";
<HfSlider min={0} max={100} step={5} value={40} label="Volume" />;Events are handlers
Each custom event becomes an on prop. The handler receives the CustomEvent, typed from the element's event map:
<HfSlider label="Volume" onChange={(event) => setVolume(event.detail.value)} />Events bubble and cross the shadow boundary, so a parent can also listen with addEventListener.
Refs give you the element
The ref is the element class, with its properties and methods:
import * as React from "react";
import { PxOrb, type PxOrbElement } from "@/components/lumesec/px-orb";
export function Agent() {
const orb = React.useRef<PxOrbElement>(null);
return (
<>
<PxOrb ref={orb} state="thinking" />
<button type="button" onClick={() => orb.current?.set("done")}>
Finish
</button>
</>
);
}Outside React, the tags are registered in HTMLElementTagNameMap, so document.querySelector("px-orb") is typed the same way.
Why attribute names are upper-cased
React 19 assigns a prop to a same-named element property when one exists. Several elements expose read-only properties, such as name on form controls, which would throw. The bindings in lib/lumesec/react.ts pass attribute names upper-cased: React finds no property, calls setAttribute, and the browser stores the lower-case name. You never see this unless you inspect the props.
Was this page helpful?