Skip to content

Focus Trap

Constrains focus within a subtree when active. Used by modals and overlays to prevent focus from escaping to background content.

Usage

ui.focusTrap(
  { id: "modal-trap", active: state.modalOpen },
  [
    ui.text("Are you sure?"),
    ui.button({ id: "confirm", label: "Confirm" }),
    ui.button({ id: "cancel", label: "Cancel" }),
  ]
)

Props

Prop Type Default Description
id string required Unique identifier for the trap
active boolean required Whether focus is currently trapped
initialFocus string - ID of element to focus when trap activates
returnFocusTo string - ID of element to focus when trap deactivates
key string - Reconciliation key

Behavior

When active is true:

  • Tab/Shift+Tab cycles only through focusable elements inside the trap
  • Focus cannot escape to elements outside the trap
  • Attempting to Tab past the last element wraps to the first
  • Attempting to Shift+Tab before the first element wraps to the last

When active becomes false:

  • Focus is restored to returnFocusTo if specified
  • Normal Tab navigation resumes

Initial Focus

Specify which element receives focus when the trap activates:

ui.focusTrap({
  id: "confirm-dialog",
  active: state.showConfirm,
  initialFocus: "cancel",  // Focus "Cancel" by default
}, [
  ui.text("Delete this item?"),
  ui.button({ id: "delete", label: "Delete" }),
  ui.button({ id: "cancel", label: "Cancel" }),
])

Focus Restoration

Restore focus to a specific element when the trap closes:

ui.focusTrap({
  id: "settings",
  active: state.showSettings,
  returnFocusTo: "settings-btn",  // Return to the button that opened it
}, [...])

Focus traps are typically used with modals:

state.showModal && ui.layer({
  id: "modal-layer",
  backdrop: "dim",
  content: ui.focusTrap({
    id: "modal-trap",
    active: true,
    initialFocus: "ok",
    returnFocusTo: "open-modal-btn",
  }, [
    ui.box({ title: "Settings", p: 2 }, [
      // Modal content
      ui.row({ gap: 2 }, [
        ui.button({ id: "ok", label: "OK" }),
        ui.button({ id: "cancel", label: "Cancel" }),
      ]),
    ]),
  ]),
})

Nested Traps

When traps are nested, only the innermost active trap is effective:

ui.focusTrap({ id: "outer", active: true }, [
  // Outer content
  ui.focusTrap({ id: "inner", active: state.showInner }, [
    // Inner content - focus trapped here when showInner is true
  ]),
])