R

@robonen/primitives

A collection of unstyled, accessible UI primitives for Vue 3 — the headless building blocks for design systems and component libraries.

Most component libraries bundle behavior and styling together, so the moment your design diverges you end up fighting the framework. @robonen/primitives ships the hard part — state, focus management, keyboard interaction, ARIA wiring, portalling and positioning — and leaves the markup and styling entirely to you. Every primitive is composed from small, controllable parts (a Root, a Trigger, a Content, and so on) following the same conventions, so once you learn one you know them all.

Unstyled by design

No CSS shipped. Primitives render the DOM you ask for and expose state via data attributes, so you bring your own styles — Tailwind, vanilla CSS, anything.

Accessible out of the box

Focus scopes, roving tabindex, visually-hidden labels and correct ARIA roles are handled for you. The suite is tested against axe-core in a real browser.

Controlled or uncontrolled

Bind state with v-model when you need control, or set a defaultValue / defaultOpen and let the primitive manage itself.

Composable & polymorphic

Every part takes an as prop, or use as="template" to merge behavior onto your own element. Floating UI powers positioning for popovers, tooltips and menus.

Install

sh
pnpm add @robonen/primitives

Usage

Primitives are assembled from named parts. Here is a complete dialog — open state is uncontrolled, focus is trapped, body scroll is locked, and the content is portalled out of the DOM flow:

vue
<script setup lang="ts">
import {
  DialogRoot,
  DialogTrigger,
  DialogPortal,
  DialogOverlay,
  DialogContent,
  DialogTitle,
  DialogDescription,
  DialogClose,
} from '@robonen/primitives';
</scr­ipt>

<template>
  <DialogRoot>
    <DialogTrigger class="btn">Open</DialogTrigger>

    <DialogPortal>
      <DialogOverlay class="overlay" />
      <DialogContent class="dialog">
        <DialogTitle>Delete project</DialogTitle>
        <DialogDescription>This action cannot be undone.</DialogDescription>
        <DialogClose class="btn">Cancel</DialogClose>
      </DialogContent>
    </DialogPortal>
  </DialogRoot>
</template>

Need full control over open state? Bind it directly — the same primitive works either way:

vue
<DialogRoot v-model:open="isOpen">
  <!-- ... -->
</DialogRoot>

The Primitive component

At the core of every part is Primitive, a polymorphic functional component. Pass as to choose the element, or as="template" to forward behavior onto a child of your own.

ts
import { Primitive, Slot } from '@robonen/primitives';

// <Primitive as="button" /> renders a <button>
// <Primitive as="template"> merges props onto the slotted child

Where to next

The full primitive index is listed below. A few good starting points:

All components · 47

Accordion4 parts
RootItemTriggerContent
AlertDialog4 parts
ActionCancelContentRoot
AspectRatio1 parts
Root
Avatar3 parts
RootImageFallback
Calendar12 parts
RootHeaderHeadingPrev+8
Checkbox2 parts
IndicatorRoot
Collapsible3 parts
RootTriggerContent
Combobox16 parts
AnchorArrowCancelContent+12
Command8 parts
EmptyGroupInputItem+4
ContextMenu16 parts
ArrowCheckboxItemContentGroup+12
DatePicker9 parts
RootTriggerAnchorPortal+5
Dialog11 parts
RootTriggerPortalOverlay+7
DismissableLayer1 parts
Root
DropdownMenu16 parts
ArrowCheckboxItemContentGroup+12
Editable7 parts
RootAreaPreviewInput+3
FocusScope1 parts
Root
HoverCard6 parts
RootTriggerPortalContent+2
Label1 parts
Root
Listbox7 parts
RootContentItemItemIndicator+3
Menu20 parts
AnchorArrowCheckboxItemContent+16
Menubar17 parts
RootMenuTriggerContent+13
NavigationMenu10 parts
RootSubListItem+6
NumberField4 parts
DecrementIncrementInputRoot
Pagination8 parts
RootListListItemEllipsis+4
PinInput2 parts
InputRoot
Popover10 parts
RootTriggerAnchorContent+6
Popper4 parts
RootAnchorContentArrow
Presence1 parts
Root
Progress2 parts
RootIndicator
RadioGroup3 parts
IndicatorItemRoot
RovingFocus2 parts
ItemGroup
ScrollArea10 parts
CornerRootScrollbarScrollbarAuto+6
Select20 parts
RootTriggerValueIcon+16
Separator1 parts
Root
Slider4 parts
RangeRootThumbTrack
Stepper7 parts
RootItemTriggerIndicator+3
Switch1 parts
Root
Tabs4 parts
RootListTriggerContent
TagsInput6 parts
RootItemItemTextItemDelete+2
Teleport1 parts
Root
Toast7 parts
ProviderRootTitleDescription+3
Toggle1 parts
Root
ToggleGroup2 parts
ItemRoot
Toolbar3 parts
ButtonRootSeparator
Tooltip7 parts
ProviderRootTriggerPortal+3
Tree2 parts
RootItem
VisuallyHidden1 parts
Root