Primitive

Unstyled base element that renders any native HTML element with optional asChild composition.

Primitive

The Primitive component is the foundation of all unstyled, headless UI components in @loke/ui. It provides a consistent way to render any native HTML element (div, button, span, etc.) with support for the asChild pattern.

Primitive is rarely used directly. You use it internally when building custom components, and you interact with it through other primitives (Button, Dialog, etc.).


Features

  • Renders any native HTML element (div, button, span, a, form, img, svg, etc.)
  • Supports asChild to merge props with a child element via Slot
  • Forwards refs correctly
  • Passes through all native HTML attributes

Usage

import { Primitive } from '@loke/ui/primitive';

// Render a div
<Primitive.div className="my-component">Content</Primitive.div>

// Render a button
<Primitive.button onClick={handleClick}>Click me</Primitive.button>

// Use asChild to render a custom element with Primitive props
<Primitive.button asChild>
  <a href="/page">Link that looks like a button</a>
</Primitive.button>

Props

Prop

Type


Available Elements

Primitive supports all common HTML elements:

Primitive.a
Primitive.button
Primitive.div
Primitive.form
Primitive.h2
Primitive.h3
Primitive.img
Primitive.input
Primitive.label
Primitive.li
Primitive.nav
Primitive.ol
Primitive.p
Primitive.select
Primitive.span
Primitive.svg
Primitive.ul

Examples

Primitive.div — renders a <div>

Primitive.p — renders a <p>

Basic element rendering

<Primitive.div className="p-4 border rounded">
  Unstyled div with custom classes
</Primitive.div>

Form elements

<Primitive.label htmlFor="email">Email</Primitive.label>
<Primitive.input id="email" type="email" />

asChild pattern

// Without asChild: extra wrapper
<Primitive.button onClick={onClose}>
  <span>Close</span>
</Primitive.button>

// With asChild: no wrapper, props merged onto child
<Primitive.button asChild>
  <span onClick={onClose}>Close</span>
</Primitive.button>

How asChild works

When asChild is true:

  1. Primitive uses Slot to merge its props with the child element
  2. Handlers (onClick, onChange, etc.) are composed together
  3. Class names are merged (both applied)
  4. Styles are merged
  5. Refs are forwarded to the child

This allows you to render a custom element while keeping Primitive's behavior (proper prop passing, ref forwarding, handler composition).


Accessibility

Primitive is unstyled and has no built-in accessibility features. Ensure:

  • Semantic HTML is used (button for buttons, a for links, etc.)
  • ARIA attributes are applied correctly (role, aria-label, aria-expanded, etc.)
  • Keyboard interactions are implemented (Tab, Enter, Escape, Arrow keys as appropriate)

Best practices

  • Use Primitive internally in component libraries; rarely use directly in applications
  • Prefer semantic HTML elements over divs (button, a, label, etc.)
  • Don't set asChild={true} on non-interactive elements rendered as interactive (e.g., div as button without proper ARIA/keyboard handling)
  • Remember that Primitive adds no styling; styling is the responsibility of the consuming code