@zag-js/focus-trap
Focus trap utility
Installation
yarn add @zag-js/focus-trap
# or
npm i @zag-js/focus-trapContribution
Yes please! See the contributing guidelines for details.
Licence
This project is licensed under the terms of the MIT license.
Focus trap utility
Focus trap utility
yarn add @zag-js/focus-trap
# or
npm i @zag-js/focus-trapYes please! See the contributing guidelines for details.
This project is licensed under the terms of the MIT license.
All notable changes to this project will be documented in this file.
For v0.x changelog, see the v0 branch
Date Picker: Fix crash in range date picker when typing end date first by adding null/undefined checks when
accessing date properties.
Image Cropper
Radio Group: Revert to using offsetLeft/offsetTop to fix indicator positioning in scrollable containers.
Tabs: Revert to using offsetLeft/offsetTop to fix indicator positioning in scrollable containers.
Tour
Image Cropper [New]: Initial release of image cropper state machine
Marquee [New]: Initial release of marquee component for continuously scrolling content
Angle Slider: Fix issue where clicking and dragging the angle-slider thumb from a non-center position causes unexpected value jumps. The thumb now maintains its relative position from the initial click point throughout the drag operation, providing more intuitive dragging behavior.
Slider: Fix issue where slider thumb offset changes dynamically during drag, causing unexpected value jumps. The thumb now maintains a constant offset from the pointer throughout the drag operation, matching the initial grab position.
Svelte: Refactor mergeProps to return the class values as an array, this delegates the resolution to Svelte's
native class handling, which uses clsx internally. This ensures proper support for conditional classes, arrays, and
objects.
Toast: Fix issue in Solid.js where toasts collapse immediately when dismissing while hovering, by tracking pointer state and temporarily ignoring spurious mouse events during DOM mutations using requestAnimationFrame.
Angle Slider: Fix accessibility violation where the slider thumb element lacked an accessible name. The thumb now
supports aria-label and aria-labelledby props, and automatically falls back to the label element's ID for proper
ARIA labeling.
Select: Fix accessibility violation where the required state was not set correctly to on the trigger.
Tags Input: Fix issue where entering a custom tag with combobox integration required pressing Enter twice. The
tags-input now correctly handles custom values when the combobox has no highlighted item (aria-activedescendant is
empty), allowing the tag to be added on the first Enter press.
Checkbox
indeterminate doesn't workapi.checkedState returns the correct checked state (boolean | "indeterminate")Collapsible: Fix issue where dir prop value was hardcoded to ltr instead of using the provided value
Combobox: Fix issue where controlled single-select combobox does not propagate its initial value to inputValue
Listbox: Fix issue where pressing Enter key when no highlighted item still calls event.preventDefault()
Radio Group: Refactor to use getBoundingClientRect() for precise indicator positioning
Slider
minStepsBetweenThumbsTabs: Refactor to use getBoundingClientRect() for precise indicator positioning
Tags Input: Fix issue where maxLength doesn't apply to the edit input as well
F2 on any tree node would lock navigation and prevent selecting other nodes.canRename callback to be explicitly provided, making it opt-in rather than
opt-out.Tree View: [Experimental] Add support for renaming tree node labels with validation and control features.
This feature enables users to edit tree node labels inline, unlocking use cases like file explorers, folder management systems, content hierarchies, and any tree-based interface where users need to rename items.
Key Features:
F2 on any node to enter rename modeEnter to submit or Escape to cancelValidation & Control:
canRename - Control which nodes are renameable based on node type or custom logiconRenameStart - Called when rename mode starts (useful for analytics, showing hints)onBeforeRename - Validate rename before accepting (e.g., prevent duplicates, empty names)onRenameComplete - Handle the rename and update your collectionStyling & Visual State:
data-renaming attribute - Added to both item and branch elements when in rename mode for easy stylingnodeState.renaming - Boolean property to check if a node is currently being renamedEditable: Fix issue where input value fails to revert after repeated full deletion
Focus Visible: Fix "Cannot assign to read only property 'focus'" console error by gracefully handling
environments where HTMLElement.prototype.focus is non-configurable.
Listbox: Fix splitProps to avoid partial
Presence: Fix race condition where dialog remains closed when open prop rapidly changes from true to false
to true
Solid: Fix issue where transition actions received stale event data
Collapsible: Add support for collapsedHeight and collapsedWidth props to control the dimensions of the
collapsible content when in its collapsed state.
Focus Trap: Allow elements referenced by aria-controls to be included in the trap scope. This makes it possible
for menus, popovers, etc. to be portalled and work correctly.
Pagination: Add getPageUrl prop for generating href attributes when using pagination as links.
const service = useMachine(pagination.machine, {
type: "link",
getPageUrl: ({ page, pageSize }) => `/products?page=${page}&size=${pageSize}`,
})splitMarkerProps helper.Scroll Area: Fix RTL horizontal scrollbar positioning on Safari
Slider: Fix issue where slider continues dragging when disabled during drag operation.
Switch: Fix issue where data-active is inconsistently applied when disabled state changes at runtime
Date Picker: Fix issue where year range picker doesn't show the hovered range
Date Utils:
I18n Utils:
unitSystem property to allow changing between decimal (1000 bytes) and binary (1024
bytes) systems.Number Input: When formatOptions is used (like style: "currency"), the cursor would jump to the end of the
input when typing in the middle. The cursor now maintains its relative position during formatting changes.
Pin Input: Fix issue where using the keyboard shortcuts Cmd+Backspace and Cmd+Delete to delete text in pin
inputs would insert "undefined" instead of clearing the field.
Scroll Area: Fix issue where resize tracking was not observing the root element, which caused the scrollbar to not update when the root element's size changed.
mergeProps throws when props is undefined or nullalwaysSubmitOnEnter prop to allow bypassing the default two-step behavior (Enter to close
combobox, then Enter to submit form) and instead submit the form immediately on Enter press. This is useful for
single-field autocomplete forms where Enter should submit the form directly.Editable: Allow text selection in editable preview when autoResize is enabled
Previously, when autoResize was set to true, the preview element had userSelect: "none" applied, preventing
users from selecting text. This has been fixed by removing the userSelect style property.
File Upload: Fix regression where clicking the trigger doesn't open the file picker when used within the dropzone
Hover Card: Change default delay values for hover card to improve accessibility.
openDelay: from 700ms to 600msMenu: Fix issue where keyboard activation of menu items with target="_blank" would open two tabs
Svelte: Fix Svelte warning about state reference capturing initial value instead of current reactive state
Tooltip: Change default delay values for tooltip to improve accessibility. Learn more
openDelay: from 1000ms to 400mscloseDelay: from 500ms to 150msdialog,
popover, menu, or listbox. This enables:data-nested attribute on nested layers of the same typedata-has-nested attribute on parent layers with nested children of the same type--nested-layer-count CSS variable indicating the number of nested layers of the same typeCore: Fix issue where mergeProps strips symbols after merging
Dom Query:
isActiveElement checks don't consider the Shadow DOMgetActiveElement returns activeElement rather than null for focusable web components with no
focusable childrenMenu: Fix issue where hovering a partially visible item with pointer causes it to scroll into view
Tabs: Fix issue where ids for item and content could not be customized
Toast: Allow creating a toast store without any arguments
// before
const store = toast.createStore({})
// after
const store = toast.createStore()Collection: Fix issue where disabled items could be reached via typeahead
Date Picker: Fix issue where datepicker doesn't revert to a valid value when the input value exceeds the min/max and blurred
Tags Input: Fix issue where highlighted item doesn't clear when tabbing out of the input to an external button
within the control part.
disabled propColor Picker: Fix issue where color picker was not working correctly in RTL mode
Dismissable: Expose onRequestDismiss custom event handler for event a parent layer requests the child layer to
dismiss. If prevented via event.preventDefault(), the child layer will not dismiss when the parent layer is
dismissed.
Number Input
pattern when formatOptions is provided. This prevents native pattern validation from conflicting
with formatted values (e.g., currency or percent).data-scrubbing attribute to the number input parts.Tooltip
closeOnPointerdown to false when closeOnClick is set to false@zag-js/store dependency with a lightweight store implementation.SortDetails interface with filterText parameter for consistent filtering context across local and
server-side operationsDate Picker:
min/max constraints.disabled on api.getMonths() and api.getYears() results to indicate options out of range for current
constraints.Listbox:
getElement to scrollToIndexFn detailshighlightedValue if the item is no longer in the collection.Scroll Area:
data-dragging attribute to scroll area parts.Select: Add getElement to scrollToIndexFn details
Combobox: Add getElement to scrollToIndexFn details
exactMatch option that enables whole-word matching using regex word boundaries.Menu: Fix context menu repositioning logic
Scroll Area: Add data-hover to scroll area
Menu: Fix context menu positioning bug where reopening at the same coordinates fails to reposition
Scroll Area: Rename data-hovering to data-hover for consistency
data-overflow-* to content and cornerScroll Area: Ensure types are exported and fix incorrect @zag-js/dom-query/src/query import
ListCollection
at() and indexOf()find() method (was checking != null instead of !== -1)GridCollection: Avoid recomputing rows on every call to getRows()
Menu: Add data-state attribute for context menu trigger
Async List: Improve type inference for descriptors
Framework Components: Improve runtime performance of components by removing refs/events from stateful to non-stateful objects (affects Svelte, Solid, and Vue components)
Carousel:
slideCount or autoplay props change.loop: false was ignored when using autoplay. Now, the carousel will stop when it gets to the
last slide.Date Picker: Expose data-inline attribute on Content part to enable distinct styling for inline date pickers
versus popover date pickers.
Menu: Fix issue where onCheckedChange could be called twice on checkbox or radio item
Radio Group: Fixed issue where arrow key navigation doesn't apply data-focus-visible on the newly focused item.
Carousel: Fix issue where controlled carousel ignores last slide
Tour: Re-expose WaitOptions
Floating Panel: Add data attributes for floating panel stage status
api.transforming to track file transformation state when using transformFiles. This enables
developers to show loading states during file processing.--layer-index with positioner and backdroptrapFocus from modal so it's possible to set modal=false and trapFocus=trueDate Picker: Fixed issue where hovered range was connect to selected values, when it shouldn't
Tree View: Fixed issue where tree view doesn't scroll into view when content overflows.
CalendarDate, CalendarDateTime, ZonedDateTime) are mixed,
particularly in scenarios involving time components.Date Picker: Added hover range preview support for date picker range selection. Added inHoveredRange,
firstInHoveredRange, and lastInHoveredRange properties to DayTableCellState with corresponding data attributes
data-in-hover-range, data-hover-range-start, and data-hover-range-end.
Hover range states are only active when not overlapping with actual selected range, enabling distinct styling for hover preview vs actual selection in range mode.
File Upload: Add support for programmatically controlling the accepted files via acceptedFiles and
defaultAcceptedFiles
Signature Pad: Add support for programmatically controlling the paths via paths and defaultPaths props.
groupArraysAfterLength to truncate large arrays into chunks (reducing the
number of DOM nodes rendered).General: Fix issue destructuring returned api could throw an ESLint unbound-method warning
Tree View: Fix issue where onExpandedChange, onSelectionChange and onFocusChange doesn't infer the tree node
types
Popper: Expose floatingElement to the updatePosition function
Collection: Fix issue where the filter method completely deletes the children key from the node when there are
no matching children
Number Input: Fix issue where default pattern does not allow negative numbers with decimal point
Carousel: Fix issue where full page carousel could trap scrolling
File Upload:
api.setFiles invokes validation with incorrect acceptedFilesupsert to list collection. Useful for making creatable items in select or comboboxfocusedValue could not be fully controlledtitle or description could not accept React or Vue elementsCombobox:
reason to onOpenChange and onInputValueChange callbacksapi.clearHighlightedValue function to clear highlighted valueDate Picker: Fix issue where datepicker errors when setting selectionMode=range and minView=year
Listbox: Select highlighted item only if it exists in the collection
Progress: Improve valueAsString formatting
Select:
api.clearHighlightedValue function to clear highlighted valueTour: Fix an issue where the goto function in StepActionMap doesn't work when passing step IDs (string)
Tree View: Expose id in the tree node state
inline prop to render color picker inlineinline prop to render the date calendar inlinerole="status" and output elements when applying aria-hidden# if missing when using the hex channel inputdefaultValue or value after fetching items doesn't
update the valueAsStringAngle Slider: Fix issue where scrubbing doesn't feel smooth on touch devices
Timer:
targetMs when window is not visiblestartMs and targetMs are configured correctlyprogressPercent calculation for countdown timersTree View: Expose node details in onExpandChange, onSelectionChange and onFocusChange
Collection:
findNodes to find multiple nodes by value in a single passgetLastNode not returning the last node in the tree with only one branchI18n Utils: Add new createCollator function for locale sensitive string comparison
Date Picker: Fix issue with keyboard selection where setting unavailable date causes month view to behave differently from clicking with mouse
Toast: Fix issue where app crashes when toaster.promise is called without loading option. The loading option
is now required. A warning will be logged if it is not provided
Tree View:
aria-busy attribute from branch trigger when not loading childrenvertical don't workdefaultValue to null doesn't show indeterminate stategetItemCheckboxProps to getNodeCheckboxProps since it can be used in both items and branchesTree View
defaultCheckedValue, checkedValue, onCheckedChange propsloadChildren fails via onLoadChildrenError propapi.getCheckedMap method to get the checked state of all nodesTree Collection: Add support for getDescendantNodes and getDescendantValues
api.collapse and api.deselect throws error when called without argumentsexactOptionalPropertyTypes compiler optionCollection: Improve the APIs around tree.flatten(...) and flattenedToTree to ensure the original node
properties are preserved.
Previously,
tree.flatten()would return an array of objects withvalueandlabelstripping out the original node properties.
const tree = new TreeCollection({
rootNode: {
value: "ROOT",
children: [{ value: "child1" }, { value: "child2" }],
},
})
const flattened = tree.flatten()
const reconstructed = flattenedToTree(flattened)
console.log(reconstructed.rootNode)
// {
// value: "ROOT",
// children: [{ value: "child1" }, { value: "child2" }],
// }allowCustomValue: true used within in a form requires two enter keypress
to submitonOpenChange could be called twice when controlleddownloadFile function to handle webview scenariosonInputValueChange could be called twice when selecting an itemgetItemTextProps and getItemIndicatorProps to accept a partial interface of option item.activationMode=nonecopy methodgetParentNodes to accept a value or index pathslidesPerPage is 0undefined in acceptedFiles when no files acceptedloadChildren is a function that is used to load the children of a node.onLoadChildrenComplete is a callback that is called when the children of a node are loaded. Used to update the
tree collection.childrenCount to the node object to indicate the number of children.function TreeAsync() {
const [collection, setCollection] = useState(initCollection)
const service = useMachine(tree.machine, {
id: useId(),
collection,
async loadChildren({ valuePath, signal }) {
const url = `/api/file-system/${valuePath.join("/")}`
const response = await fetch(url, { signal })
const data = await response.json()
return data.children
},
onLoadChildrenComplete({ collection }) {
setCollection(collection)
},
})
// ...
}Shift + ArrowRight set value to 0 instead of max when step is too large (e.g. 20)onValueChangeEnd doesn't return the latest value when dragging very fasttransformFiles context property.const service = useMachine(fileUpload.machine, {
id: useId(),
accept: ["image/jpeg", "image/png"],
transformFiles: async (files) => {
return Promise.all(files.map((file) => compress(file, { size: 200 })))
},
})minStepsBetweenThumbs isn't computed correctly when interacting with pointer or
keyboard.Api and Serviceapi.toggleVisible function to toggle the visibility of the password inputtranslations prop to customize the visibility trigger accessibility labelonSelect callback that gets fired when an item is selected via keyboard/mouse.Color Picker: Fix issue where value change end event is invoked when committing via an input.
Svelte: Fix issue with microtask timing in svelte 5.28.3+.
Toast: Fix issue where calling toast.remove() without an id shows a TypeScript error.
Carousel:
allowMouseDrag is set where carousel no longer snaps after mouse interactionCombobox: Fix issue where onInputValueChange doesn't get called when autoFocus is set to true
Focus Visible: Fix an issue where an assignment to the browser's HTMLElement prototype is not supported (e.g.
happy-dom)
Preact: Remove react and react-dom from peerDependencies
Slider: Fix issue where slider could throw a error when rendered in an popover or dialog
Svelte: Improve reactivity when events don't trigger a state transition
Tour: Fix issue where calling api.start(<id>) with a step id doesn't work as expected
navigate propCollection: Fix issue where getNextValue and getPreviousValue doesn't work as expected when groupBy is used.
Combobox:
href to params in navigate context propertyMenu, Tabs: Add href to params in navigate context property
outsideDaySelectable prop to allow selecting days outside the current month (on the
same visible date range)SelectionMode interfaceToggle Group: Add support for deselectable prop to ensure one or more toggle is selected at any time.
Splitter:
api.resetSizes() to reset the size to the initial specified sizeSelect: Fix issue where machine doesn't leave focus state when interacting outside with another editable element.
This leads to the data-focus attribute not being removed from the trigger element.
Floating Panel: Fix issue where clicking the trigger when panel is open, doesn't close the panel
absolute to fixed to improve positioning consistencyStageTriggerProps and AnchorPositionDetails was not exportedToast: Add support for queuing toasts that exceed the maximum limit. When the maximum number of toasts is reached:
Listbox
api.clearHighlightedValue function to clear the highlighted valuedata-empty attribute to indicate when the listbox is emptyCollection: Add filter function to collection methods
data-empty attribute to the listbox and content to indicate when the listbox is emptydefaultValue is set.Listbox
loopFocus was not set.extended selection mode was not working as expected.Dom Query: Improve platform detection logic to detect macOS and iOS correctly.
ListCollection instead of mutating
the internal itemsTabs: Fix issue where tabs indicator animation behaves inconsistently.
Date Picker
Interact Outside
Slider
origin: end to align the thumb to the end of the track.thumbSize as CSS variables in the root element. Can be useful for styling the slider.Menu: Fix issue where addItemListener doesn't work as expected.
thumbSize CSS variable is not applied.addItemListener function to allow listening for dispatched custom event on menu item.tabIndex to improve usage within dialogsmenu:select event on the menu item when it is selectedSplitter: Fix issue where onResizeStart and onResizeEnd callbacks are not called when using keyboard
Combobox: Fix issue where onOpenChange with the same open value
ColorStringFormat typedownloadFile where name is not considered in some casesNumber Input: Set the default step to 0.01 when formatOptions.style is set to percent
[Breaking] Splitter: Redesign splitter machine to support more use cases and improve DX.
Before:
const service = useMachine(splitter.machine, {
id: useId(),
defaultSize: [
{ id: "a", size: 50 },
{ id: "b", size: 50 },
],
})After:
const service = useMachine(splitter.machine, {
id: useId(),
panels: [{ id: "a" }, { id: "b" }],
defaultSize: [50, 50],
})The also comes with new features such as:
Toast: Fix issue where setting offsets to undefined causes machine to throw
Select: Fix issue where select valueAsString loses reactivity
api.setSizes to set the size of multiple panelsapi.getSize to get the size of a panelNumber Input: Fix issue where number input onValueChange is called with incorrect valueAsNumber
Slider: Fix issue where setting min to value other than 0 causes incorrect initial placement of thumbs
Combobox: Fix issue where cursor moves unexpected when editing input value
Tags Input: Improve caret detection logic to prevent unwanted tag removal
Timer
interval value from 250 to 1000Pin Input: Fix issue where OTP SMS autofill doesn't work as expected.
Date Picker: Fix issue where Svelte throws a state_unsafe_mutation error when controlling the datepicker range
mode.
Rating Group: Fix issue where rating group label places focus incorrectly.
React: Flush effects synchronously rather than within a microtask.
Checkbox: Fix issue where checkbox incorrectly sets data-invalid when invalid is false.
Radio Group, Switch: Improve focus behavior in Safari browser.
Accordion: Fix issue in Safari where clicking triggers doesn't show the content as expected.
Auto Resize:
maxHeight, overflowY, and boxSizing CSS properties.Date Picker:
api.getViewProps.visibleRangeText property to api.offset() return value.Progress: Add support for locale and formatOptions to properly format the api.percentAsString result.
Framework Adapters (React, Svelte, Solid, Vue): Support reenter:true in machine transitions.
Progress: Allow for more precise (decimal) values.
Scroll Snap: Fix issue where getScrollPadding could return NaN in test environments.
Date Picker: Fix issue where onValueChange doesn't get called when value is cleared.
Number Input: Fix issue where allowOverflow was not implemented.
Signature Pad
readOnly or onChange.getDataUrl in the onDrawEnd callback after clearing the signature pad does not return an
empty string.React: Improve Hot Module Replacement such that effects are replayed correctly. This removes the need to refresh the page for changes to take effect.
Toggle Group: Fix issue where calling api.setValue with an array doesn't work as expected.
Pin Input: Fix flushSync was called from inside a lifecycle method warning.
Vue: Fix issue where choose is not a function error could be thrown for some machines.
File Upload: Fix issue where win.DataTransfer could throw in testing environments.
Time Picker: Fix [@zag-js/dismissable] node is null or undefined warning when lazy mounting the content.
count prop to improve SSR aria-label attribute.Pin Input: Fix issue where editing existing values don't work as expected.
Number Input: Fix issue where value prop wasn't consumed in the machine.
Carousel: Fix issue where page was not consumed in the machine.
Textarea: Fix issue where ResizeObserver loop could throw undelivered notifications warning.
Toggle: Bring back toggle machine.
Hover Card: Expose interaction outside handlers to hover card.
Collection: Widen items type to allow Iterable instead of just Array since we internally convert iterables
to an array.
Carousel: Enforce required slideCount to ensure machine works as expected.
Framework Bindings: Fix issue where undefined values were not filtered out before resolving props.
React: Fix issue where flushSync warnings could be shown when unmounting a component.
Select: Fix regression where multiple: true doesn't work.
Timer: Fix issue where timer doesn't restart when startMs changes.
Toggle Group: Fix issue where data-focus doesn't get removed after blurring the toggle group.
Toast: Fix keyboard navigation issue where toast group skips the close button within the toast item and moves to the next focusable element in the document.
<component>.Machine type to help when typecasting generic components like combobox and select.Core: Rewrite machines for increased performance and initial mount time. The results show roughly 1.5x - 4x performance improvements across components.
[Breaking] Toast
createStore<Key> component to render toastsMenu: Fix issue where context menu doesn't update positioning on subsequent right clicks.
Avatar: Fix issue where api.setSrc doesn't work.
File Upload: Fix issue where drag-and-drop doesn't work when directory is true.
Carousel
Timer: Fix issue where timer stops when switching tabs.