npm.io
1.0.0-next.2 • Published 21h ago

@solid-primitives/focus

Licence
MIT
Version
1.0.0-next.2
Deps
2
Size
30 kB
Vulns
0
Weekly
0
Stars
1.5K

Solid Primitives Focus

@solid-primitives/focus

size version stage tested with vitest

Primitives for autofocusing HTML elements and trapping focus within a container.

The native autofocus attribute only works on page load, which makes it incompatible with SolidJS. These primitives run on render, allowing autofocus on initial render as well as dynamically added components.

  • autofocus - Ref callback factory to autofocus an element on render.
  • createAutofocus - Reactive primitive to autofocus an element on render.
  • createFocusTrap - Traps focus inside a given DOM element.
  • createFocusRestore - Restores focus to the previously focused element, without trapping.

Installation

npm install @solid-primitives/focus
# or
yarn add @solid-primitives/focus
# or
pnpm add @solid-primitives/focus

autofocus

How to use it

autofocus is a ref callback factory. It uses the native autofocus attribute to determine whether to focus the element.

import { autofocus } from "@solid-primitives/focus";

<button ref={autofocus()} autofocus>
  Autofocused
</button>;

To conditionally enable autofocus, control the autofocus attribute directly — the autofocus() ref only focuses when the attribute is present, so removing it is sufficient to opt out:

// Conditionally autofocus by toggling the attribute
<button ref={autofocus()} autofocus={shouldFocus()}>
  Maybe Autofocused
</button>

Note: The enabled parameter was removed because it was redundant — the same effect is achieved by omitting the autofocus attribute. Previously, Solid directives always received an accessor argument whether you used it or not, which gave the impression an explicit toggle was necessary.

createAutofocus

createAutofocus reactively autofocuses an element passed in as a signal.

import { createAutofocus } from "@solid-primitives/focus";

// Using ref
let ref!: HTMLButtonElement;
createAutofocus(() => ref);

<button ref={ref}>Autofocused</button>;

// Using ref signal
const [ref, setRef] = createSignal<HTMLButtonElement>();
createAutofocus(ref);

<button ref={setRef}>Autofocused</button>;

createFocusTrap

createFocusTrap traps keyboard focus inside a given DOM element, cycling through focusable children on Tab / Shift+Tab. It uses a MutationObserver to stay up to date with DOM changes and restores focus to the previously focused element when deactivated.

Ported from solid-focus-trap by Jasmin Noetzli (GiyoMoon), adapted for Solid.js 2.0.

How to use it
import { createFocusTrap } from "@solid-primitives/focus";

const DialogContent: Component<{ open: boolean }> = props => {
  const [contentRef, setContentRef] = createSignal<HTMLElement | null>(null);

  createFocusTrap({
    element: contentRef,
    enabled: () => props.open,
  });

  return (
    <Show when={props.open}>
      <div ref={setContentRef}>
        <button>Close</button>
        <input />
      </div>
    </Show>
  );
};
Props
Prop Type Default Description
element MaybeAccessor<HTMLElement|null> Element to trap focus within.
enabled MaybeAccessor<boolean> true Whether the trap is active.
observeChanges MaybeAccessor<boolean> true Watch for DOM mutations inside the container and refresh focusable elements.
initialFocusElement MaybeAccessor<HTMLElement|null> First focusable element Element to focus when the trap activates.
restoreFocus MaybeAccessor<boolean> true Restore focus to the previously focused element when the trap deactivates.
finalFocusElement MaybeAccessor<HTMLElement|null> Previously focused element Element to focus when the trap deactivates.
onInitialFocus (event: Event) => void Callback when focus moves into the trap. Call event.preventDefault() to cancel.
onFinalFocus (event: Event) => void Callback when focus restores. Call event.preventDefault() to cancel.
Custom initial focus
const [contentRef, setContentRef] = createSignal<HTMLElement | null>(null);
const [inputRef, setInputRef] = createSignal<HTMLElement | null>(null);

createFocusTrap({
  element: contentRef,
  enabled: () => props.open,
  initialFocusElement: inputRef,
});

return (
  <Show when={props.open}>
    <div ref={setContentRef}>
      <button>Close</button>
      <input ref={setInputRef} />
    </div>
  </Show>
);
Preventing focus moves
createFocusTrap({
  element: contentRef,
  onInitialFocus: event => {
    event.preventDefault(); // focus won't move on activation
  },
  onFinalFocus: event => {
    event.preventDefault(); // focus won't restore on deactivation
  },
});

createFocusRestore

createFocusRestore saves the currently focused element while active and restores focus to it once deactivated — without trapping focus or managing tab order. Use it for non-modal surfaces (Popover, Tooltip, Menu) that should return focus to their trigger on close but must not intercept Tab navigation while open. For modal dialogs that need both behaviors, use createFocusTrap's restoreFocus option instead.

How to use it
import { createFocusRestore } from "@solid-primitives/focus";

const Popover: Component<{ open: boolean }> = props => {
  createFocusRestore({ enabled: () => props.open });

  return (
    <Show when={props.open}>
      <div role="dialog">...</div>
    </Show>
  );
};
Props
Prop Type Default Description
enabled MaybeAccessor<boolean> true Whether focus-restore is active.
element MaybeAccessor<HTMLElement|null> document.body Element to dispatch the onFinalFocus event on.
finalFocusElement MaybeAccessor<HTMLElement|null> Previously focused element Element to focus when deactivated.
onFinalFocus (event: Event) => void Callback when focus restores. Call event.preventDefault() to cancel.

Credits

createFocusTrap is ported from solid-focus-trap, part of the corvu UI toolkit by Jasmin Noetzli (GiyoMoon). Licensed under the MIT License.

Changelog

See CHANGELOG.md

Keywords