Fluixi UI
GitHub ↗

primitives

Headless behaviour managers: focus-scope, floating, roving-focus, virtualizer and more.


Layer 2 of the Fluixi headless UI ecosystem: reusable behavioral primitives that components compose. They contain no markup and render nothing.

Each primitive is a manager — a reactive function that receives an element accessor, wires behavior (events, focus, registration, lifecycle), and returns a disposer. Behavior is deliberately decoupled from rendering, so a primitive can power any component regardless of how it is marked up. Managers run inside a reactive owner and also self-clean via onCleanup.

Built on @fluixi/core (reactivity) and @fluixi/kit (Layer 1 utilities).

Overlay foundation

This release covers the primitives that the overlay components — Dialog, Popover, Tooltip, Menu, Select, Combobox, Drawer, HoverCard — are built from.

Primitive Import Responsibility
layerStack @fluixi/primitives/layer-stack global registry of open layers; topmost/nesting queries
createEscapeKeyDown @fluixi/primitives/escape-key document-level Escape handling
createInteractOutside @fluixi/primitives/outside-interaction pointer/focus interaction outside an element
createDismissableLayer @fluixi/primitives/dismissable-layer composes the three above into layer-aware dismissal
createPresence @fluixi/primitives/presence keeps an element mounted through its CSS exit animation
lockScroll / createScrollLock @fluixi/primitives/scroll-lock reference-counted body scroll lock
createFocusScope @fluixi/primitives/focus-scope focus trapping, auto-focus and focus restoration

Navigation family

The primitives that power list and menu widgets — Menu, Listbox, Select, Combobox, Tabs, RadioGroup, Toolbar, Accordion.

Primitive Import Responsibility
getNavigationAction / getNextEnabledIndex @fluixi/primitives/keyboard-navigation pure key→action mapping and disabled-aware index math
createCollection @fluixi/primitives/collection reactive registry of items kept in document order
createRovingFocusGroup @fluixi/primitives/roving-focus roving tabindex + arrow navigation
createTypeahead @fluixi/primitives/typeahead type-to-search buffer with ARIA conventions
createSelectionManager @fluixi/primitives/selection-manager single/multiple selection state

createRovingFocusGroup composes createCollection (ordering) with the pure keyboard-navigation helpers (index math), and createSelectionManager builds on @fluixi/kit's createControllableSignal so selection can be controlled or uncontrolled with one API.

import { createRovingFocusGroup } from '@fluixi/primitives/roving-focus';

const group = createRovingFocusGroup({ orientation: () => 'vertical', loop: () => true });
// each item: group.collection.register({ ref: el }); tabindex = group.tabIndexFor(el)
// on the container: onKeyDown={group.onKeyDown}

Composition

createDismissableLayer is the worked example of composition over duplication: it registers the element on the layerStack, then reuses createEscapeKeyDown and createInteractOutside — dismissing only when the element is the topmost layer, and never when a handler calls preventDefault(). The same building blocks are independently useful: a Popover can use createInteractOutside without full layer semantics; any widget can use createEscapeKeyDown.

import { createDismissableLayer } from '@fluixi/primitives/dismissable-layer';

createDismissableLayer(() => contentEl(), {
  onDismiss: () => setOpen(false),
  disableOutsidePointerEvents: true,
});

Interaction family

Pointer and keyboard interaction managers that attach to an element and expose reactive state.

Primitive Import Responsibility
createPress @fluixi/primitives/press unified pointer + keyboard press with isPressed state
createHover @fluixi/primitives/hover fine-pointer hover tracking (ignores touch)
createLongPress @fluixi/primitives/long-press threshold-based long press

Observers, forms and positioning

Primitive Import Responsibility
createResizeObserver @fluixi/primitives/resize-observer reactive ResizeObserver binding
createIntersectionObserver / createIsIntersecting @fluixi/primitives/intersection-observer reactive IntersectionObserver binding
createFormControl @fluixi/primitives/form-control field id/label/description/error wiring and ARIA/validation state
visuallyHiddenProps @fluixi/primitives/visually-hidden spreadable screen-reader-only styles
Portal @fluixi/primitives/portal re-export of the framework Portal
createFloatingPosition @fluixi/primitives/floating-layer reactive anchored positioning (wraps @floating-ui/dom)

createFormControl is the wiring layer for fields — it generates ids, computes aria-describedby from registered description/error elements, and exposes validation state. It is intentionally independent of any form-state engine; the component layer bridges external field state (e.g. @fluixi/forms) into it.

createFloatingPosition is the one primitive with an external dependency: @floating-ui/dom, the standard positioning engine. It keeps a floating element aligned to its reference via autoUpdate and exposes the resolved coordinates, placement and arrow offsets as signals; applying them to the DOM is left to the component.

Accessibility

These primitives implement the behavior WAI-ARIA dialog and popup patterns require: focus is trapped within modal surfaces and restored to the trigger on close, Escape dismisses the topmost layer, and outside interaction closes transient surfaces. createPresence ensures exit animations complete before an element leaves the accessibility tree.

Performance

  • Side-effect free ("sideEffects": false) with per-primitive subpath exports for precise tree-shaking.
  • Listeners are attached once per manager and removed on disposal; the layer stack is a single shared array rather than per-instance machinery.
  • SSR-safe: scroll locking and DOM access no-op on the server.

Extension points

Every manager accepts an ownerDocument for multi-window / portal scenarios and exposes granular callbacks (onEscapeKeyDown, onPointerDownOutside, onFocusOutside) that can call preventDefault() to opt out of the default behavior. Components layer their own semantics on top without forking the primitive.


Part of the Fluixi UI component library. Made with ☕ by the Fluixi team.