Vue3 and Vue2 directive for keyboard navigation - roving movement and trapping inside container
npm install @pdanpdan/vue-keyboard-trap

!github release
!jsdelivr hits
!npm release
!npm downloads
Vue directive and composable for keyboard navigation - roving movement and trapping inside container.
Works both for Vue3 and Vue2, as a directive (v-kbd-trap) or as a composable (useKeyboardTrap).
Source code, Issues, Discussions
``bash`
pnpm add @pdanpdan/vue-keyboard-trap`
orbash`
yarn add @pdanpdan/vue-keyboard-trap`
orbash`
npm install @pdanpdan/vue-keyboard-trap
#### As composable (both Vue3 and Vue2)
`html
...
`
#### As plugin on Vue3 - directive
`javascript
import { createApp } from 'vue';
import { VueKeyboardTrapDirectivePlugin } from '@pdanpdan/vue-keyboard-trap';
import App from './App.vue';
const app = createApp(App);
app.use(VueKeyboardTrapDirectivePlugin, {
// ...options if required
});
app.mount('#app');
`
#### As plugin on Vue2 - directive
`javascript
import Vue from 'vue';
import { VueKeyboardTrapDirectivePlugin } from '@pdanpdan/vue-keyboard-trap';
import App from './App.vue';
Vue.use(VueKeyboardTrapDirectivePlugin, {
// ...options if required
});
new Vue({
el: '#app',
});
`
#### Included in specific components (Vue3 script setup) - directive
`html`
#### Included in specific components (Vue3 script) - directive
`html`
#### Included in specific components (Vue2) - directive
`html`
#### User hint styles (cosmetic)
The directive does not require any CSS styles to work, but for cosmetic purposes (as user hints) some example styles are provided in dist/styles/index.sass.
in Javascript
`javascript`
import '@pdanpdan/vue-keyboard-trap/styles';
or in SASS
`sass`
@import '@pdanpdan/vue-keyboard-trap/styles'
or (if the /styles export is not used by your bundler)
in Javascript
`javascript`
import '@pdanpdan/vue-keyboard-trap/dist/styles/index.sass';
or in SASS
`sass`
@import '@pdanpdan/vue-keyboard-trap/dist/styles/index.sass'
Load the javascript from https://cdn.jsdelivr.net/gh/pdanpdan/vue-keyboard-trap/dist/index.umd.js.
It will expose a global object VueKeyboardTrap with VueKeyboardTrapDirectivePlugin and VueKeyboardTrapDirectiveFactory keys.
In order to work it requires that VueDemi is already loaded on the page. You can do it like this:
`html
`
#### As composable (both Vue3 and Vue2)
`javascript
const { ref } = Vue;
const { useKeyboardTrapFactory } = VueKeyboardTrap;
const useKeyboardTrap = useKeyboardTrapFactory({
// ...options if required
});
const elRef = ref(null);
useKeyboardTrap(
// element (reactive)
elRef,
// modifiers (optional, reactive, default all modifiers are false)
{
roving: true,
},
// active (optional, reactive, default true)
true
);
`
#### As plugin on Vue3 - directive
`javascript
const { createApp } = Vue;
const { VueKeyboardTrapDirectivePlugin } = VueKeyboardTrap;
const app = createApp({});
app.use(VueKeyboardTrapDirectivePlugin, {
// ...options if required
});
app.mount('#app');
`
#### As plugin on Vue2 - directive
`javascript
const { VueKeyboardTrapDirectivePlugin } = VueKeyboardTrap;
Vue.use(VueKeyboardTrapDirectivePlugin, {
// ...options if required
});
new Vue({
el: '#app',
});
`
#### As directive on Vue3 - directive
`javascript
const { createApp } = Vue;
const { VueKeyboardTrapDirectiveFactory } = VueKeyboardTrap;
const app = createApp({});
const { name, directive } = VueKeyboardTrapDirectiveFactory({
// ...options if required
});
app.directive(name, directive);
app.mount('#app');
`
#### As directive on Vue2 - directive
`javascript
const { VueKeyboardTrapDirectiveFactory } = VueKeyboardTrap;
const { name, directive } = VueKeyboardTrapDirectiveFactory({
// ...options if required
});
Vue.directive(name, directive);
`
#### User hint styles (cosmetic)
If you want you can access the CSS cosmetic style (user hints) from https://cdn.jsdelivr.net/gh/pdanpdan/vue-keyboard-trap/dist/styles/index.css.
| Option | Description | Default |
|--------|-------------|:-------:|
| name | snake-case name of the directive (without v- prefix) | kbd-trap |datasetName
| | camelCase name of the data-attribute to be set on element when trap is enabled | v${PascalCase from name} |focusableSelector
| | CSS selector for focusable elements | see here |rovingSkipSelector
| | CSS selector for elements that should not respond to roving key navigation (input, textarea, ...) | see here |gridSkipSelector
| | CSS selector that will be applied in .roving.grid mode to exclude elements - must be a series of :not() selectors | see here |autofocusSelector
| | CSS selector for the elements that should be autofocused | see here |trapTabIndex
| | tabIndex value to be used when trap element has a tabIndex of -1 and has no tabindex attribute | -9999 |
#### Default focusableSelector:
`css`
:focus,
a[href]:not([tabindex^="-"]),
area[href]:not([tabindex^="-"]),
video[controls]:not([tabindex^="-"]),
audio[controls]:not([tabindex^="-"]),
iframe:not([tabindex^="-"]),
[tabindex]:not(slot):not([tabindex^="-"]),
[contenteditable]:not([contenteditable="false"]):not([tabindex^="-"]),
details > summary:first-of-type:not([tabindex^="-"]),
input:not([type="hidden"]):not(fieldset[disabled] input):not([disabled]):not([tabindex^="-"]),
select:not(fieldset[disabled] input):not([disabled]):not([tabindex^="-"]),
textarea:not(fieldset[disabled] input):not([disabled]):not([tabindex^="-"]),
button:not(fieldset[disabled] input):not([disabled]):not([tabindex^="-"]),
fieldset[disabled]:not(fieldset[disabled] fieldset) > legend input:not([type="hidden"]):not([disabled]):not([tabindex^="-"]),
fieldset[disabled]:not(fieldset[disabled] fieldset) > legend select:not([disabled]):not([tabindex^="-"]),
fieldset[disabled]:not(fieldset[disabled] fieldset) > legend textarea:not([disabled]):not([tabindex^="-"]),
fieldset[disabled]:not(fieldset[disabled] fieldset) > legend button:not([disabled]):not([tabindex^="-"]),
[class*="focusable"]:not([disabled]):not([tabindex^="-"])
By default a tags without href are not focusable - add a tabindex="0" attribute on them to make them focusable.
This can be done for all other elements if you want them to be focusable.
#### Default rovingSkipSelector:
`css`
input:not([disabled]):not([type="button"]):not([type="checkbox"]):not([type="file"]):not([type="image"]):not([type="radio"]):not([type="reset"]):not([type="submit"]),
select:not([disabled]),
select:not([disabled]) *,
textarea:not([disabled]),
[contenteditable]:not([contenteditable="false"]),
[contenteditable]:not([contenteditable="false"]) *
#### Default gridSkipSelector:
`css`
:not([disabled]),
:not([tabindex^="-"])
#### Default autofocusSelector:
`css`
[autofocus]:not([disabled]):not([autofocus="false"]),
[data-autofocus]:not([disabled]):not([data-autofocus="false"])
Use the value of the directive (boolean) to enable/disable it.
` The modifiers are reactive so if you use render functions you can dynamically change the behaviour. | Modifier | Description | - In order to specify the navigation pattern you must use 2 dataset attributes on the focusable elements inside the - Any or both attributes can have a value of #### Navigation rules - the first focusable element on the row / col (based on direction of movement) is focused In order to specify the navigation pattern you must use role attributes All focusable element must have The #### Navigation rules - the first focusable element on the row / col (based on direction of movement) is focused The directive checks the closest parent DOM Element of the active element that has a If the direction is RTL the The directive does not require any styles, but it might help the users to have visual hints for navigation. A default style is provided as SASS in The default style is also provided as CSS in There are some CSS variables that can be used to customize the aspect of the hints: | Variable | Role | Default | In the default style the hint is positioned on the top-right corner of the trap group. <<< @/../src/public/styles/index.sass `` ` ` ` Vue Keyboard Trap (vue-keyboard-trap) * Name: Dan Popescu (PDan) Copyright © 2022-present Dan Popescu. This application is distributed under , see LICENSE for more information.html
`.autofocus$3
|----------|-------------|
| | autofocuses the first element that matches autofocusSelector or (if no such element is found) the first focusable child element when the directive is mounted or enabled (only if it not covered by another element) |.roving
| or .roving.vertical.horizontal | allow roving navigation (Home, End, ArrowKeys) |.roving.vertical
| | allow roving navigation (Home, End, ArrowUp, ArrowDown) |.roving.horizontal
| | allow roving navigation (Home, End, ArrowLeft, ArrowRight) |.roving.grid
| | allow roving navigation (Home, End, ArrowKeys) using dataset attrs on elements [data-${camelCase from datasetName}-(row/col)]; [data-${camelCase from datasetName}-(row/col)~="*"] is a catchall |.roving
| used on an element with [role="grid"] | allow roving navigation (Home, End, ArrowKeys) using role attrs on elements [role="row/gridcell"] |.roving.tabinside
| | Tab key navigates to next/prev element inside trap (by default Tab key navigates to next/prev element outside trap in roving mode) |.escrefocus
| | refocus element that was in focus before activating the trap on Esc |.escexits
| | refocus a parent trap on Esc (has priority over .escrefocus) |.indexorder
| used without .grid modifier and on elements without [role="grid"] | force usage of order in tabindex (tabindex in ascending order and then DOM order) |TABKeyboard navigation
/ SHIFT+TAB key.roving
- moves to next / previous focusable element inside the trap group (moves from last one to first one or from first one to last one when no more focusable elements are available in the group)
- if modifier is used moves to next / previous trap group or focusable element outside the current trap group.roving.tabinside
- if modifiers are used then move inside the trap group.indexorder
- if modifier is used without .grid and on elements without [role="grid"] - the order of tabindex will be usedESC
- key.escexits
- disables / enables the current tab group
- if modifier is used then refocus the last active focusable element in a parent trap group.escrefocus
- if modifier is used then refocus the last focusable element that was active before the current trap group got focus.escexits
- if or .escrefocus are used then press SHIFT + ESC to disable / enable the current tab groupHOME
- / END when .roving modifier is usedARROW_KEYS
- move to first / last focusable element in the current trap group
- when .roving modifier is used (.roving.horizontal.vertical is the same as .roving).horizontal
- if only modifier is used then only ARROW_LEFT / ARROW_RIGHT keys can be used.vertical
- if only modifier is used then only ARROW_UP / ARROW_DOWN keys can be usedARROW_LEFT
- / ARROW_UP move to the previous focusable element inside the trap groupARROW_RIGHT
- / ARROW_DOWN move to the next focusable element inside the trap group.indexorder
- if modifier is used without .grid and on elements without [role="grid"] - the order of tabindex will be usedARROW_KEYS
- when .roving.grid modifiers are used or .roving modifier on a trap element with [role="grid"].roving
- move in the grid inside the current trap group$3
trap group:data-v-kbd-trap-row specifies the numeric identifier of the row the element belongs to (numbers need not be consecutive, but their natural order determines the navigation order)data-v-kbd-trap-col
- specifies the numeric identifier of the column the element belongs to (numbers need not be consecutive, but their natural order determines the navigation order)* that means that it is an alement that can be focused from elements having any coresponding (row or col) attribute.*
- an element with for row or col is considered to belong to any row / col[role="row"]$3
and [role="gridcell"].[role="gridcell"] and must be inside [role="row"] elements inside [role="grid"] trap element.gridcells will be considered inline-start aligned in every row.[dir="rtl"]$3
or [dir="ltr] attribute.ARROW_LEFT and ARROW_RIGHT keys move in reverse (according to document order of the focusable elements) but consistent to the way the elements are ordered on screen.CSS (visual hints for users)
dist/styles/index.sass (can be imported as import '@pdapdan/vue-keyboard-trap/styles', as import '@pdapdan/vue-keyboard-trap/dist/styles/index.sass' (if the bundler does not use the /styles export) or included from https://cdn.jsdelivr.net/gh/pdanpdan/vue-keyboard-trap/dist/styles/index.sass).dist/styles/index.css (can be imported as import '@pdapdan/vue-keyboard-trap/dist/styles/index.css' or included from https://cdn.jsdelivr.net/gh/pdanpdan/vue-keyboard-trap/dist/styles/index.css).
|----------|------|:-------:|
| --color-v-kbd-trap-enabled | the text color when directive is enabled | #c33 ■ |
| --color-v-kbd-trap-disabled | the text color when directive is disabled | #999 ■ |
| --color-v-kbd-trap-background | the background color of the hint area | #eeee ■ |
| --text-v-kbd-trap-separator | separator between elements | / |
| --text-v-kbd-trap-enabled | indicator for enabled but not active trap | Trap |
| --text-v-kbd-trap-esc | indicator for Esc key active | Esc |
| --text-v-kbd-trap-esc-refocus | indicator for Esc key active when it refocuses | Esc\2949 / Esc⥉ |
| --text-v-kbd-trap-esc-exits | indicator for Esc key active when it exits trap | Esc\2923 / Esc⤣ |
| --text-v-kbd-trap-tab | indicator for Tab key active inside trap | Tab |
| --text-v-kbd-trap-tab-exits | indicator for Tab key active when it exits trap | Tab\21C5 / Tab⇅ |
| --text-v-kbd-trap-grid | indicator for grid mode active | \229E / ⊞ |
| --text-v-kbd-trap-arrows-all | indicator for move keys active in roving mode | \2962\2963\2965\2964 / ⥢⥣⥥⥤ |
| --text-v-kbd-trap-arrows-horizontal | indicator for move keys active in roving mode horizontal | \2962\2964 / ⥢⥤ |
| --text-v-kbd-trap-arrows-vertical | indicator for move keys active in roving mode vertical | \2963\2965 / ⥣⥥ |Development
$3
bash`
pnpm i$3
bash`
pnpm dev$3
bash`
pnpm lint$3
bash``
pnpm buildSource code, issues, bug reports, feature requests
Author
* Email: pdan.popescu@gmail.com
* Website: https://github.com/pdanpdan/
* Github: @pdanpdanLicense