Vue Focus Trap Component
focus-trap-vue is a Vue component designed to enhance web accessibility by programmatically trapping keyboard focus within a specified DOM element. This is crucial for UI patterns like modals, dialogs, and sidebars, preventing users of assistive technologies from accidentally tabbing out of the active context. The current stable version is 4.1.0, last published in August 2025, with regular patch and minor releases, indicating active maintenance and a stable API. It acts as a Vue-specific wrapper around the robust `focus-trap` library, inheriting its extensive focus management capabilities. Key differentiators include its seamless integration into Vue applications, support for both Vue 2 (via `@legacy` package) and Vue 3, and flexible control mechanisms via `active` prop, `v-model:active`, or direct method calls, while enforcing best practices for accessibility by leveraging its underlying peer dependency.
Common errors
-
Cannot find module 'focus-trap-vue' or 'Cannot find name 'FocusTrap'.
cause The package or its types were not installed correctly, or the import path is wrong.fixVerify installation with `npm install focus-trap-vue` (or `@legacy`) and ensure correct import statement: `import { FocusTrap } from 'focus-trap-vue';`. -
The FocusTrap component expects a single child element.
cause The `FocusTrap` component has more than one root child element in its slot.fixWrap all content within the `FocusTrap` component inside a single container `<div>` or custom component. -
[Vue warn]: Failed to resolve component: FocusTrap
cause The `FocusTrap` component was used in a template but not properly registered with Vue.fixEnsure `FocusTrap` is either imported and registered locally in a component's `components` option, or globally registered via `app.component('FocusTrap', FocusTrap)` for Vue 3. -
Vue packages version mismatch: - focus-trap-vue requires Vue '^3.0.0' but Vue version is '2.x.x'.
cause An incorrect version of `focus-trap-vue` was installed for the corresponding Vue version in the project.fixFor Vue 2 projects, use `npm install focus-trap-vue@legacy`. For Vue 3 projects, ensure `focus-trap-vue` is installed without `@legacy` and that your `vue` version meets the peer dependency requirement (e.g., `^3.0.0`).
Warnings
- breaking Version 4.0.0 introduced breaking changes, primarily due to upgrading its underlying `focus-trap` dependency to v7. This includes dropping support for Internet Explorer browsers and other behavioral changes in the core focus trapping logic. Refer to the `focus-trap` changelog for full details.
- gotcha The `focus-trap` and `vue` packages are peer dependencies and must be installed separately alongside `focus-trap-vue`. Failure to do so will result in runtime errors.
- gotcha The `FocusTrap` component is designed to wrap exactly one child element. Providing multiple root children will lead to undefined behavior or errors.
- gotcha When setting `initialFocus`, the target element should be a focusable and ideally interactable element (e.g., an input, button, or an element with `tabindex="-1"` if it's a container). If a non-focusable element is targeted, the trap might not behave as expected.
- gotcha Different versions of `focus-trap-vue` are required for Vue 2 vs. Vue 3. The main package (`focus-trap-vue`) is for Vue 3, while Vue 2 requires `focus-trap-vue@legacy`.
Install
-
npm install focus-trap-vue -
yarn add focus-trap-vue -
pnpm add focus-trap-vue
Imports
- FocusTrap
const FocusTrap = require('focus-trap-vue').FocusTrap;import { FocusTrap } from 'focus-trap-vue'; - FocusTrapTabbableOptions
import type { FocusTrapTabbableOptions } from 'focus-trap-vue';
Quickstart
import { createApp, ref } from 'vue';
import { FocusTrap } from 'focus-trap-vue';
const app = createApp({
components: {
FocusTrap,
},
setup() {
const isActive = ref(false);
const nameInput = ref<HTMLInputElement | null>(null);
return {
isActive,
nameInput,
acceptCookies: () => {
alert('Cookies accepted!');
isActive.value = false;
},
};
},
template: `
<div>
<button @click="isActive = true">Open Cookie Consent</button>
<focus-trap v-model:active="isActive" :initial-focus="() => nameInput.value">
<div v-if="isActive" style="border: 1px solid #ccc; padding: 20px; background: #f9f9f9; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1000; width: 300px; text-align: center;">
<p>Do you accept our use of cookies?</p>
<label>Your Name: <input ref="nameInput" type="text" /></label>
<div style="margin-top: 15px;">
<button @click="acceptCookies" style="margin-right: 10px;">Yes, I accept</button>
<button @click="isActive = false">No, thank you</button>
</div>
</div>
</focus-trap>
<p style="margin-top: 200px;">Content behind the modal.</p>
<button>Another button</button>
</div>
`,
});
app.mount('#app');