Component
Headless web components
@getdom/studio/headlessBehaviour shipped as plain custom elements — works in any HTML, in any framework, with zero JavaScript runtime beyond the controls themselves.
Why
The headless layer
Every interactive component in this library starts as a custom element — <dom-dropdown>, <dom-dialog>, <dom-popover> and so on. They carry the behavioural contract (state, ARIA wiring, keyboard handling, event emission) but no visual styling. Drop them into a plain HTML page, a Rails view, a Django template, or wrap them in any framework. The Vue components in this library are thin wrappers around exactly these controls.
- ✓ ~2kb gzipped per control, SSR-safe (guards
customElements) - ✓ Light DOM by default — Tailwind classes cascade in normally
- ✓ ARIA + roving tabindex + focus management built in
- ✓ Events use the
dom:*convention with bubbling
Install
One import, all controls
<!-- Register every <dom-*> custom element. One import is enough — they
ship as ES modules and self-register via customElements.define. -->
<script type="module">
import '@getdom/studio/headless';
</script>
<!-- Or pick what you need: -->
<script type="module">
import '@getdom/studio/headless/dropdown.js';
import '@getdom/studio/headless/dialog.js';
</script>Floating UI
Dropdowns and popovers without the usual traps
The shared floating.js engine and <dom-popover> panel solve the failure modes that usually make dropdowns expensive: clipped parents, transformed ancestors, visual viewport offsets, side collisions, scroll containers, long menus, arrow placement, and resize reflow. Use the headless popover, dropdown, combobox, autocomplete, and tooltip controls first; drop to the low-level helper only for custom surfaces.
Reference
Every control at a glance
<dom-dropdown>→A menu that opens from a button. The menu panel uses the same native Popover API as dom-popover (top layer, light-dismiss, Esc), plus menu keyboard navigation and dom:select.
- Attrs
- open, placement, align, offset, collision-padding, floating-mode, lock-scroll, data-menu-id
- Events
- dom:open · dom:close · dom:select
<dom-dialog>→Self-contained wrapper around the native HTML <dialog>. Top-layer rendering, nested stacking, ::backdrop and Esc-to-close are all native. Trigger slot is optional — open via commandfor, the open attribute, or .open() / .close() / .toggle().
- Attrs
- id, open, static, no-backdrop
- Events
- dom:open · dom:close
<dom-popover>→Floating panel anchored to a trigger. Built on the native HTML Popover API — top layer, light-dismiss and Esc are handled by the browser.
- Attrs
- open, placement, align, offset, collision-padding, floating-mode, flip, lock-scroll, trigger, hover-close-delay, data-trigger-id, data-panel-id
- Events
- dom:open · dom:close
<dom-tabs>→Tab list with managed ARIA, roving tabindex and panel linking via data-tab / data-panel pairs.
- Attrs
- value
- Events
- dom:change
<dom-toggle>→Accessible switch with role="switch", keyboard support, and aria-checked maintained as you toggle.
- Attrs
- checked, disabled, aria-label
- Events
- dom:change
<dom-tooltip>→Lightweight tooltip wired through aria-describedby. The bubble appears on hover and focus, and is injected as a sibling element.
- Attrs
- text, placement, delay
- Events
- dom:show · dom:hide
<dom-accordion>→Disclosure-panel group containing one or more <dom-accordion-item> children.
- Attrs
- multiple
- Events
- —
<dom-combobox>→Select-like combobox with a text input, optional toggle button, managed activedescendant, keyboard navigation, and floating list positioning.
- Attrs
- value, open, placement, floating-mode, data-menu-id
- Events
- dom:input · dom:query · dom:select · dom:change
<dom-autocomplete>→Free-text autocomplete. Suggestions are optional; typed values are valid and Enter commits the current text.
- Attrs
- value, open, placement, floating-mode, data-menu-id
- Events
- dom:input · dom:query · dom:select · dom:custom · dom:change
<dom-drawer>→Light-DOM drawer primitive anchored to the left, right, or bottom edge. It owns open state, ARIA, focus trap, scroll lock, Esc/backdrop dismiss, and events while keeping panel/backdrop styling in editable Tailwind classes.
- Attrs
- open, show, side, static, enter, enter-from, enter-to, leave, leave-from, leave-to
- Events
- dom:open · dom:close
Cards reflect each class's static __doc. Click for the full slots / attributes / events / keyboard reference.
Events
The dom:* convention
Every control emits dom:open / dom:close when it shows or hides, plus an component-specific verb (dom:select, dom:change, dom:show) for the action it mediates. All events bubble and use the detail field for data:
document.addEventListener('dom:select', (e) => {
console.log(e.target.tagName, '→', e.detail.value);
});Teleport / portals
External popups
When a popup needs to escape its host (e.g. you teleport a menu to document.body to dodge an ancestor with overflow: hidden), set data-menu-id on <dom-dropdown> or <dom-combobox>, and data-panel-id on <dom-popover>. The headless re-resolves the popup from document.getElementById on every open.