Skip to content
Updated May 2026Edit this page ↗

Focus Management

TermUI ships four hooks for managing keyboard focus. Together they handle everything from basic input activation to fully accessible keyboard navigation.

Hooks overview

HookRole
useFocusManagerOwns focus state for a subtree — mount once near the root
useFocusReads and controls focus for a single widget
useFocusTrapConfines Tab/Shift+Tab to a list of IDs (for modals)
useKeyboardNavigationArrow key navigation for lists and menus

useFocusManager

Provides the focus context for all descendant components. Returns { focused, focus, blur, FocusContext } — wrap children in FocusContext.Provider to share state.

TYPESCRIPT
import { useFocusManager } from '@termuijs/jsx'
 
function Form() {
    const { focused, focus, blur, FocusContext } = useFocusManager()
 
    return (
        <FocusContext.Provider value={{ focused, focus, blur }}>
            <NameField />
            <EmailField />
            <SubmitButton />
        </FocusContext.Provider>
    )
}

focused is the ID string of the currently focused widget, or null if nothing is focused.

API

PropertyTypeDescription
focusedstring \| nullID of the focused widget
focus(id)(id: string) => voidSet focus to the given ID
blur()() => voidClear focus
FocusContextContext objectPass to Provider to share state with descendants

useFocus

Reads and controls focus for a single widget. Call inside any component that wants to participate in focus.

TYPESCRIPT
import { useFocus } from '@termuijs/jsx'
 
function NameField() {
    const { isFocused, focus, blur } = useFocus({ id: 'name-field', autoFocus: true })
 
    return (
        <Box border={isFocused ? 'double' : 'single'} borderColor={isFocused ? 'cyan' : 'brightBlack'}>
            <TextInput placeholder="Name" />
        </Box>
    )
}

Options

OptionTypeDefaultDescription
idstringRequiredUnique ID within the focus manager
autoFocusbooleanfalseImmediately focus this widget on mount

Returns

PropertyTypeDescription
isFocusedbooleanWhether this widget currently has focus
focus()() => voidRequest focus
blur()() => voidRelease focus

useFocusTrap

Traps Tab and Shift+Tab within a list of widget IDs. When Tab is pressed on the last item it wraps to the first; Shift+Tab on the first wraps to the last.

Use inside modals, dialogs, or any overlay that should capture all keyboard navigation.

TYPESCRIPT
import { useFocusTrap } from '@termuijs/jsx'
 
function ConfirmModal() {
    useFocusTrap(['modal-yes', 'modal-no'])
 
    return (
        <Box border="double">
            <Text>Are you sure?</Text>
            <FocusableButton id="modal-yes" label="Yes" />
            <FocusableButton id="modal-no" label="No" />
        </Box>
    )
}

The trap is removed automatically when the component unmounts.

API

TYPESCRIPT
useFocusTrap(ids: string[]): void

ids should be the ordered list of focusable widget IDs. Tab cycles forward through the list; Shift+Tab cycles backward.


useKeyboardNavigation

Provides standard arrow-key navigation for lists and menus. Handles /, Home, End, PageUp, PageDown.

TYPESCRIPT
import { useKeyboardNavigation } from '@termuijs/jsx'
 
function Menu({ items }: { items: string[] }) {
    const { selectedIndex, select } = useKeyboardNavigation({
        items,
        loop: true,
        pageSize: 10,
    })
 
    return (
        <col>
            {items.map((item, i) => (
                <Text key={item} bold={i === selectedIndex}>
                    {i === selectedIndex ? '▶ ' : '  '}{item}
                </Text>
            ))}
        </col>
    )
}

Options

OptionTypeDefaultDescription
itemsT[]RequiredThe list of items to navigate
loopbooleanfalseWrap around at list boundaries
pageSizenumber10How many items PageUp/PageDown skip
initialIndexnumber0Starting position

Returns

PropertyTypeDescription
selectedIndexnumberCurrent position in the list
selectedItemTThe item at selectedIndex
select(index)(i: number) => voidProgrammatically jump to an index

Full example: accessible form

TYPESCRIPT
function LoginForm() {
    const { focused, focus, blur, FocusContext } = useFocusManager()
    useFocusTrap(['login-user', 'login-pass', 'login-submit'])
 
    return (
        <FocusContext.Provider value={{ focused, focus, blur }}>
            <Box border="single" padding={1} flexDirection="column">
                <Text bold>Sign In</Text>
                <LabeledInput id="login-user" label="Username" autoFocus />
                <LabeledInput id="login-pass" label="Password" password />
                <SubmitButton id="login-submit" />
            </Box>
        </FocusContext.Provider>
    )
}

See also

  • useKeymap — declarative key bindings per component
  • UI Inputs — PasswordInput, NumberInput, PathInput built on this focus system