tabbable: the utility that decides what the Tab key will reach
Find descendants of a DOM node that are in the tab order
At a glance
- What is it?
- tabbable is a small dependency free JavaScript library that returns the DOM nodes inside a container that are actually in the tab order, applying the rules about hidden elements, disabled fields, radio groups and inert containers that browsers apply silently. It is the piece the focus trap libraries use to know where focus is allowed to go.
- Who is it for?
- Adopt tabbable if you build a focus trap, a menu, a dialog or any component that has to know which elements the user can reach with the keyboard, and you would rather not reimplement the visibility, disabled and radio group rules that browsers apply without telling anyone.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the browser hides, the library has to model
The library exists because tabbing behaviour is mostly invisible. A DOM node can look perfectly present to your code and still be unreachable by the Tab key, and the rules that decide this are spread across the HTML spec and browser behaviour. tabbable returns an array of all tabbable DOM nodes within a containing node, and the README is upfront that all carries caveats you learn by reading it. The library comes from the focus-trap organisation, which is the signal about its use case: modal dialogs, overlays and menus need to know the set of focusable elements inside a container, and getting that set wrong means either trapping focus on a hidden element or letting it escape. The stated goals are accuracy as far as is reasonable, no dependencies, small size and speed, which is a sensible profile for a utility that every focus management library ends up depending on.
What counts as tabbable, and the eleven ways to be excluded
The inclusion list is short and concrete. Button, input, select and textarea elements qualify, as do anchor and area elements with an href attribute, audio and video elements with controls, the first summary element directly under a details element, a details element that has no summary, elements with a contenteditable attribute, and anything with a non-negative tabindex attribute. Then the exclusions apply, and this is where the work is. A node is dropped if it has a negative tabindex, a disabled attribute, if it or an ancestor is hidden with display: none, if it carries visibility: hidden or visibility: collapse, if it is nested under a closed details element other than the first summary, if it is a radio input in a group where another radio is checked, if it is a form field inside a disabled fieldset, or if it is inert or sits inside an inert container. The inert check is limited to browsers that support the attribute, and the README notes it is not yet supported on Firefox and Safari.
Ordering puts positive tabindex first
The array is ordered, and the ordering rules are stated as two phases. Nodes with a positive tabindex come first, ordered by ascending tabindex and then by source order. After that come nodes with a zero tabindex and elements that receive focus by default without a positive tabindex, in source order. The practical consequence is that the returned array is the sequence in which the Tab key moves, not merely a set of elements, which is what makes the result usable directly for a focus trap that has to move focus in a defined order. The inverse option also exists in spirit: if you think a node should be included but it is not, the README says you can add tabindex="0" to include it deliberately, and if it is included but you do not want it, tabindex="-1" excludes it. It also notes that doing so produces more consistent cross-browser behaviour, which is a small argument for setting tabindex explicitly rather than relying on the computed answer.
focusable is a different set from tabbable
The package exports four functions and the distinction between them matters for the traps that use them. tabbable returns the ordered list of nodes in the tab order. isTabbable answers the same question for a single node, returning a boolean. focusable returns the nodes that can receive focus at all, in DOM order, and the README warns that this will not match the order tabbable returns. isFocusable is the single node version of that. The README spells out the relationship in one sentence: all tabbable elements are focusable, but not all focusable elements are tabbable, and elements with tabindex="-1" are the standard example, focusable by script but skipped by the Tab key. A fourth export, getTabIndex, returns the negative, zero or positive tab index of a node in the DOM, with exceptions made where browsers are inconsistent around elements such as audio.
includeContainer and inert decide the edges
Two option shapes run through the API. includeContainer defaults to false, and setting it to true adds the container itself to the returned array when the container is tabbable. The README attaches a condition to that option that is easy to miss: whether includeContainer is true or false, if the container is inert then none of its children, however deep, count as tabbable. The same rule is restated for focusable. The remaining options are shared across the functions, and the README links to a display check section describing how the hidden element rule can be modified, which matters when your component renders in a hidden container that is later shown. The options object also carries a reference to browser inconsistencies, since the library has to make one decision where engines disagree.
Installing and calling it
The library is a plain npm package with no runtime dependencies:
npm install tabbableThe API is a named import and a call with a container and an optional options object:
import { tabbable } from 'tabbable';
tabbable(container, [options]);The container argument is required and must be a Node. The README also flags an edge case for very old browsers: they may need a polyfill for the CSS.escape API for tabbable to work properly with radio buttons whose name attributes contain special characters. The browser support section says support is as old and as broad as reasonably possible, excluding browsers out of support or with nearly no user base, with focus on desktop browsers and particularly Chrome, Edge, Firefox, Safari and Opera, and it notes that mobile browsers are not officially tested.
Rollup builds, Jest and Cypress tests, changesets for releases
The repository shows a mature small library rather than a weekend project. package.json compiles three bundles, ESM, CommonJS and UMD, through Rollup with Babel, and the files list ships dist, src, the TypeScript declarations and the usual licence and changelog files. The test script runs a format check, lint, a type check with tsc, Jest for unit tests and Cypress for end to end tests, and the package is marked with sideEffects false so bundlers can drop unused imports. Releases go through changesets, with a release script that builds and publishes, and the tree also holds a SECURITY.md, CONTRIBUTING.md, an eslint flat config, a Prettier setup and a CHANGELOG.md. Version 6.5.0 was released on 2026-06-20, preceded by 6.4.0 at the end of 2025, and the last push was on 2026-10-21, so the project is still moving.
Editorial conclusion
Adopt tabbable if you build a focus trap, a menu, a dialog or any component that has to know which elements the user can reach with the keyboard, and you would rather not reimplement the visibility, disabled and radio group rules that browsers apply without telling anyone. Do not adopt it expecting perfect fidelity with every browser quirk, because the README states that performance work and old browser support often conflict, so the library cannot always use the most modern APIs, and inert is not yet supported on Firefox and Safari. Verify first that the ordering matches what you need, since focusable returns DOM order while tabbable returns tab order, check the includeContainer option against your own container element, and add tabindex attributes deliberately when the automatic answer is wrong, which the README recommends for more consistent cross-browser behaviour.
Frequently asked questions
What does the tabbable library do?
It returns an array of the tabbable DOM nodes within a containing node, in the order the Tab key would reach them. It also exports isTabbable, focusable, isFocusable and getTabIndex for single node and focus only variants.
How do I install tabbable and use it?
Install it with npm install tabbable and import the named function, calling tabbable(container, [options]) with the container node and optional options. It ships with no runtime dependencies.
What is the difference between tabbable and focusable?
Every tabbable element is focusable, but not every focusable element is tabbable, for instance elements with tabindex="-1" which can be focused by script but are skipped by the Tab key. The focusable function also returns nodes in DOM order rather than tab order.
How does tabbable decide that an element is not tabbable?
Elements are excluded when they have a negative tabindex or a disabled attribute, when they or an ancestor are hidden with display: none, when they carry visibility hidden or collapse, when they are inside a closed details element, when they are an unchecked radio in a group with a checked radio, when they sit in a disabled fieldset, or when they are inert.
Does tabbable work with inert containers?
Yes, but only in browsers that support the inert attribute. The README notes that if the container is inert, none of its children count as tabbable or focusable whatever includeContainer is set to, and that inert is not yet supported on Firefox and Safari.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/focus-trap-tabbable)Was this useful?