Skip to content

Update tabbable and focus-trap dependencies in focus plugin#4737

Merged
calebporzio merged 4 commits intomainfrom
josh/update-tabbable-and-focus
Feb 9, 2026
Merged

Update tabbable and focus-trap dependencies in focus plugin#4737
calebporzio merged 4 commits intomainfrom
josh/update-tabbable-and-focus

Conversation

@joshhanley
Copy link
Collaborator

@joshhanley joshhanley commented Feb 4, 2026

This PR updates the tabbable and focus-trap dependencies in the @alpinejs/focus plugin.

Package Before After
tabbable ^5.3.3 ^6.4.0
focus-trap ^6.9.4 ^8.0.0

Context

A user reported that a governmental security audit flagged these dependencies as outdated. The maintainers of tabbable and focus-trap only support the latest version. Older releases don't receive security fixes, so staying current ensures we receive any future security patches.

Related: #4727

Breaking Changes in Upstream Packages

tabbable 5.x → 6.x

  • IE support dropped: ❌ Not relevant. Alpine already doesn't support IE.
  • displayCheck: 'full' no longer treats detached nodes as visible: ❌ Not relevant. Alpine uses displayCheck: 'none' for focusable() calls.
  • isTabbableRadio() returns fewer elements in nested scenarios: ⚠️ Edge case. Radio buttons in complex nesting might behave differently in focus traps.

focus-trap 6.x → 7.x

  • IE support dropped: ❌ Not relevant.
  • Inherits tabbable v6 displayCheck behaviour: ⚠️ Possible impact. focus-trap uses default displayCheck, not overridden by Alpine.

focus-trap 7.x → 8.x

  • onPostActivate() now called after initial focus is set (previously called before): ✅ Tested. Alpine's .inert modifier uses onPostActivate but wraps it in Alpine.nextTick(), which already deferred execution. All tests pass.

New Features Available

  • Native inert attribute support: elements with HTML inert attribute are properly excluded from tabbable results
  • Better shadow DOM / web component handling
  • isolateSubtrees option in focus-trap (similar to what Alpine does manually with aria-hidden)

Testing

All 23 tests pass, including 3 new tests added to cover upgrade-related scenarios:

  • x-trap works with radio button groups: verifies focus trap works correctly with radio groups
  • $focus.focusables excludes elements with inert attribute: tests new inert attribute support from tabbable v6
  • x-trap handles dynamically added focusable elements: verifies trap adapts when elements are added

joshhanley and others added 4 commits February 5, 2026 08:53
Update `tabbable` from ^5.3.3 to ^6.4.0 and `focus-trap` from ^6.9.4 to ^8.0.0.

These packages only support their most recently published version, so staying
on older versions means no security patches. This addresses concerns raised
in governmental security audits.

Breaking changes in these packages (IE support dropped, displayCheck defaults)
do not affect Alpine's usage patterns.
These tests cover scenarios related to the tabbable/focus-trap upgrade:

- x-trap works with radio button groups
- $focus.focusables excludes elements with inert attribute (new feature)
- x-trap handles dynamically added focusable elements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@calebporzio
Copy link
Collaborator

PR Review: #4737 — Update tabbable and focus-trap dependencies in focus plugin

Type: Dependency upgrade
Verdict: Merge

What's happening (plain English)

  1. Alpine's @alpinejs/focus plugin depends on two packages: tabbable (finds focusable/tabbable elements) and focus-trap (creates keyboard focus traps).
  2. These are currently pinned to old major versions — tabbable 5.x and focus-trap 6.x.
  3. The upstream maintainers only support the latest major version, so the old versions don't receive security patches.
  4. A user's government security audit flagged these as outdated.
  5. This PR bumps to tabbable 6.x and focus-trap 8.x, and adds 3 new tests.

The upgrade is clean — Alpine's usage of these libraries is straightforward (focusable() with displayCheck: 'none', createFocusTrap() with standard options), and nothing in the breaking changes between versions affects Alpine's usage.

Other approaches considered

  1. Pin to exact patch versions — would prevent accidental future breakage but would also prevent receiving patch fixes. The current ^ range is fine and matches the project's existing pattern.
  2. Do nothing — the current versions work, but they won't receive security patches. Not a good long-term position.
  3. Vendor the code — overkill. These are well-maintained, focused libraries.

The dependency bump is the correct and simplest approach.

Changes Made

  • Fixed package-lock.json name field: the contributor accidentally changed it from "alpine" to "alpine--update-tabbable-and-focus" (likely from their branch setup). Restored to "alpine".

Test Results

All 23 focus tests pass (22 existing + 3 new from this PR). CI also passes.

Regression verification:

  • The $focus.focusables excludes elements with inert attribute test correctly fails on old tabbable 5.x (returns 3 focusables instead of 2) and passes on new tabbable 6.x. This is the strongest test — it validates a real behavior difference from the upgrade.
  • The radio button and dynamic element tests pass on both old and new versions. They're fine as general coverage but aren't upgrade-specific regression tests.

Code Review

Tests: The inert attribute test (focus.spec.js:426) is the only test that actually validates the upgrade. The other two (radio buttons at line 401, dynamic elements at line 442) test existing behavior that works on both old and new versions. They're harmless but the contributor's framing that they "cover upgrade-related scenarios" is slightly misleading — only the inert test does.

Size impact: The focus plugin CDN bundle grows from 4.6 KB to 7.9 KB minified+gzipped (~72% increase). This is because tabbable v6 adds inert-aware selector logic. Worth noting but acceptable for a plugin that's opt-in.

No Alpine source changes: The PR only touches package.json, package-lock.json, and test files. The focus plugin source code (packages/focus/src/index.js) is untouched, which is correct — no API changes needed.

Security

This PR directly addresses a security concern — keeping dependencies current so they receive patches. No new security concerns introduced.

Verdict

Clean dependency bump. Tests pass, CI passes, no breaking changes that affect Alpine's usage. The contributor did thorough research on upstream breaking changes and documented them well. The lockfile name field issue was minor and has been fixed.

One thing to be aware of: the 4.6 KB → 7.9 KB size increase in the focus plugin bundle. If that matters, it's worth knowing about, but it's a reasonable tradeoff for staying on supported versions.

Merge it.


Reviewed by Claude

@calebporzio calebporzio merged commit 776c719 into main Feb 9, 2026
2 checks passed
@calebporzio calebporzio deleted the josh/update-tabbable-and-focus branch February 9, 2026 01:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants