ValyroUI Русская версия

Headless hooks

valyro-ui/headless: the behaviour hooks the components are built on — focus, keyboard, floating layers, dismiss — without markup or CSS, with live examples.

import { useDisclosure, useDismissableLayer, useFloating } from 'valyro-ui/headless';

State

useControllableState

useControllableState({ value, defaultValue, onChange }) → [value, setValue]

A value that works controlled (value + onChange) or uncontrolled (defaultValue); setValue takes a value or an updater.

useDisclosure

useDisclosure({ open?, defaultOpen?, onOpenChange? }) → { open, setOpen, onOpen, onClose, onToggle }

Open / closed state of a dialog, drawer, popover or menu, controlled or not.

Keyboard and focus

useListNavigation

useListNavigation({ count, isDisabled?, getLabel?, locale?, onSelect?, loop?, orientation?, initialIndex? }) → { activeIndex, setActiveIndex, onKeyDown }

Menu and listbox keys: arrows, Home / End, Enter / Space and type-ahead (with getLabel, compared with the locale collator: "e" finds "Éclair"). Focus stays on the container; mark the active row with aria-activedescendant.

stepIndex

stepIndex(count, from, delta, isDisabled?, loop?) → index

The next enabled index in a direction, skipping disabled ones.

useFocusTrap

useFocusTrap(ref, active, { initialFocusRef?, restoreFocus?, returnFocusRef?, contain? })

Keeps Tab inside ref while active, focuses the first tabbable (or initialFocusRef) and gives focus back on release; if that element is gone, to returnFocusRef or the nearest place it was. contain (modals) also brings back focus that lands outside by other means (a screen reader, a click). Traps stack.

getTabbables

getTabbables(container) → HTMLElement[]

The elements Tab visits inside a container, in order.

useDismissableLayer

useDismissableLayer({ enabled, refs, onDismiss, closeOnEscape?, closeOnOutside?, passThrough? })

Escape and outside presses for a layer. Open layers form one stack: Escape closes the topmost, an outside press the ones it lands outside of.

DISMISS_IGNORE_ATTRIBUTE

'data-ui-dismiss-ignore'

Presses inside an element with this attribute dismiss no layer (toasts, floating toolbars).

useScrollLock

useScrollLock(active)

Locks page scroll while active, without a layout shift; ref-counted for nested modals.

ariaHideOutside

ariaHideOutside(targets, root = document.body) → undo

Hides everything outside targets from screen readers with aria-hidden, as React Aria does for modals: aria-modal alone does not stop every virtual cursor. Keeps the live announcer, toasts and layers opened meanwhile readable; nests.

useAriaHideOutside

useAriaHideOutside(refs, active)

ariaHideOutside for the elements in refs while active.

useTypeahead

useTypeahead(locale?, timeout?) → { type(event), isTyping(), matches(label, query) }

The type-ahead buffer of one widget: keys typed in a row build one query ("new y" → "New York"); while typing, Space belongs to the query.

createPrefixMatcher

createPrefixMatcher(locale?) → (label, query) => boolean

Whether a label starts with a query, ignoring case and accents for the locale ("e" finds "Éclair").

createContainsMatcher

createContainsMatcher(locale?) → (text, query) => boolean

Whether a text contains a query anywhere, ignoring case and accents for the locale ("resume" finds "Résumé"): the filter of the comboboxes and command palette.

findTypeaheadIndex

findTypeaheadIndex(count, query, activeIndex, getLabel, matches, isDisabled?) → index | -1

The item a query selects: a repeated letter cycles through the items starting with it, a longer query refines the match.

Screen-reader announcements

announce

announce(message, assertiveness = 'assertive', timeout?)

Reads a change that has no visible text of its own (a new sort order, a drop target, a result count) through one hidden live region; plain DOM, callable from any handler.

clearAnnouncer

clearAnnouncer(assertiveness?)

Drops pending messages, e.g. when what they describe is gone.

destroyAnnouncer

destroyAnnouncer()

Removes the live region from the page (tests, unmounting micro-frontends); the next announce creates it again.

LIVE_ANNOUNCER_ATTRIBUTE

'data-live-announcer'

On the announcer root, so layers that hide the page from screen readers can leave it readable.

useAnnounceCount

useAnnounceCount(count, { active, message, key?, delay? })

Reads how many results a filtered list holds once typing pauses ("5 results available"), as React Aria's combobox does; skips zero, which the empty state says itself.

Layers and positioning

useFloating

useFloating({ open, anchorRef, floatingRef, placement?, offset?, padding?, matchAnchorWidth? }) → { floatingStyles, side, align, update }

Places a position: fixed panel next to an anchor (an element or a virtual one with getBoundingClientRect), flips and shifts it to stay on screen, follows scroll and resize. Sets --ui-available-height and the arrow offsets.

computePosition

computePosition({ anchor, floating, viewport, placement, offset, padding, rtl? }) → { x, y, side, align, … }

The pure geometry behind useFloating, for canvases and tests.

parsePlacement

parsePlacement('bottom-start') → ['bottom', 'start']

Splits a placement into side and alignment.

usePresence

usePresence(open, exitMs?) → { isMounted, state: 'open' | 'closed' }

Keeps an element mounted while its exit animation plays; animate [data-state="closed"].

Portal

<Portal container?>

Renders into the nearest ThemeScope’s layer container (so the layer is themed like its page), else document.body; nothing on the server.

useIsClient

useIsClient() → boolean

False during SSR and hydration, true after: client-only markup without hydration mismatches.

usePortalContainer

usePortalContainer() → Element | null

The element Portal renders into.

Slot

<Slot {...props}>{child}</Slot>

Merges its props into its single child: the asChild pattern. Handlers both run (the child’s first; yours is skipped after its preventDefault()), classes join, styles and refs merge.

mergeProps

mergeProps(slotProps, childProps) → props

The merge Slot does, for your own components.

Lists and motion

useVirtualizer

useVirtualizer({ count, scrollRef, estimateSize, getItemKey?, horizontal?, overscan?, gap?, … }) → { items, totalSize, range, scrollToIndex, measureElement, … }

Renders only the visible rows of a long list: fixed or measured sizes, horizontal lists, scroll-to-index, scroll anchoring on prepend, SSR.

useFlip

useFlip(containerRef, { spring?, enabled?, animateEnter?, auto? }) → capture

FLIP layout animation: call capture() before a change; elements with data-flip-id slide to their new place on a spring. Off under reduced motion.

flipDelta

flipDelta(first, last) → { x, y, scaleX, scaleY } | null

How far a box moved between two measurements.

useSpringValue

useSpringValue(target, onFrame, { spring?, tempoRef?, enabled? })

Springs a number towards target and writes each frame through onFrame, without a React render per frame.

useNow

useNow(intervalMs?) → number

The current time, re-rendering every intervalMs from one timer shared by every caller of the interval.

subscribeNow

subscribeNow(intervalMs, listener) → unsubscribe

The same shared clock outside React.

getNow

getNow(intervalMs) → number

The time of that clock, stable within a render pass.

Components without markup

useAccordion

useAccordion({ type?, collapsible?, value?, defaultValue?, onValueChange? }) → { value, isOpen, toggle, getRootProps, getItemProps, getTriggerProps, getPanelProps }

The Accordion kit component renders with this hook: open sections (single or multiple), trigger ↔ panel ids, arrows / Home / End between triggers. Each getter takes the section and your props and merges them (your handler first).

useTreeView

useTreeView({ nodes, expanded?, defaultExpanded?, selected?, defaultSelected?, … }) → { visible, isExpanded, setExpanded, activate, focus, getTreeProps, getItemProps, getRowProps, getGroupProps }

The TreeView kit component renders with this hook: expanded and selected nodes, a roving tab stop and the ARIA tree keys (arrows, also under RTL, Home / End, Enter / Space, type-ahead).

useDataTable

useDataTable({ data, getRowId?, getValue?, getSearchText?, sort?, selected?, query?, pageSize?, page?, … }) → { rows, results, toggleSort, setQuery, setPage, toggleRow, toggleAll, getColumnProps, getSortProps, getRowProps, getRowCheckboxProps, getSelectAllProps, getSearchProps }

The row model of DataTable: search, a sort that cycles ascending → descending → off, pages and row selection, each controlled or not. Render it as a table, a list or a grid of cards.

useListbox

useListbox({ items, multiple?, value?, defaultValue?, onValueChange?, focusable?, selectionBehavior?, … }) → { options, value, activeIndex, select, selectRange, selectAll, onKeyDown, getListboxProps, getGroupProps, getGroupLabelProps, getOptionProps }

The option list of Listbox, Select, MultiSelect and Combobox: options flat or in groups, one or several selected, the highlighted option and the listbox keys. Focusable on its own, or driven by a trigger or an input (focusable: false). A focusable multiple list selects ranges as React Aria does: Shift+click, Shift+arrows / Home / End / Page keys, Shift+Space, Ctrl/Cmd+A, Escape to clear; selectionBehavior: 'replace' selects like a file list.

useSelect

useSelect({ items, multiple?, value?, open?, searchable?, floating?, native?, … }) → { value, selected, open, show, close, listbox, getTriggerProps, getPopupProps, getSearchProps, getListboxProps, getOptionProps, getNativeSelectProps }

Select and MultiSelect without markup: a button trigger (select-only combobox) that opens a listbox, letters jump to an option, Alt+↑ picks and closes, an optional filter box. With floating it positions the popup and closes it on an outside press or Escape. With native the field is a <select> (getNativeSelectProps): the phone's own picker, form validation and autofill.

useCombobox

useCombobox({ items, value?, inputValue?, filter?, allowCustomValue?, selectionMode?, onSelect?, … }) → { value, inputValue, open, search, listbox, getInputProps, getFieldProps, getToggleButtonProps, getPopupProps, getListboxProps, getOptionProps }

Combobox without markup: typing filters and opens the list, the highlight follows its option when results change, leaving the field resolves the text. selectionMode: "none" turns it into a command menu: CommandPalette renders with it.

useRadioGroup

useRadioGroup({ items, value?, defaultValue?, onValueChange?, disabled?, required? }) → { value, getGroupProps, getInputProps, getRadioProps, getDescriptionId }

RadioGroup and SegmentedControl without markup: native radios (getInputProps) or any element as a radio (getRadioProps) with a roving tab stop, every arrow key and selection on focus.

useCheckboxGroup

useCheckboxGroup({ items, value?, defaultValue?, onValueChange?, disabled?, readOnly?, required?, invalid?, name? }) → { value, isChecked, toggle, getGroupProps, getInputProps, getDescriptionId }

CheckboxGroup without markup: the checked values of native checkboxes in a group. required means at least one: while none is checked every box is required, so the form does not submit; invalid, disabled and readOnly reach every box.

useField

useField({ id?, label?, description?, invalid?, required?, counter? }) → { id, context, getLabelProps, getControlProps, getDescriptionProps, getErrorProps, getCounterProps }

The wiring of FormField: one id per part, the label pointing at the control, its aria-describedby (description or error, counter), aria-invalid and aria-required. context is what useFormField reads inside a FormField.

useTabs

useTabs({ tabs, value?, defaultValue?, onValueChange?, orientation?, activation? }) → { value, getListProps, getTabProps, getPanelProps }

Tabs without markup: tab ↔ panel ids, a roving tab stop, ←/→ (mirrored under RTL) or ↑/↓ when vertical, Home / End, and automatic or manual activation.

useMenu

useMenu({ items, open?, onClose?, onBack?, defaultHighlight?, floating?, … }) → { open, show, close, entries, activeIndex, submenu, getTriggerProps, getTargetProps, getMenuProps, getItemProps, getSubmenuOptions }

One menu level shared by DropdownMenu and ContextMenu: actions, checkboxes and radio rows, type-ahead, submenus (another useMenu with getSubmenuOptions), a menu button or a context target (right-click, long-press, Shift+F10).

usePagination

usePagination({ pageCount, value?, defaultValue?, onValueChange?, siblings?, boundaries? }) → { page, range, setPage, getListProps, getPreviousProps, getNextProps, getPageProps }

Pagination without markup: the page range with ellipses (getPaginationRange, a pure function), previous / next, arrow keys between the buttons, focus kept when a step button is disabled.

getPaginationRange

getPaginationRange({ page, pageCount, siblings?, boundaries? }) → (number | "start-ellipsis" | "end-ellipsis")[]

The page numbers and ellipses to show; constant length once collapsing starts, so the control never changes width.

useDialog

useDialog({ open?, defaultOpen?, onOpenChange?, role?, modal?, layer? }) → { open, setOpen, titleId, descriptionId, getTriggerProps, getCloseProps, getDialogProps, getTitleProps, getDescriptionProps }

Dialog and Drawer without markup: open state, the title and description ids that name the dialog, the dialog-button ARIA, and with layer the modal layer itself: focus trap, scroll lock, Escape and outside press.

useTooltip

useTooltip({ open?, defaultOpen?, onOpenChange?, delay?, disabled?, floating?, placement? }) → { visible, show, hide, getTriggerProps, getTooltipProps }

Tooltip without markup: hover opens after a delay and keyboard focus at once, a grace period to reach the tooltip (WCAG 1.4.13), Escape, one tooltip at a time and a shared warm-up, aria-describedby on the trigger.

useResizablePanels

useResizablePanels({ panels, direction?, layout?, onLayout?, autoSaveId?, keyboardStep? }) → { layout, collapse, expand, resize, getGroupProps, getPanelProps, getHandleProps }

ResizablePanels without markup: sizes in percent with pixel limits, collapsing, a saved layout, and the window-splitter handle (drag, arrows, Home / End, Enter, double click).

useDateField

useDateField({ value?, defaultValue?, onValueChange?, locale?, minDate?, maxDate?, … }) → { value, setValue, outOfRange, segments, getGroupProps, focusFirst }

The DateField kit component renders with this hook: day, month and year as spinbutton segments in the locale's order (Intl formatToParts), typed digit by digit or stepped with the arrows, as React Aria's DateField. Render each of segments as a span with its props.

useTimeField

useTimeField({ value?, defaultValue?, onValueChange?, hourCycle, locale?, … }) → { value, setValue, segments, getGroupProps, focusFirst }

The same for a time: hour, minute and, on a 12-hour clock, AM/PM; the value is 24-hour "HH:mm".

useCheckboxTree

useCheckboxTree({ nodes, value?, defaultValue?, onValueChange?, expanded?, defaultExpanded?, … }) → { value, visible, getCheckState, toggle, isExpanded, setExpanded, focus, getTreeProps, getItemProps, getRowProps, getToggleProps, getGroupProps }

The CheckboxTree kit component renders with this hook: checked leaves, branches checked, mixed or unchecked from them, a branch that toggles its enabled leaves, expanded branches, a roving tab stop and the tree keys (arrows, also under RTL, Home / End, Space / Enter).

useStepper

useStepper({ steps, value?, defaultValue?, onValueChange?, clickable? }) → { value, setValue, getStepState, isClickable, getStepProps, getTriggerProps }

Stepper without markup: each step complete, current, upcoming or failed, aria-current="step", and with clickable the passed steps as buttons that go back; the step gone back to keeps the focus.

useCalendar

useCalendar({ mode?, value?, defaultValue?, onValueChange?, month?, numberOfMonths?, locale?, weekStartsOn?, minDate?, maxDate?, … }) → { value, months, weekdays, getWeeks, getDayState, select, shiftMonth, getRootProps, getPreviousProps, getNextProps, getCaptionProps, getGridProps, getWeekdayProps, getCellProps, getDayProps }

The Calendar kit component renders with this hook: one date or a range (the end previewed under the pointer, both picks read out), months side by side, the week from the locale's first day, min / max / disabled days and the date-grid keys (arrows, PageUp / PageDown, Home / End). A new month sets data-turn on the root for a CSS page turn.

useSlider

useSlider({ value?, defaultValue?, onValueChange?, min?, max?, step?, disabled?, formatValue?, name?, … }) → { values, isRange, dragging, setThumbValue, getRootProps, getTrackProps, getRangeProps, getThumbProps, getInputProps, getThumbLabelProps, getHiddenInputProps }

Slider without markup: one value or a range whose thumbs cannot pass each other, a press that grabs the nearest thumb, the keys (arrows mirrored under RTL, PageUp / PageDown, Home / End) and a visually hidden native range input per thumb, so VoiceOver and TalkBack swipes change it.

useSortable

useSortable({ items?, defaultItems?, onReorder?, getKey?, layout?, handle?, isItemDisabled?, getItemLabel?, labels? }) → { shown, grabbed, overlayItem, lift, drop, cancel, move, getMoveActions, getListProps, getItemProps, getHandleProps, getStartButtonProps, getOverlayProps, getInstructionsProps }

Sortable without markup: a pointer drag with a copy that follows the pointer (press-and-hold on touch), Space / arrows / Enter / Escape from the keyboard, a start button and move buttons for screen readers and a single pointer (WCAG 2.5.7), every step read out, and items gliding to their new place.

useCarousel

useCarousel({ count, slidesPerView?, gap?, index?, defaultIndex?, onIndexChange?, loop?, autoplay?, align? }) → { index, pages, goTo, previous, next, rotating, togglePlay, getRootProps, getTrackProps, getSlideProps, getPreviousProps, getNextProps, getRotationProps, getIndicatorsProps, getIndicatorProps, getLiveRegionProps }

Carousel without markup (APG carousel): a scroll-snap track whose page follows swipes and scrolling, previous / next, keys on the track, a mouse drag with a flick, dots or thumbnails, autoplay that pauses on hover, focus and reduced motion, and a polite live region.

usePopover

usePopover({ open?, defaultOpen?, onOpenChange?, floating?, placement?, offset? }) → { open, setOpen, triggerRef, contentRef, getTriggerProps, getContentProps }

Popover without markup: a trigger that toggles a non-modal dialog named by the trigger unless it has a name of its own, focus moved in on open and back on close, Tab past the panel continuing from the trigger's place in the page. With floating it positions the panel and closes it on an outside press or Escape.

useNumberInput

useNumberInput({ value?, defaultValue?, onValueChange?, min?, max?, step?, precision?, locale?, formatOptions?, allowWheel?, … }) → { value, inputValue, increment, decrement, getInputProps, getIncrementProps, getDecrementProps, getHiddenInputProps }

NumberInput without markup: a spinbutton in the locale format, typed text kept as a draft and committed on blur or Enter (clamped and rounded), ↑ / ↓ (Shift ×10), PageUp / PageDown, Home / End, the wheel while focused, and step buttons that repeat faster while held.

Refs, events and direction

mergeRefs

mergeRefs(...refs) → ref callback

One ref for several: object refs and callbacks, cleanups included.

composeHandlers

composeHandlers(theirs, ours) → handler

Calls the user’s handler first and skips yours when it called preventDefault().

useLatest

useLatest(value) → ref

A ref that always holds the latest value, for effects and stable callbacks.

useEventCallback

useEventCallback(fn) → fn

A stable function that always calls the latest implementation.

cx

cx('a', cond && 'b', undefined) → 'a b'

Joins class names, skipping falsy values.

useDirection

useDirection(ref) → 'ltr' | 'rtl'

The writing direction at an element, re-read after each render.

getDirection

getDirection(element) → 'ltr' | 'rtl'

The same, read once from CSS (follows dir on any ancestor).

inlineArrowStep

inlineArrowStep(key, direction) → -1 | 0 | 1

ArrowLeft / ArrowRight as a step along the inline axis, mirrored in RTL.

isComposingKey

isComposingKey(event) → boolean

True while an IME composes (Japanese, Chinese, Korean input): leave Enter and arrows to it.

clamp

clamp(value, min, max) → number

Keeps a number within a range.

React 17–19 helpers

useId

useId() → string

React’s useId on 18+; on React 17 an id assigned right after mount (none in server HTML, so hydration matches).

useSyncExternalStore

useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

React’s on 18+, an equivalent from state and effects on 17.

useIsomorphicLayoutEffect

useIsomorphicLayoutEffect(effect, deps)

useLayoutEffect in the browser, useEffect on the server (no SSR warning).

withRef

withRef(render, statics?)

ref as a prop on every version: the render itself on React 19, forwardRef before. statics become compound parts (Card.Header).

cleanupRef

cleanupRef(node => cleanup) → ref callback

A ref callback that returns a cleanup, the React 19 way, also on 17 and 18.

sharedCleanupRef

sharedCleanupRef(node => cleanup) → ref callback

cleanupRef for one stable callback many elements share (ref={measure} on every row).

getElementRef

getElementRef(element) → ref

The ref of an element: props.ref on React 19, element.ref before.

startTransition

startTransition(scope)

React’s on 18+; runs the update at once on 17.

useTransition

useTransition() → [isPending, startTransition]

React’s on 18+; never pending on 17.

useDeferredValue

useDeferredValue(value) → value

React’s on 18+; the value itself on 17.