PUI
Getting Started Interactive Primitives

Interactive Primitives

Compose zero-style PUI primitives when your application owns interactive markup.

Interactive primitives

PUI is styled by default. For a hook-managed interaction with application-owned markup and classes, import that family's .Primitive module. Primitives keep the ID, ARIA, state, and JavaScript hook contract while contributing no visual classes.

import PUI.Select.Primitive
<.root id="assignee" class="relative">
<.input id="assignee-value" name="assignee" />
<.trigger id="assignee-trigger" listbox_id="assignee-listbox" class="my-trigger">
<.value placeholder="Choose an assignee" />
</.trigger>
<.content id="assignee-listbox" trigger_id="assignee-trigger" class="my-menu">
<.item value="ada" class="my-option data-[active=true]:bg-accent">Ada</.item>
</.content>
</.root>

Interactive Demo

Select Primitive

What PUI still handles

  • ARIA attributes and keyboard navigation
  • Select positioning, dismissal, and keyboard behavior
  • Only application classes control the appearance
<PUI.Dropdown.Primitive.root id="account-menu" placement="bottom-start">
<PUI.Dropdown.Primitive.trigger id="account-menu-trigger" controls="account-menu-content">
Account
</PUI.Dropdown.Primitive.trigger>
<PUI.Dropdown.Primitive.content id="account-menu-content" class="aria-hidden:hidden block my-menu">
<PUI.Dropdown.Primitive.item class="w-full text-left aria-selected:bg-accent aria-selected:text-accent-foreground">Profile</PUI.Dropdown.Primitive.item>
<PUI.Dropdown.Primitive.item class="w-full text-left aria-selected:bg-accent aria-selected:text-accent-foreground">Settings</PUI.Dropdown.Primitive.item>
</PUI.Dropdown.Primitive.content>
</PUI.Dropdown.Primitive.root>

Custom Menu Primitive

What PUI still handles

  • ARIA attributes and keyboard navigation
  • Popover positioning, dismissal, and focus behavior
  • Slot-based composition for items and triggers

Dialog primitive

<PUI.Dialog.Primitive.root :let={actions} id="delete-project">
<PUI.Dialog.Primitive.backdrop id="delete-project-backdrop" class="fixed inset-0 bg-black/50" />
<PUI.Dialog.Primitive.content id="delete-project-content" class="fixed inset-0 m-auto h-fit w-96 rounded-xl bg-white p-6 shadow-xl">
<h2>Delete project?</h2>
<button type="button" phx-click={actions.hide}>Cancel</button>
</PUI.Dialog.Primitive.content>
</PUI.Dialog.Primitive.root>

Use normal HTML for custom static controls such as buttons, alerts, cards, tables, empty states, and native accordions.

For a Select, keyboard navigation keeps DOM focus on the listbox and marks its current option with data-active="true"; style that attribute. Dropdown items are native buttons or links, so give them w-full when each should fill the menu width and style aria-selected with the same surface used for hover.