Solid Primitives (Active Element)
Reactive document.activeElement tracking and focus-state signals for SolidJS.
Repository Health
Technical Analysis
@solid-primitives/active-element gives SolidJS applications a reactive read on browser focus state without hand-wiring focus and blur listeners. It exposes a global reactive accessor for document.activeElement, a per-element focus signal, and a use:focus directive for JSX-first ergonomics, so components can react to focus changes the same way they react to any other Solid signal.
Part of the Solid Primitives monorepo — a curated collection of high-quality, community-maintained building blocks for Solid — it follows the project’s shared conventions for SSR safety, tree-shakeable exports, and thorough test coverage, including dedicated SSR tests that assert every primitive degrades to safe no-ops on the server.
What You Get
- createActiveElement - a reactive Accessor<Element | null> mirroring document.activeElement, updated via capturing focus/blur listeners on window.
- createFocusSignal - a boolean signal for one element’s focus state, accepting either a raw element or a reactive accessor so it re-subscribes when the target changes.
- focus directive - a use:focus JSX directive that reports an element’s active state via callback, avoiding manual signal wiring in components.
- Non-reactive listener helpers - makeActiveElementListener and makeFocusListener for cases where you want the raw event callback instead of a Solid signal.
- SSR-safe no-ops - every primitive checks isServer and returns inert defaults (null, false, no-op cleanup) so server rendering never throws.
Common Use Cases
- Custom focus-ring styling - drive component-level focus rings from createFocusSignal instead of relying on :focus-visible alone.
- Focus-aware analytics - use createActiveElement to log which UI region has focus for accessibility or usage instrumentation.
- Keyboard navigation components - build comboboxes, menus, and command palettes that need to know which item currently holds focus.
- Editor/canvas UIs - track whether a text input or contenteditable region is focused to conditionally show toolbars or overlays.
Under The Hood
Architecture The package is a thin, single-file primitive (src/index.ts) built on two shared primitives from the same monorepo — @solid-primitives/event-listener for capturing focus/blur listeners and @solid-primitives/utils for createHydratableSignal and shared directive/MaybeAccessor types — with all four exports (createActiveElement, createFocusSignal, makeActiveElementListener, makeFocusListener) plus a use:focus directive implemented as plain functions guarded by an early isServer branch. There is no internal state machine or class, just closures composed over window/element listeners; the abstraction the package leans on most heavily is the capturing-listener contract of makeEventListener from event-listener, so a change there would ripple through every export here.
Tech Stack TypeScript across a pnpm workspace of roughly 85 sibling primitive packages, built with Node’s experimental type-stripping via a custom loader (@nothing-but/node-resolve-ts) rather than tsc/tsup directly, tested with Vitest in both client and SSR modes from a shared configs/vitest.config.ts, linted with a flat ESLint config plus Prettier, versioned and published via Changesets, and shipped through GitHub Actions workflows that cover tests, formatting, CodeQL scanning, and dual npm/JSR releases. The package declares solid-js ^1.6.12 as a peer dependency.
Code Quality Two dedicated test files exercise the whole public surface: an index.test.ts that dispatches real FocusEvents inside createRoot to assert reactive behavior and cleanup, and a server.test.ts that explicitly asserts every primitive is a safe no-op under SSR. Source code is fully typed with no use of any, exported functions carry JSDoc with usage examples and doc links, and the monorepo enforces a zero-warnings ESLint policy plus CI-checked formatting.
API Design The API mirrors Solid’s own create*/make* naming conventions, offering three levels of abstraction for the same capability — a raw callback listener, a reactive signal, and a JSX directive — so consumers can pick the ergonomics level that fits their component. Focus tracking itself isn’t a novel concept, but the consistent multi-level API and inline documented examples make it easy to drop into an existing Solid codebase with minimal boilerplate.