Doc Page

Primitive Concepts

Learn the shared state and event principles behind all primitives.

Updated: 2026-05-30 · Owner: docs@nake-ui

Mental Model

A primitive is a behavior contract with a public DOM shape. It is not a styled component. The controller decides state transitions, the adapter maps them to framework syntax, and the DOM reflects the result for users, assistive technology, tests, and agents.

Every primitive has four layers:

  1. Anatomy: named slots and their relationships.
  2. State: controlled/uncontrolled inputs and reflected output attributes.
  3. Interaction: native events, keyboard model, focus policy, and dismissal rules.
  4. Validation: schema-backed rules that report broken structure or semantics.

Sources: shared/en/primitive-concepts

Anatomy

Slots are public. If a spec names trigger, content, option, panel, or hidden-input, the rendered DOM should expose that slot through data-slot.

Slot rules:

  • Required slots must exist.
  • Repeatable slots need stable identity.
  • Optional slots still follow public naming when rendered.
  • Parent/child relationships matter for validation.
  • Portal boundaries do not erase ownership relationships.

An agent should be able to inspect markup and answer "which primitive is this, which slot is this node, and what state is it in?"

Sources: shared/en/primitive-concepts

State

Primitives support controlled and uncontrolled state when interaction complexity requires it.

Controlled examples:

  • open + onOpenChange for Dialog, Popover, Menu, Select, Combobox, Tooltip.
  • value + onValueChange for Tabs, Listbox, Select.
  • inputValue + onInputValueChange for Combobox.

Uncontrolled examples:

  • defaultOpen
  • defaultValue
  • defaultInputValue

Do not pass both controlled and uncontrolled values for the same state dimension after initial implementation. The validator treats controlled/uncontrolled conflicts as contract bugs because they create ambiguous truth.

Sources: shared/en/primitive-concepts

Reflected Attributes

Important state must be visible in DOM:

State concept Typical reflection
open/closed data-state, aria-expanded, hidden
selected data-selected, aria-selected
highlighted data-highlighted, aria-activedescendant or roving tabIndex
disabled data-disabled, aria-disabled, native disabled when valid
invalid data-invalid, aria-invalid
orientation data-orientation, aria-orientation where applicable
floating layout data-placement, data-side, data-align

If state is visible only through CSS classes, the primitive is not agent-friendly enough.

Sources: shared/en/primitive-concepts

Event Model

The event model follows the web platform:

  • Pointer and mouse: pointerdown, mousedown, click, mouseenter, mouseleave.
  • Focus: focus, blur, focusin, focusout.
  • Keyboard: keydown with named key handling.
  • Forms: input, change, submit.

Adapter callbacks should describe state changes, but they should not replace platform events as the underlying model.

Sources: shared/en/primitive-concepts

Keyboard And Focus

Complex primitives require explicit keyboard behavior:

  • Tabs: roving focus, Arrow*, Home, End, optional manual activation with Enter/Space.
  • Dialog: focus trap in modal mode, LIFO close behavior, focus restoration.
  • Menu: roving focus, typeahead, submenu open/close, Escape.
  • Listbox/Select: option navigation and selection commit.
  • Combobox: focus stays on input while aria-activedescendant points at highlighted options.
  • Tooltip: trigger keeps focus, content is not tabbed into by default.

Do not ship a primitive that "looks clickable" but cannot be operated from the keyboard.

Sources: shared/en/primitive-concepts

ARIA

Use native elements first, then ARIA only where native semantics are insufficient.

Common relationships:

  • aria-controls: trigger/input points to content/listbox/panel.
  • aria-labelledby: content/panel points back to label/title/trigger.
  • aria-describedby: trigger or content points to helper text.
  • aria-selected: option or tab selected state.
  • aria-activedescendant: active option while DOM focus remains on input/listbox.
  • aria-modal: modal dialog content.

Every ARIA ID reference should resolve to an element in the rendered DOM after hydration.

Sources: shared/en/primitive-concepts

SSR And Hydration

Server output must match the client contract:

  • IDs are deterministic.
  • Initial state is the same on server and client.
  • Client-only measurement waits until mount.
  • Hidden content stays hidden until activated.
  • Portal targets do not break ARIA linkage.

Hydration mismatches are not just React warnings. They can break validator repair, focus restoration, and screen reader relationships.

Sources: shared/en/primitive-concepts

Validator Language

Validator messages should include:

  • Rule ID.
  • Trigger condition.
  • Fix suggestion.
  • Whether the issue can be auto-fixed.

Example issue shape:

{
  "ruleId": "aria.broken-controls-linkage",
  "trigger": "Trigger aria-controls points to a missing content id.",
  "suggestion": "Render the content slot with the referenced id or update aria-controls.",
  "canAutoFix": false
}

Sources: shared/en/primitive-concepts

Checklist

  • Required slots are present.
  • State has one source of truth.
  • ARIA relationships resolve.
  • Keyboard model is documented and tested.
  • Focus behavior is deterministic.
  • Public data-* attributes match the schema.

Sources: shared/en/primitive-concepts