Valora

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 subpath
import { 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.
  • asChild merges onto your element. Pass asChild and a single child to render behavior onto your tag instead of the default one (via Slot).
  • Refs forward; props spread. Each primitive forwards its ref to the DOM node and spreads unknown props through.

Primitives

PrimitiveElementOwns
SlotasChild 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
PopoverPrimitiverender-propopen 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.

On this page

Valora is local-first

No API key, no server — everything in this doc runs on-device.

Star on GitHub