Plugins & Interactions
Oxide UI uses Headless UI v1 for Vue as its interaction layer — the same library made by Tailwind Labs. It handles all ARIA, keyboard navigation, and focus management so you don't have to.
For patterns that Headless UI doesn't cover (file upload, PIN input, etc.), lightweight custom Vue composables are provided instead.
Headless UI reference
| Headless UI component | Oxide UI page | Preline equivalent |
|---|---|---|
Dialog | Modal | Overlay |
Menu | Dropdown | Dropdown |
Disclosure | Accordion | Accordion, Collapse |
Tabs | Tabs | Tabs |
Switch | Toggle | — |
Listbox | Select | Advanced Select |
Combobox | Select → Combobox | ComboBox |
Popover | Popover | Tooltip & Popover |
RadioGroup | Radio Group | — |
Transition | Used internally in all animated components | — |
Why Headless UI over Preline plugins?
Preline uses a data-attribute (hs-dropdown, hs-overlay, etc.) JavaScript plugin that scans the DOM on load. This works fine for vanilla HTML but creates friction in Vue/Nuxt because:
- It runs outside Vue's reactivity system
- It requires
window.HSStaticMethods.autoInit()after every Vue navigation - It conflicts with Vue's virtual DOM diffing
- It doesn't integrate with Vue transitions or
<Teleport>
Headless UI is renderless Vue components — they plug directly into Vue's reactivity, work with <Transition>, and generate correct ARIA automatically.
Custom implementations (no Headless UI equivalent)
These patterns require small custom implementations:
| Pattern | Approach |
|---|---|
| Stepper | Custom ref + step array, see backlog |
| File upload (drag & drop) | Native drag events + input[type=file] |
| PIN input | Array of single-char inputs + auto-focus logic |
| Strong password indicator | computed score from zxcvbn or custom regex |
| Toggle password visibility | ref toggling type="text" / type="password" |
| Textarea autoheight | @input event + el.style.height = el.scrollHeight + 'px' |
| Carousel | Custom ref index + CSS translate or @vueuse/core useScroll |
| Scrollspy | IntersectionObserver + active section tracking |
| Copy to clipboard | navigator.clipboard.writeText() + toast feedback |
| Datatables | TanStack Table for Vue |
Installing Headless UI
# npm
npm install @headlessui/vue
# pnpm
pnpm add @headlessui/vue
# yarn
yarn add @headlessui/vueIn Nuxt, no plugin registration is needed — import components directly:
<script setup>
import { Dialog, DialogPanel, DialogTitle } from '@headlessui/vue'
</script>Keyboard navigation (built-in)
All Headless UI components handle these automatically — no extra code required:
| Component | Keys |
|---|---|
| Menu (Dropdown) | ↑ ↓ to navigate, Enter to select, Esc to close |
| Dialog (Modal) | Esc to close, focus trapped inside |
| Disclosure (Accordion) | Enter / Space to toggle |
| Tabs | ← → to switch panels |
| Combobox | ↑ ↓ to navigate options, Enter to select, Esc to close |
| Switch (Toggle) | Enter / Space to toggle |
| Listbox (Select) | ↑ ↓ to navigate, Enter to select |