# Focus Trap (REACT)

## Motivation

Focus trapping is essential for modal interfaces and other interactive elements that require user attention.

The `FocusTrap` component helps maintain accessibility by ensuring keyboard focus remains within a designated container
until explicitly released.

## Examples

**Example: basic**

#### React

```tsx
import { FocusTrap } from '@ark-ui/react/focus-trap'
import { useState } from 'react'

export const Basic = () => {
  const [trapped, setTrapped] = useState(false)
  return (
    <>
      <button onClick={() => setTrapped(true)}>Start Trap</button>
      <FocusTrap returnFocusOnDeactivate={false} disabled={!trapped}>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '1rem',
            paddingBlock: '1rem',
          }}
        >
          <input type="text" placeholder="input" />
          <textarea placeholder="textarea" />
          <button onClick={() => setTrapped(false)}>End Trap</button>
        </div>
      </FocusTrap>
    </>
  )
}

```

#### Solid

```tsx
import { FocusTrap } from '@ark-ui/solid/focus-trap'
import { createSignal } from 'solid-js'

export const Basic = () => {
  const [trapped, setTrapped] = createSignal(false)
  return (
    <>
      <button onClick={() => setTrapped(true)}>Start Trap</button>
      <FocusTrap returnFocusOnDeactivate={false} disabled={!trapped()}>
        <div
          style={{
            display: 'flex',
            'flex-direction': 'column',
            gap: '1rem',
            'padding-block': '1rem',
          }}
        >
          <input type="text" placeholder="input" />
          <textarea placeholder="textarea" />
          <button onClick={() => setTrapped(false)}>End Trap</button>
        </div>
      </FocusTrap>
    </>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { FocusTrap } from '@ark-ui/vue/focus-trap'
import { ref } from 'vue'

const trapped = ref(false)
</script>

<template>
  <button @click="trapped = true">Start Trap</button>
  <FocusTrap :return-focus-on-deactivate="false" :disabled="!trapped">
    <div style="display: flex; flex-direction: column; gap: 1rem; padding-block: 1rem">
      <input type="text" placeholder="input" />
      <textarea placeholder="textarea" />
      <button @click="trapped = false">End Trap</button>
    </div>
  </FocusTrap>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { FocusTrap } from '@ark-ui/svelte/focus-trap'

  let trapped = $state(false)
</script>

<button onclick={() => (trapped = true)}>Start Trap</button>

<FocusTrap returnFocusOnDeactivate={false} disabled={!trapped}>
  <div style="display: flex; flex-direction: column; gap: 1rem; padding-block: 1rem">
    <input type="text" placeholder="input" />
    <textarea placeholder="textarea"></textarea>
    <button onclick={() => (trapped = false)}>End Trap</button>
  </div>
</FocusTrap>

```

### Autofocus

The focus trap respects elements with the `autofocus` attribute.

**Example: autofocus**

#### React

```tsx
import { FocusTrap } from '@ark-ui/react/focus-trap'
import { useRef, useState } from 'react'

export const Autofocus = () => {
  const [trapped, setTrapped] = useState(false)
  const toggle = () => setTrapped((c) => !c)

  const buttonRef = useRef<HTMLButtonElement | null>(null)
  const getButtonNode = () => {
    const node = buttonRef.current
    if (!node) throw new Error('Button not found')
    return node
  }

  return (
    <div>
      <button ref={buttonRef} onClick={toggle}>
        {trapped ? 'End Trap' : 'Start Trap'}
      </button>
      {trapped && (
        <FocusTrap disabled={!trapped} setReturnFocus={getButtonNode}>
          <div
            style={{
              display: 'flex',
              flexDirection: 'column',
              gap: '1rem',
              paddingBlock: '1rem',
            }}
          >
            <input type="text" placeholder="Regular input" />
            {/* biome-ignore lint/a11y/noAutofocus: intentional */}
            <input type="text" placeholder="Autofocused input" autoFocus />
            <button onClick={() => setTrapped(false)}>End Trap</button>
          </div>
        </FocusTrap>
      )}
    </div>
  )
}

```

#### Solid

```tsx
import { FocusTrap } from '@ark-ui/solid/focus-trap'
import { Show, createSignal } from 'solid-js'

export const Autofocus = () => {
  const [trapped, setTrapped] = createSignal(false)
  let buttonRef!: HTMLButtonElement

  return (
    <div>
      <button ref={buttonRef} onClick={() => setTrapped((v) => !v)}>
        {trapped() ? 'End Trap' : 'Start Trap'}
      </button>
      <Show when={trapped()}>
        <FocusTrap disabled={!trapped()} setReturnFocus={buttonRef}>
          <div
            style={{
              display: 'flex',
              'flex-direction': 'column',
              gap: '1rem',
              'padding-block': '1rem',
            }}
          >
            <input type="text" placeholder="Regular input" />
            <input type="text" placeholder="Autofocused input" autofocus />
            <button onClick={() => setTrapped(false)}>End Trap</button>
          </div>
        </FocusTrap>
      </Show>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { FocusTrap } from '@ark-ui/vue/focus-trap'
import { ref } from 'vue'

const trapped = ref(false)
const buttonRef = ref<HTMLButtonElement>()
</script>

<template>
  <div>
    <button ref="buttonRef" @click="trapped = !trapped">
      {{ trapped ? 'End Trap' : 'Start Trap' }}
    </button>
    <FocusTrap v-if="trapped" :disabled="!trapped" :set-return-focus="buttonRef">
      <div style="display: flex; flex-direction: column; gap: 1rem; padding-block: 1rem">
        <input type="text" placeholder="Regular input" />
        <input type="text" placeholder="Autofocused input" autofocus />
        <button @click="trapped = false">End Trap</button>
      </div>
    </FocusTrap>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { FocusTrap } from '@ark-ui/svelte/focus-trap'

  let trapped = $state(false)
  let buttonNode: HTMLButtonElement | null = $state(null)

  function getButtonNode() {
    if (!buttonNode) throw new Error('Button not found')
    return buttonNode
  }
</script>

<div>
  <button bind:this={buttonNode} onclick={() => (trapped = !trapped)}>
    {trapped ? 'End Trap' : 'Start Trap'}
  </button>
  {#if trapped}
    <FocusTrap disabled={!trapped} setReturnFocus={getButtonNode}>
      <div style="display: flex; flex-direction: column; gap: 1rem; padding-block: 1rem">
        <input type="text" placeholder="Regular input" />
        <!-- svelte-ignore a11y_autofocus -->
        <input type="text" placeholder="Autofocused input" autofocus />
        <button onclick={() => (trapped = false)}>End Trap</button>
      </div>
    </FocusTrap>
  {/if}
</div>

```

### Initial Focus

Use the `initialFocus` prop to set the element that should receive initial focus when the trap is activated.

**Example: initial-focus**

#### React

```tsx
import { FocusTrap } from '@ark-ui/react/focus-trap'
import { useRef, useState } from 'react'

export const InitialFocus = () => {
  const [trapped, setTrapped] = useState(false)
  const toggle = () => setTrapped((c) => !c)

  const inputRef = useRef<HTMLInputElement>(null)

  return (
    <div>
      <button onClick={toggle}>{trapped ? 'End Trap' : 'Start Trap'}</button>
      <FocusTrap disabled={!trapped} initialFocus={() => inputRef.current}>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: '1rem',
            paddingBlock: '1rem',
          }}
        >
          <input type="text" placeholder="First input" />
          <input ref={inputRef} type="text" placeholder="Second input (initial focus)" />
          <textarea placeholder="textarea" />
          <button onClick={() => setTrapped(false)}>End Trap</button>
        </div>
      </FocusTrap>
    </div>
  )
}

```

#### Solid

```tsx
import { FocusTrap } from '@ark-ui/solid/focus-trap'
import { createSignal } from 'solid-js'

export const InitialFocus = () => {
  const [trapped, setTrapped] = createSignal(false)
  const toggle = () => setTrapped((v) => !v)

  let inputRef!: HTMLInputElement

  return (
    <div>
      <button onClick={toggle}>{trapped() ? 'End Trap' : 'Start Trap'}</button>
      <FocusTrap disabled={!trapped()} initialFocus={() => inputRef}>
        <div
          style={{
            display: 'flex',
            'flex-direction': 'column',
            gap: '1rem',
            'padding-block': '1rem',
          }}
        >
          <input type="text" placeholder="First input" />
          <input ref={inputRef} type="text" placeholder="Second input (initial focus)" />
          <textarea placeholder="textarea" />
          <button onClick={toggle}>End Trap</button>
        </div>
      </FocusTrap>
    </div>
  )
}

```

#### Vue

```vue
<script setup lang="ts">
import { FocusTrap } from '@ark-ui/vue/focus-trap'
import { ref } from 'vue'

const trapped = ref(false)
const inputRef = ref<HTMLInputElement>()
const toggle = () => {
  trapped.value = !trapped.value
}
</script>

<template>
  <div>
    <button @click="toggle">{{ trapped ? 'End Trap' : 'Start Trap' }}</button>
    <FocusTrap :disabled="!trapped" :initial-focus="() => inputRef">
      <div style="display: flex; flex-direction: column; gap: 1rem; padding-block: 1rem">
        <input type="text" placeholder="First input" />
        <input ref="inputRef" type="text" placeholder="Second input (initial focus)" />
        <textarea placeholder="textarea" />
        <button @click="trapped = false">End Trap</button>
      </div>
    </FocusTrap>
  </div>
</template>

```

#### Svelte

```svelte
<script lang="ts">
  import { FocusTrap } from '@ark-ui/svelte/focus-trap'

  let trapped = $state(false)
  let inputNode: HTMLInputElement | null = $state(null)

  function getInputNode() {
    if (!inputNode) throw new Error('Input not found')
    return inputNode
  }
</script>

<div>
  <button onclick={() => (trapped = !trapped)}>
    {trapped ? 'End Trap' : 'Start Trap'}
  </button>
  <FocusTrap disabled={!trapped} initialFocus={getInputNode}>
    <div style="display: flex; flex-direction: column; gap: 1rem; padding-block: 1rem">
      <input type="text" placeholder="First input" />
      <input bind:this={inputNode} type="text" placeholder="Second input (initial focus)" />
      <textarea placeholder="textarea"></textarea>
      <button onclick={() => (trapped = false)}>End Trap</button>
    </div>
  </FocusTrap>
</div>

```

## API Reference

**Component API Reference**

#### React

**FocusTrap Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `disabled` | `boolean` | No | Whether the focus trap is disabled. |
| `fallbackFocus` | `FocusTarget` | No | By default, an error will be thrown if the focus trap contains no
elements in its tab order. With this option you can specify a
fallback element to programmatically receive focus if no other
tabbable elements are found. For example, you may want a popover's
`<div>` to receive focus if the popover's content includes no
tabbable elements. *Make sure the fallback element has a negative
`tabindex` so it can be programmatically focused.

NOTE: If `initialFocus` is `false` (or a function that returns `false`),
this function will not be called when the trap is activated, and no element
will be initially focused. This function may still be called while the trap
is active if things change such that there are no longer any tabbable nodes
in the trap. |
| `initialFocus` | `VoidFunction | FocusTargetOrFalse` | No | By default, when a focus trap is activated the first element in the
focus trap's tab order will receive focus. With this option you can
specify a different element to receive that initial focus, or use `false`
for no initially focused element at all.

NOTE: Setting this option to `false` (or a function that returns `false`)
will prevent the `fallbackFocus` option from being used.

Setting this option to `undefined` (or a function that returns `undefined`)
will result in the default behavior. |
| `onActivate` | `VoidFunction` | No | A function that will be called **before** sending focus to the
target element upon activation. |
| `onDeactivate` | `VoidFunction` | No | A function that will be called **before** sending focus to the
trigger element upon deactivation. |
| `returnFocusOnDeactivate` | `boolean` | No | Default: `true`. If `false`, when the trap is deactivated,
focus will *not* return to the element that had focus before activation. |
| `setReturnFocus` | `type ONLY_FOR_FORMAT =
  | FocusTargetValueOrFalse
  | ((nodeFocusedBeforeActivation: HTMLElement | SVGElement) => FocusTargetValueOrFalse)` | No | By default, focus trap on deactivation will return to the element
that was focused before activation. |


#### Solid

**FocusTrap Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `(props: ParentProps<'div'>) => Element` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `disabled` | `boolean` | No | Whether the focus trap is disabled. |
| `fallbackFocus` | `FocusTarget` | No | By default, an error will be thrown if the focus trap contains no
elements in its tab order. With this option you can specify a
fallback element to programmatically receive focus if no other
tabbable elements are found. For example, you may want a popover's
`<div>` to receive focus if the popover's content includes no
tabbable elements. *Make sure the fallback element has a negative
`tabindex` so it can be programmatically focused.

NOTE: If `initialFocus` is `false` (or a function that returns `false`),
this function will not be called when the trap is activated, and no element
will be initially focused. This function may still be called while the trap
is active if things change such that there are no longer any tabbable nodes
in the trap. |
| `initialFocus` | `VoidFunction | FocusTargetOrFalse` | No | By default, when a focus trap is activated the first element in the
focus trap's tab order will receive focus. With this option you can
specify a different element to receive that initial focus, or use `false`
for no initially focused element at all.

NOTE: Setting this option to `false` (or a function that returns `false`)
will prevent the `fallbackFocus` option from being used.

Setting this option to `undefined` (or a function that returns `undefined`)
will result in the default behavior. |
| `onActivate` | `VoidFunction` | No | A function that will be called **before** sending focus to the
target element upon activation. |
| `onDeactivate` | `VoidFunction` | No | A function that will be called **before** sending focus to the
trigger element upon deactivation. |
| `returnFocusOnDeactivate` | `boolean` | No | Default: `true`. If `false`, when the trap is deactivated,
focus will *not* return to the element that had focus before activation. |
| `setReturnFocus` | `type ONLY_FOR_FORMAT =
  | FocusTargetValueOrFalse
  | ((nodeFocusedBeforeActivation: HTMLElement | SVGElement) => FocusTargetValueOrFalse)` | No | By default, focus trap on deactivation will return to the element
that was focused before activation. |


#### Vue

**FocusTrap Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `boolean` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `disabled` | `boolean` | No | Whether the focus trap is disabled |
| `fallbackFocus` | `FocusTarget` | No | Element to focus if initialFocus is not found. By default, focuses on container element |
| `initialFocus` | `FocusTargetOrFalse | VoidFunction` | No | Element to focus when trap is activated. By default, focuses on first tabbable element |
| `returnFocusOnDeactivate` | `boolean` | No | Whether to return focus to the element that had focus when trap was activated |
| `setReturnFocus` | `FocusTargetValueOrFalse | ((node: FocusableElement) => FocusTargetValueOrFalse)` | No | Custom element to return focus to when trap is deactivated |


#### Svelte

**FocusTrap Props:**

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `asChild` | `Snippet<[PropsFn<'div'>]>` | No | Use the provided child element as the default rendered element, combining their props and behavior. |
| `disabled` | `boolean` | No | Whether the focus trap is disabled. |
| `fallbackFocus` | `FocusTarget` | No | By default, an error will be thrown if the focus trap contains no
elements in its tab order. With this option you can specify a
fallback element to programmatically receive focus if no other
tabbable elements are found. For example, you may want a popover's
`<div>` to receive focus if the popover's content includes no
tabbable elements. *Make sure the fallback element has a negative
`tabindex` so it can be programmatically focused.

NOTE: If `initialFocus` is `false` (or a function that returns `false`),
this function will not be called when the trap is activated, and no element
will be initially focused. This function may still be called while the trap
is active if things change such that there are no longer any tabbable nodes
in the trap. |
| `initialFocus` | `VoidFunction | FocusTargetOrFalse` | No | By default, when a focus trap is activated the first element in the
focus trap's tab order will receive focus. With this option you can
specify a different element to receive that initial focus, or use `false`
for no initially focused element at all.

NOTE: Setting this option to `false` (or a function that returns `false`)
will prevent the `fallbackFocus` option from being used.

Setting this option to `undefined` (or a function that returns `undefined`)
will result in the default behavior. |
| `onActivate` | `VoidFunction` | No | A function that will be called **before** sending focus to the
target element upon activation. |
| `onDeactivate` | `VoidFunction` | No | A function that will be called **before** sending focus to the
trigger element upon deactivation. |
| `ref` | `Element` | No |  |
| `returnFocusOnDeactivate` | `boolean` | No | Default: `true`. If `false`, when the trap is deactivated,
focus will *not* return to the element that had focus before activation. |
| `setReturnFocus` | `type ONLY_FOR_FORMAT =
  | FocusTargetValueOrFalse
  | ((nodeFocusedBeforeActivation: HTMLElement | SVGElement) => FocusTargetValueOrFalse)` | No | By default, focus trap on deactivation will return to the element
that was focused before activation. |