tailwindlabs/headlessui · warning
There are no focusable elements inside the <FocusTrap />
Error message
There are no focusable elements inside the <FocusTrap />
What it means
This warning is emitted by Headless UI React's <FocusTrap /> (used internally by <Dialog>) during initial focus when it cannot find any focusable element inside the trap container. The useInitialFocus hook tries the `initialFocus` prop/ref, then the container's first focusable element; if every strategy fails it warns and the dialog opens with nothing focused, which breaks keyboard accessibility.
Source
Thrown at packages/@headlessui-react/src/components/focus-trap/focus-trap.tsx:402
return // Worked, bail
}
}
// Try to focus the first focusable element.
else if (focusIn(containerElement!, Focus.First) !== FocusResult.Error) {
return // Worked, bail
}
// Try the fallback
if (initialFocusFallback?.current) {
focusElement(initialFocusFallback.current)
if (ownerDocument?.activeElement === initialFocusFallback.current) {
return // Worked, bail
}
}
// Nothing worked
console.warn('There are no focusable elements inside the <FocusTrap />')
}
previousActiveElement.current = ownerDocument?.activeElement as HTMLElement
})
}, [initialFocusFallback, enabled, features])
return previousActiveElement
}
function useFocusLock(
features: FocusTrapFeatures,
{
ownerDocument,
container,
containers,
previousActiveElement,
}: {
ownerDocument: Document | nullView on GitHub (pinned to eea57cf46f)
Solutions
- Add at least one focusable element inside DialogPanel, e.g. action buttons or a close <button>.
- Pass initialFocus pointing to a mounted element: <Dialog initialFocus={cancelRef}> (or initialFocus on FocusTrap).
- Ensure the ref target is not null: use a callback/useRef assigned to an element that always renders, and check it isn't disabled or hidden.
- If content is async, render the Dialog only when content is ready, or keep a persistent focusable fallback (e.g. tabindex={-1} on the panel wrapper and pass it as initialFocus).
Example fix
// before
<Dialog onClose={close}>
<DialogPanel>
<p>Just text, nothing focusable</p>
</DialogPanel>
</Dialog>
// after
function Example() {
const cancelRef = useRef(null)
return (
<Dialog onClose={close} initialFocus={cancelRef}>
<DialogPanel>
<p>Are you sure?</p>
<button ref={cancelRef} onClick={close}>Cancel</button>
<button onClick={confirm}>Confirm</button>
</DialogPanel>
</Dialog>
)
} Defensive patterns
Strategy: validation
Validate before calling
// React: check before opening / at render
function hasFocusable(root: HTMLElement | null): boolean {
if (!root) return false
return root.querySelector(
'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
) !== null
}
// in the component
const panelRef = useRef<HTMLDivElement>(null)
const open = () => {
if (!cancelRef.current && !hasFocusable(panelRef.current)) {
panelRef.current?.setAttribute('tabindex', '-1') // fallback focus target
}
setOpen(true)
} Type guard
function isFocusable(el: Element | null): el is HTMLElement {
if (!el) return false
return (
!el.hasAttribute('disabled') &&
el.getAttribute('aria-hidden') !== 'true' &&
(el.matches('a[href],button,input,select,textarea,[tabindex]') ||
el.getAttribute('contenteditable') === 'true')
)
} Prevention
- Always include at least one focusable control inside DialogPanel.
- Guard initialFocus refs: `initialFocus={ref.current ? ref : undefined}` or ensure the element always renders.
- Avoid conditionally unmounting the initialFocus target.
- Test dialogs with keyboard only (Tab/Esc) in CI or manual QA.
When it happens
Trigger: Rendering a <Dialog> whose panel contains no focusable elements (no buttons, inputs, links, or [tabindex] elements) while initialFocus is unset, or passing an initialFocus ref whose current target is null/detached/disabled at mount time. Content that renders empty on first paint (async data) also triggers it.
Common situations: Dialogs with only text and a close button rendered conditionally after data loads; initialFocus refs attached to elements inside a v-if/false branch or removed by state changes; elements with `disabled`, `hidden`, `aria-hidden`, or `display:none`; SSR timing issues where the ref isn't populated when the trap activates.
Related errors
- There are no focusable elements inside the <FocusTrap />
- Invalid role [${role}] passed to <Dialog />. Only `dialog` a
- You have to provide an `open` and an `onClose` prop to the `
- You provided an `onClose` prop to the `Dialog`, but forgot a
- You provided an `open` prop to the `Dialog`, but forgot an `
AI-assisted analysis of tailwindlabs/headlessui@eea57cf46f (2026-08-28).
Data as JSON: /api/errors/b3a5d6006c07d4eb.
Report an issue: GitHub.