Action

Focus Trap

Keeps focus within an element for accessibility.

/// DEMO ///

Press tab key to cycle through focusable elements.

Email

Password

///

1
<script>
2
    import Button from '$lib/components/base/button.svelte';
3
    import Field from '$lib/components/base/field.svelte';
4
    import Input from '$lib/components/base/input.svelte';
5
    import { focusTrap } from '$lib/actions/focus-trap.svelte';
6
</script>
7

8
<!-- Use focusTrap to keep focus within the form -->
9
<form use:focusTrap class="flex flex-col gap-2 border p-4">
10
    <p>Press tab key to cycle through focusable elements.</p>
11
    <Field label="Email">
12
        <Input type="email" />
13
    </Field>
14
    <Field label="Password">
15
        <Input type="password" />
16
    </Field>
17
    <Field class="mt-4 flex justify-end">
18
        <Button>Sign In</Button>
19
    </Field>
20
</form>
21

Guide

Create the action.

1
export function focusTrap(node: HTMLElement) {
2
    const focusableSelectors = ['a[href]', 'button', 'input', 'textarea', 'select', '[tabindex]:not([tabindex="-1"])'];
3

4
    function getFocusableElements() {
5
        return Array.from(node.querySelectorAll<HTMLElement>(focusableSelectors.join(','))).filter(
6
            (el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden')
7
        );
8
    }
9

10
    function handleKeyDown(event: KeyboardEvent) {
11
        if (event.key !== 'Tab') return;
12

13
        const focusableElements = getFocusableElements();
14
        const firstElement = focusableElements[0];
15
        const lastElement = focusableElements[focusableElements.length - 1];
16

17
        if (event.shiftKey && document.activeElement === firstElement) {
18
            event.preventDefault();
19
            lastElement?.focus();
20
        } else if (!event.shiftKey && document.activeElement === lastElement) {
21
            event.preventDefault();
22
            firstElement?.focus();
23
        }
24
    }
25

26
    $effect(() => {
27
        const focusableElements = getFocusableElements();
28
        focusableElements[0]?.focus();
29

30
        node.addEventListener('keydown', handleKeyDown);
31

32
        return () => {
33
            node.removeEventListener('keydown', handleKeyDown);
34
        };
35
    });
36
}
37

npx lomer-ui action focus-trap

Share treats:

Copyright © 2025 - Cliemtor Fabros