Headless primitives
Unstyled, behavior-only React primitives (@valora-ai/react/headless) — the layer the Teams and shadcn skins are both built on.
@valora-ai/react/headless is a set of unstyled, behavior-only React primitives in
the spirit of Radix. They own interaction, state, and accessibility; you own the
markup and styling. Both the themed @valora-ai/react (Teams skin) and
@valora-ai/react/shadcn (Tailwind skin) are built on these exact primitives.
npm install @valora-ai/react # primitives live at the /headless subpathimport { ControlButtonPrimitive } from "@valora-ai/react/headless";No stylesheet is required.
Conventions
Every primitive follows the same three rules:
- State is on
data-*attributes. Style[data-active],[data-danger],[data-empty]in your own CSS/Tailwind — the primitive never ships colors. asChildmerges onto your element. PassasChildand a single child to render behavior onto your tag instead of the default one (viaSlot).- Refs forward; props spread. Each primitive forwards its ref to the DOM node and spreads unknown props through.
Primitives
| Primitive | Element | Owns |
|---|---|---|
Slot | — | asChild merge: className/style/handlers/ref |
ControlButtonPrimitive | <button> | data-active / data-danger, a11y, asChild |
TrackTogglePrimitive | <button> | mic/camera/screen toggle (room-bound or local), aria-pressed |
ComposerPrimitive | <form> | controlled input + submit, IME-safe, data-empty |
ReactionPickerPrimitive | <div> | emoji buttons + onPick, per-button aria-label |
PopoverPrimitive | render-prop | open state, outside-pointerdown + LIFO-Escape close |
ListboxPrimitive | <div role=listbox> | role=option + aria-selected, click select, Arrow/Home/End nav |
ControlButtonPrimitive
A button whose visual state lives on data-active (decoupled from aria-pressed,
which you set only when it's a real toggle).
<ControlButtonPrimitive
active={muted}
aria-pressed={muted}
onClick={() => setMuted((m) => !m)}
className="rounded px-3 py-2 data-[active]:bg-indigo-600"
>
{muted ? "Unmute" : "Mute"}
</ControlButtonPrimitive>
// asChild — behavior on your own element:
<ControlButtonPrimitive asChild active={on}>
<a href="#settings">Settings</a>
</ControlButtonPrimitive>PopoverPrimitive + ListboxPrimitive
A dropdown is the two composed. PopoverPrimitive is a render-prop that hands you
open, setOpen, a rootRef (attach to your wrapper for outside-click), and
triggerProps (aria-expanded + onClick). Nested popovers close innermost-first
on Escape.
import { PopoverPrimitive, ListboxPrimitive } from "@valora-ai/react/headless";
function VoiceSelect({ items, index, onPick }) {
return (
<PopoverPrimitive>
{({ open, setOpen, rootRef, triggerProps }) => (
<div ref={rootRef} className="relative">
<button {...triggerProps}>Choose a voice…</button>
{open && (
<ListboxPrimitive
options={items}
selectedIndex={index}
getKey={(o) => o.id}
getLabel={(o) => o.label}
onSelect={(i) => { onPick(i); setOpen(false); }}
optionClassName="px-2 py-1.5 aria-selected:bg-zinc-100"
/>
)}
</div>
)}
</PopoverPrimitive>
);
}ListboxPrimitive emits role="listbox" with role="option" children, sets
aria-selected, guarantees each option's accessible name via aria-label={getLabel},
and handles ArrowUp/ArrowDown/Home/End roving focus. Use renderOption to inject
icons/markup without losing the accessible name.
ComposerPrimitive
Owns the chat input value + submit; safe against IME composition (Enter commits the
composition instead of sending). Marks data-empty when there's no sendable text.
import { ComposerPrimitive } from "@valora-ai/react/headless";
<ComposerPrimitive onSend={(text) => send(text)} className="flex gap-2">
{({ inputProps, buttonProps }) => (
<>
<input {...inputProps} className="flex-1 border px-2" />
<button {...buttonProps} className="px-3">Send</button>
</>
)}
</ComposerPrimitive>Building your own skin
Pick the primitives, add your classes, and you have a new design system over the
same behavior — exactly how the Teams and
shadcn layers are built. See the
examples/shadcn-react
app for a full worked example.