An Introduction to Focus Trapping

Focus trapping is a method to manage focus within an element, ensuring it remains contained:

– If a user attempts to tab out from the last element, focus returns to the first element.
– If the user tries to Shift + Tab out of the first element, focus returns to the last one.

Focus trapping is crucial for accessible modal dialogs, although using the dialog API can eliminate this need if implemented correctly.

The theory of focus trapping is simple, but implementation is complex due to the many components involved.

### Simple and Easy Focus Trapping with Splendid Labz

If you’re open to using pre-built code, consider this snippet from Splendid Labz:

1. Detect all focusable elements within an element.
2. Manage focus with a keydown event listener.

“`javascript
import { getFocusableElements, trapFocus } from ‘@splendidlabz/utils/dom’

const dialog = document.querySelector(‘dialog’)

// Get all focusable content
const focusables = getFocusableElements(node)

// Traps focus within the dialog
dialog.addEventListener(‘keydown’, event => {
trapFocus({ event, focusables })
})
“`

The snippet simplifies focus trapping, but understanding the underlying functions is beneficial for creating your own or learning the process.

### Selecting All Focusable Elements

Research indicates focusable elements include:

– `a`, `button`, `input`, `textarea`, `select`, `details`, `iframe`, `embed`, `object`, `summary`, `dialog`, `audio[controls]`, `video[controls]`, `[contenteditable]`, `[tabindex]`

The first step in `getFocusableElements` is identifying these elements within a container:

“`javascript
export function getFocusableElements(container = document.body ) {
return {
get all () {
const elements = Array.from(
container.querySelectorAll(
`a,
button,
input,
textarea,
select,
details,
iframe,
embed,
object,
summary,
dialog,
audio[controls],
video[controls],
[contenteditable],
[tabindex]
`,
),
)
}
}
}
“`

Filter out `disabled`, `hidden`, or `display: none` elements:

“`javascript
export function getFocusableElements(container = document.body ) {
return {
get all () {
return elements.filter(el => {
if (el.hasAttribute(‘disabled’)) return false
if (el.hasAttribute(‘hidden’)) return false
if (window.getComputedStyle(el).display === ‘none’) return false
return true
})
}
}
}
“`

Retrieve keyboard-only focusable elements by removing `tabindex` values less than `0`:

“`javascript
export function getFocusableElements(container = document.body ) {
return {
get all () { /* … */ },
get keyboardOnly() {
return this.all.filter(el => el.tabIndex > -1)
}
}
}
“`

To trap focus:

– If a user tabs out from the last element, return focus to the first.
– If a user Shift + Tabs out of the first element, return focus to the last.

Add `first` and `last` getters to retrieve these elements:

“`javascript
export function getFocusableElements(container = document.body ) {
return {
// …
get first() { return this.keyboardOnly[0] },
get last() { return this.keyboardOnly[0] },
}
}
“`

### How to Trap Focus

Detect a keyboard event with `addEventListener`:

“`javascript
const container = document.querySelector(‘.some-element’)
container.addEventListener(‘keydown’, event => {/* … */})
“`

Check if the user is:

– Pressing tab (without Shift)
– Pressing tab (with Shift)

Splendid Labz provides functions for these:

“`javascript
import { isTab, isShiftTab } from ‘@splendidlabz/utils/dom’

// …
container.addEventListener(‘keydown’, event => {
if (isTab(event)) // Handle Tab
if (isShiftTab(event)) // Handle Shift Tab
/* … */
})
“`

Create `isTab` and `isShiftTab` functions:

“`javascript
export function isTab(event) {
return !event.shiftKey && event.key === ‘Tab’
}

export function isShiftTab(event) {
return event.shiftKey && event.key === ‘Tab’
}
“`

Skip handling other keys with an early return:

“`javascript
container.addEventListener(‘keydown’, event => {
if (event.key !== ‘Tab’) return

if (isTab(event)) // Handle Tab
if (isShiftTab(event)) // Handle Shift Tab
/* … */
})
“`

Determine the current focused element with `document.activeElement`:

“`javascript
container.addEventListener(‘keydown’,


Discover more from WIREDGORILLA

Subscribe to get the latest posts sent to your email.

Similar Posts