Focus Trap
The focus trap utility is used to trap focus within a DOM element, preventing users from tabbing outside of a specific region.
Features
- Handles edge cases like hidden elements
- Works with shadow DOM
- Supports nested focus traps
- Follows ARIA-controlled elements
Installation
npm install @zag-js/focus-trap
Usage
import { trapFocus } from "@zag-js/focus-trap" // Trap focus within an element const restore = trapFocus(element, { initialFocus: "#first-input", returnFocusOnDeactivate: true, }) // Later, release the trap restore()
Framework Usage
import { useEffect, useRef } from "react" import { trapFocus } from "@zag-js/focus-trap" export function Dialog() { const ref = useRef<HTMLDivElement | null>(null) useEffect(() => { if (!ref.current) return return trapFocus(ref.current, { initialFocus: "[data-autofocus]", }) }, []) return ( <div ref={ref}> <button data-autofocus>Close</button> <input type="text" placeholder="Enter text..." /> <button>Submit</button> </div> ) }
import { onCleanup, onMount } from "solid-js" import { trapFocus } from "@zag-js/focus-trap" export function Dialog() { let ref onMount(() => { if (!ref) return const restore = trapFocus(ref, { initialFocus: "[data-autofocus]", }) onCleanup(() => { restore() }) }) return ( <div ref={ref}> <button data-autofocus>Close</button> <input type="text" placeholder="Enter text..." /> <button>Submit</button> </div> ) }
<script setup> import { ref, onMounted, onBeforeUnmount } from "vue" import { trapFocus } from "@zag-js/focus-trap" const dialogRef = ref(null) onMounted(() => { const restore = trapFocus(dialogRef.value, { initialFocus: "[data-autofocus]", }) onBeforeUnmount(() => { restore() }) }) </script> <template> <div ref="dialogRef"> <button data-autofocus>Close</button> <input type="text" placeholder="Enter text..." /> <button>Submit</button> </div> </template>
<script> import { trapFocus } from "@zag-js/focus-trap" let dialogEl = $state<HTMLElement | null>(null) $effect(() => { return trapFocus(dialogEl, { initialFocus: "[data-autofocus]", }) }) </script> <div bind:this={dialogEl} class="dialog"> <button data-autofocus>Close</button> <input type="text" placeholder="Enter text..." /> <button>Submit</button> </div>
API Reference
trapFocus
function trapFocus( element: HTMLElement | null | (() => HTMLElement | null), options?: TrapFocusOptions, ): () => void
Creates a focus trap for the specified element.
Parameters:
element- The container element to trap focus within, or a function that returns the elementoptions- Configuration options for the focus trap
Returns: A cleanup function that deactivates the focus trap when called.
Options
| Property | Description |
|---|---|
initialFocus | Element to receive focus on activation |
fallbackFocus | Fallback element if no tabbable elements found |
returnFocusOnDeactivate | Return focus to previous element on deactivation |
escapeDeactivates | Allow ESC key to deactivate trap |
clickOutsideDeactivates | Deactivate trap on outside click |
allowOutsideClick | Allow outside clicks without deactivating |
preventScroll | Prevent scrolling when focusing |
delayInitialFocus | Delay initial focus to prevent event capture |
followControlledElements | Include ARIA-controlled elements |
getShadowRoot | Enable shadow DOM traversal |
isKeyForward | Custom forward navigation key |
isKeyBackward | Custom backward navigation key |
onActivate | Called before activation |
onPostActivate | Called after activation |
onDeactivate | Called before deactivation |
onPostDeactivate | Called after deactivation |