Skip to content
Guide

Adapting headless components ​

Ownership boundary ​

ConcernOwner
Semantics, ARIA, focus, keyboard inputNative or headless primitive
Controlled state and emitted eventsExisting application/component API
Geometry, surfaces, typography, motionak-ui CSS and tokens
Product copy, brand, information priorityHost application

Do not replace behavior to make styling easier. Style the rendered parts and public state attributes.

Adapter workflow ​

  1. Inventory the primitive's rendered parts, portal behavior, states, CSS variables, and keyboard contract.
  2. Write the intended component anatomy: root, trigger, surface/content, title, description, controls, and optional metadata.
  3. Keep the primitive's required parts and accessible names.
  4. Add stable project-owned or .ak-* classes to rendered DOM parts.
  5. Map state attributes such as data-state, data-disabled, data-invalid, aria-selected, and aria-checked to visible ak-ui states.
  6. Apply semantic tokens and the selected style intensity.
  7. Verify the real portalled/teleported DOM, focus order, escape behavior, outside interaction, and reduced motion.

State mapping ​

Primitive stateRequired visual result
open, expanded, activeStronger signal plus a structural or position change
selected, checked, currentPersistent fill/line and a non-color indicator
disabledReduced emphasis and disabled pointer/keyboard behavior from the primitive
invalidDanger signal plus text or icon explanation
loadingPreserve dimensions, expose status text, and avoid blocking motion
focus-visibleUnclipped, high-contrast focus indicator

Do not invent a new state store when the primitive already exposes these states.

Styling pattern ​

Use a narrow opt-in scope and semantic tokens:

css
[data-ak-ui] .command-option {
  min-height: var(--ak-density-control-height);
  padding: var(--ak-space-3) var(--ak-space-4);
  border: var(--ak-line-hairline) solid transparent;
  color: var(--ak-text-primary);
  background: var(--ak-surface-raised);
  transition:
    background var(--ak-motion-fast) var(--ak-ease-standard),
    transform var(--ak-motion-fast) var(--ak-ease-standard);
}

[data-ak-ui] .command-option[data-state='checked'] {
  border-color: var(--ak-signal-action);
  background: color-mix(in srgb, var(--ak-signal-action) 18%, var(--ak-surface-raised));
  transform: translateX(var(--ak-space-1));
}

[data-ak-ui] .command-option:focus-visible {
  outline: var(--ak-focus-width) solid var(--ak-focus-color);
  outline-offset: var(--ak-focus-offset);
}

For teleported content, put the opt-in attribute/class on the teleported surface itself or use a global stylesheet. Do not assume it remains a descendant of the application root.

Selecting a behavior foundation ​

Prefer, in order:

  1. The headless library already used by the project.
  2. Native browser semantics when they satisfy the interaction.
  3. A framework-appropriate headless library already accepted by the user.
  4. Reka UI for missing complex Vue behavior.

Do not add a headless dependency for cards, decorative panels, labels, basic buttons, or other presentation-only primitives.

Completion criteria ​

An adapter is complete only when it preserves the original behavior contract, responds to every relevant state, uses public tokens, survives its portal location, works at narrow widths, and exposes visible keyboard focus.

Interfaces and naming ​

ak-ui treats CSS classes and --ak-* variables as public interfaces. Framework adapters handle properties, events and state mapping without copying a second visual system.

Class structure ​

Components use .ak-{component} as their root class, __ for internal elements and -- for states and variants:

html
<button class="ak-button ak-button--action">Start operation</button>

<div class="ak-input-number">
  <input class="ak-input-number__inner">
</div>

This resembles BEM without adding meaningless levels for naming's sake. Consumers should depend on public classes, not the layout containers of example pages.

Visual variables ​

To change colors, dimensions or backgrounds, first override the component's public --ak-* variables:

css
.deployment-panel {
  --ak-card-place-color: var(--ak-color-advanced);
  --ak-loading-color: var(--ak-color-primary);
}

See Design tokens for semantic color, typography, spacing, geometry, motion and focus variables. Component-specific variables are documented on each component page.

Adapter boundaries ​

  • CSS Core is the single source of visual styles and stable class names for existing official components.
  • Vue adapters may encapsulate props, slots, events and keyboard interaction, but do not redeclare <style>.
  • HTML and Vue use the same .ak-* classes, so Core fixes to spacing, typography and states apply to both.
  • Override variables first for visual changes. Edit the copied adapter source when structure or behavior needs to change.
  • If ak-ui has no matching component, you may add local styles to an existing native/headless primitive. Reuse tokens.css and follow the headless adapter contract.

See the Reka UI example for a complete Vue implementation.

Unofficial ak-ui design language study.