# Hooks

- Page: https://jiguang132.com/resources/astryx/docs/hooks
- Resource: Astryx (https://jiguang132.com/resources/astryx.md)
- All guides: https://jiguang132.com/resources/astryx/docs.md

Imperatively announces a message to screen readers through a visually-hidden live region. Use it for state that is only conveyed visually; search result counts, "no results", loading and saved confirmations, validation errors (WCAG 4.1.3 Status Messages). The polite and assertive regions are created empty on first use and stay mounted, which is what makes announcements reliable: most screen readers ignore a live region that is inserted together with its content. Each message is cleared a couple of seconds after it is announced so stale status does not linger in the accessibility tree.

## Installation

```bash
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
```

Requires React 19+, base CSS and a Theme provider. See [Setup](/resources/astryx/docs/getting-started).

## useAnnounce

Imperatively announces a message to screen readers through a visually-hidden live region. Use it for state that is only conveyed visually; search result counts, "no results", loading and saved confirmations, validation errors (WCAG 4.1.3 Status Messages). The polite and assertive regions are created empty on first use and stay mounted, which is what makes announcements reliable: most screen readers ignore a live region that is inserted together with its content. Each message is cleared a couple of seconds after it is announced so stale status does not linger in the accessibility tree.

### Returns

```json
[
  {
    "name": "announce",
    "type": "(message: string, politeness?: \"polite\" | \"assertive\") => void",
    "description": "Speaks a message through the shared live region. Politeness defaults to \"polite\"; an empty message clears any lingering status instead of announcing."
  }
]
```

### Usage guidance

- Reach for this instead of hand-rolling an aria-live div; a region rendered with its content usually does not announce at all.
- Keep "polite" for status and result counts; reserve "assertive" for errors and time-sensitive alerts, since it interrupts whatever the screen reader is saying.
- Announce the outcome, not the interaction; "12 results" rather than "search ran".
- Avoid: Announce content that is already visible and correctly labeled in the DOM; that doubles up for screen reader users.

## useClickableContainer

Makes a container element clickable while preserving nested interactive element behavior. Solves the "nested interactive elements" problem: when a card is clickable but contains buttons/links, clicking those should NOT trigger the card's action. Detects interactive ancestors between the click target and the container, and ignores text selections. Supports href navigation (including middle-click and Ctrl/Cmd+click for new tabs).

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseClickableContainerOptions | Configuration object for the clickable container. |
| options.containerRef | RefObject<HTMLElement \| null> | Ref to the outer container element. |
| options.interactiveRef | RefObject<HTMLElement \| null> | Ref to the primary interactive element inside (link, button). If no onClick or href is provided, clicks are proxied to this element. |
| options.onClick | (event: MouseEvent<HTMLElement>) => void | Click handler fired when the container surface (not a nested interactive element) is clicked. |
| options.href | string | Navigation URL. When provided, clicking the container navigates to this URL. |
| options.target | string | Link target (e.g., '_blank'). Used with href for navigation behavior. |
| options.disabled | boolean | Whether the container is disabled. |

### Returns

```json
[
  {
    "name": "onClick",
    "type": "(event: MouseEvent<HTMLElement>) => void",
    "description": "Click handler to attach to the container element."
  },
  {
    "name": "onMouseUp",
    "type": "(event: MouseEvent<HTMLElement>) => void",
    "description": "Mouse up handler to attach to the container (handles middle-click navigation for href)."
  }
]
```

### Usage guidance

- Attach both onClick and onMouseUp to the container element for full click handling including middle-click.
- Use inside ClickableCard or SelectableCard for the standard card interaction pattern.
- Avoid: Use when the entire container is a single interactive element; just use a <button> or <a> directly.

## useClipboard

Copy-to-clipboard behavior: the clipboard write, a transient isCopied flag with its own reset timer, and an optional polite screen-reader announcement. Extracted so every copy affordance is a thin control over one implementation instead of re-deriving the timer and announcement. Rapid re-copies restart the reset timer so the confirmation always lasts the full duration, and the timer is cleaned up on unmount. CodeBlock and Timestamp build their built-in copy buttons on it; reach for it directly when building a copy affordance that is not a plain icon button (a menu item, a labeled text button, a copy-on-click value chip).

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseClipboardOptions | Configuration object. |
| options.announce | string | Message announced to a polite live region on a successful copy. A swapped aria-label alone is not reliably announced, so pass the localized confirmation (e.g. "Copied") to have it spoken. Omit to skip the announcement. |
| options.resetAfterMs | number | Milliseconds isCopied stays true after a successful copy before reverting. |

### Returns

```json
[
  {
    "name": "copy",
    "type": "(text: string) => Promise<boolean>",
    "description": "Writes text to the clipboard. On success flips isCopied to true, announces the configured message, restarts the reset timer, and resolves true. A clipboard rejection is a silent no-op that leaves the copied state unchanged and resolves false."
  },
  {
    "name": "isCopied",
    "type": "boolean",
    "description": "True for resetAfterMs after the most recent successful copy, then reverts. Drive the copied confirmation (e.g. a copy → check icon flip) off this."
  }
]
```

### Usage guidance

- Drive the copied confirmation (copy → check icon, label swap) off the returned isCopied flag rather than tracking your own state.
- Pass a localized announce message so the copy is spoken by screen readers; swapping the button aria-label alone is not reliably announced.
- For the common compact icon copy button, render a ghost IconButton with a "Copy" tooltip and wire onClick to copy(); the tooltip stays "Copy" and the icon flip is the confirmation.
- Avoid: Track a separate copied useState alongside the hook; isCopied already reflects the copied window and resets itself.

## useContainerReveal

A headless hover/focus reveal primitive. Gives a container a scoped trigger that reveals (or conceals) content inside it when the container is hovered or receives keyboard focus: the classic "row actions appear on hover" pattern. The reveal is CSS-only: no hover state lives in React and hovering never triggers a re-render. The caller authors no StyleX for the reveal itself; the hook hands out the container and content styles, and a nested container shadows its ancestor, so nested containers never leak hover/focus into one another. Accessible by construction: revealed content is visually hidden at rest with position and opacity (never display:none), so it stays mounted, keeps its place in the tab order, and is announced to assistive technology; it reveals on :focus-within so keyboard users see it when tabbing in, stays visible on touch (never gated behind hover on coarse pointers), and honors prefers-reduced-motion.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseContainerRevealOptions | Configuration object for the reveal container. Optional. |
| options.isEnabled | boolean | When false the hook is inert: the container gets no styles and content getters return no styles, so content is always shown. Read on every render, so a component can flip it after mount (e.g. revealOn === "hover"). |

### Returns

```json
[
  {
    "name": "getContainerProps",
    "type": "(options?: ContainerRevealOptions) => {className?: string; style?: CSSProperties}",
    "description": "Spread onto the container whose hover/focus-within drives the reveal. Accepts hoverDelay (ms the pointer must dwell before the reveal starts: a hover-intent gate like Tooltip's and HoverCard's delay, so a cursor sweeping across a list leaves nothing painted behind it) and forceState (\"active\" | \"inactive\") to pin the trigger state when a caller owns it: a motion gate, a scroll, or a row whose menu is open. \"inactive\" still yields to keyboard focus and coarse pointers."
  },
  {
    "name": "getContentRevealProps",
    "type": "(options?: ContentRevealOptions) => {className?: string; style?: CSSProperties}",
    "description": "Spread onto each revealed / concealed child. Accepts isRevealInverted to conceal-on-hover instead of reveal-on-hover, isLayoutPreserved to reserve the layout box while hidden (opacity-only) and avoid layout shift, and forceVisibility (\"shown\" | \"hidden\") to pin this one element's appearance whatever the container is doing. \"hidden\" yields to focus."
  }
]
```

### Usage guidance

- Destructure getContainerProps and getContentRevealProps; spread getContainerProps() on the container (via mergeProps with your own stylex.props) and getContentRevealProps() on the content to reveal.
- Use for secondary affordances: reveal-on-hover row actions (edit/copy/remove on list or table rows) and overlay controls on a card or media tile (e.g. Thumbnail's remove button).
- Gate the reveal with isEnabled when a consumer prop decides whether content is revealed on hover or always shown; it can change at any time.
- Pass isLayoutPreserved for absolutely-positioned or overlay content to reserve its box and avoid layout shift when it appears.
- Set a hoverDelay (100-250ms) on rows in a long list, so a cursor travelling across the list does not light up every row it passes; keyboard and touch still reveal immediately.
- Reach for forceState when something other than the pointer owns the interaction (a drag, a scroll or motion gate, an open row menu), and forceVisibility when just one element should ignore the container.
- Avoid: Reach past the API into the hook's private custom properties (--_reveal-opacity and friends) to suppress a reveal; use forceState / forceVisibility, which survive a rename.
- Avoid: Use it to hide content that must always be discoverable; keep essential actions visible instead of gating them behind hover.

## useDevWarning

Fires a dev-only "Component: message" console warning once per mount while the condition holds. It is the render-safe way for a component to flag misuse: warning straight from the render body repeats on every render, and gating it with state adds a re-render, so this uses a ref and an effect instead. For a warning outside a component, use the imperative devWarn utility.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| component | string | Component or hook name, used as the message prefix. |
| message | string | What went wrong and how to fix it. |
| condition | boolean | Whether to warn. |

### Returns

```json
[]
```

### Usage guidance

- Say what is wrong and what the builder should do instead; the message is the whole value of the warning.
- Warn about combinations the types cannot express, such as two mutually exclusive props being set together.
- Avoid: Use it for anything a user could see or for runtime error handling; it is stripped from production builds.

## useEntryAnimation

Returns a StyleX style for animating an element on mount. Only animates when the element is dynamically inserted after the initial page paint; elements rendered on page load are not animated. Uses Astryx motion tokens (duration, easing) for consistent animation timing. Requires "use client"; does not support SSR.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| preset | 'slideDown' \| 'slideUp' \| 'fadeIn' \| 'scaleIn' | Animation preset to apply on mount. |

### Returns

```json
[
  {
    "name": "entryStyle",
    "type": "StyleXStyles | null",
    "description": "A StyleX style object for the entry animation, or null if the element was rendered on initial page load (no animation needed)."
  }
]
```

### Usage guidance

- Use for conditionally rendered elements like validation messages, toasts, or expanding sections.
- Spread the returned style into stylex.props() alongside other styles.
- Avoid: Use for elements that should be visible on initial page load; they will not animate.

## useFocusTrap

Traps focus within a container element following the WAI-ARIA dialog focus trap pattern. Listens to focus events on the document and redirects focus back into the container if it escapes via keyboard navigation. Handles both Tab and Shift+Tab wrapping. When the trap deactivates or unmounts, focus is restored to the element that was focused before activation, unless focus was already moved elsewhere or never entered the trap (so popups that keep focus on their trigger, like comboboxes, are unaffected). Mouse clicks outside the container are not intercepted; use a light-dismiss handler for that.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseFocusTrapOptions | Configuration object for the focus trap. |
| options.isActive | boolean | Whether the focus trap is currently active. |
| options.onEscape | () => void | Callback when Escape key is pressed inside the trapped container. |

### Returns

```json
[
  {
    "name": "containerRef",
    "type": "React.RefObject<HTMLElement | null>",
    "description": "Ref to attach to the container element that should trap focus."
  },
  {
    "name": "focusFirst",
    "type": "() => void",
    "description": "Focuses the first focusable element inside the container."
  }
]
```

### Usage guidance

- Call focusFirst() when opening a dialog/modal to move focus into the trapped region.
- Provide an onEscape callback to close the dialog when Escape is pressed.
- Rely on the built-in focus restoration on close; only add your own onHide focus handling when you need to send focus somewhere other than the previously-focused element.
- Avoid: Use on non-modal content like tooltips or dropdowns; those need light-dismiss, not focus trapping.

## useGridFocus

Manages keyboard navigation within a 2D grid following the WAI-ARIA grid pattern. Supports arrow keys for cell-to-cell navigation, Home/End for row boundaries, Ctrl+Home/Ctrl+End for grid boundaries, and Page Up/Down for custom callbacks (e.g., month navigation in calendars). Boundary navigation callbacks allow cross-grid navigation.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseGridFocusOptions | Configuration object for grid focus behavior. |
| options.columns | number | Number of columns in the grid. Used for up/down navigation (moves by this many cells). |
| options.cellSelector | string | Selector for cells within the grid. Should match ALL cell positions in DOM order (including disabled/empty) so grid geometry is preserved. |
| options.isCellFocusable | (cell: HTMLElement) => boolean | Predicate for whether a matched cell can receive focus. Omit to treat every matched cell as focusable. |
| options.getFocusTarget | (cell: HTMLElement) => HTMLElement \| null | Resolves the element to focus for a cell, e.g. a button inside a role="gridcell" wrapper. Omit to focus the cell itself. |
| options.onNavigateBefore | (column: number, offset: number) => void | Callback when navigation would go before the first cell. Receives the column index and offset (1 for horizontal, columns for vertical). |
| options.onNavigateAfter | (column: number, offset: number) => void | Callback when navigation would go after the last cell. Receives the column index and offset. |
| options.onPageUp | () => void | Callback for Page Up key (e.g., navigate to previous month in calendars). |
| options.onPageDown | () => void | Callback for Page Down key (e.g., navigate to next month in calendars). |
| options.hasRovingTabIndex | boolean | Own a single roving tab stop across the grid: one focusable cell (its resolved focus target) carries tabindex="0", the rest -1. Stamped/repaired on render and moved with arrow navigation. Attach the returned handleFocus to the container onFocus. |

### Returns

```json
[
  {
    "name": "gridRef",
    "type": "React.RefObject<HTMLElement | null>",
    "description": "Ref to attach to the grid container element."
  },
  {
    "name": "handleKeyDown",
    "type": "(e: React.KeyboardEvent) => void",
    "description": "Key down handler to attach to the grid container."
  },
  {
    "name": "handleFocus",
    "type": "(e: React.FocusEvent) => void",
    "description": "Focus handler for the grid container. Keeps the roving tab stop in sync when hasRovingTabIndex is enabled; a no-op otherwise, so always safe to attach."
  },
  {
    "name": "focusCell",
    "type": "(index: number) => void",
    "description": "Focus a specific cell by index (clamped to valid range)."
  },
  {
    "name": "focusFirst",
    "type": "() => void",
    "description": "Focus the first focusable cell in the grid."
  },
  {
    "name": "focusLast",
    "type": "() => void",
    "description": "Focus the last focusable cell in the grid."
  }
]
```

### Usage guidance

- Use for calendar date grids: wire onPageUp/onPageDown to month navigation and onNavigateBefore/onNavigateAfter for cross-month arrow key navigation.
- Attach both gridRef and handleKeyDown to the grid container element.
- For roving-tabindex grids (e.g. Calendar), set hasRovingTabIndex: true and attach handleFocus to the container onFocus; seed one focus target with tabindex=0 and the hook repairs and moves it.
- Avoid: Use for simple linear lists; prefer useListFocus for 1D navigation.

## useHotkeys

Registers global keyboard shortcuts with a single window keydown listener per hook instance. Handlers live in a ref, so re-renders never re-subscribe. Skips events from typing targets (input, textarea, select, contenteditable) unless allowInInputs, skips defaultPrevented events, and calls preventDefault() on match. SSR-safe.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| hotkeys | Hotkey[] | Shortcut registrations. Each entry: `{ keys, onPress, allowInInputs?, isDisabled? }`. `keys` is a "+"-separated combo like "mod+k", "shift+/", "escape"; "mod" is ⌘ on macOS and Ctrl elsewhere. `onPress` receives the KeyboardEvent after preventDefault(). `allowInInputs` (default false) lets the hotkey fire while typing in inputs/textareas/selects/contenteditable. `isDisabled` temporarily disables the entry. |

### Returns

```json
[]
```

### Usage guidance

- Use for app-level shortcuts like command palettes (mod+k), help overlays (shift+/), and navigation keys.
- Pair with the Kbd component to display the same combo you register; both resolve "mod" per platform identically.
- Use isDisabled to suspend shortcuts while a modal or wizard owns the keyboard, instead of unmounting the hook.
- Avoid: Use for focus-scoped keyboard navigation inside a widget; use useListFocus or useGridFocus instead.

## useImageMode

Detects whether an image is predominantly dark or light by sampling pixels via OffscreenCanvas. Uses APCA perceptual lightness (sRGB linearization + power curve) for accurate detection, especially on saturated colors. Runs entirely off the paint path: no visible canvas, no layout thrash. Supports regional sampling for detecting luminance where text overlays will appear. Returns null while loading and falls back gracefully on CORS or network errors.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| src | string \| null \| undefined | Image source URL to analyze. When null/undefined, returns the fallback value. |
| options | UseImageModeOptions | Optional configuration for image analysis. |
| options.region | ImageSampleRegion | Region to sample within the image using normalized 0-1 coordinates ({ x, y, width, height }). Defaults to the full image. |
| options.threshold | number | Luminance threshold for the dark/light split. Below = dark, above = light. |
| options.fallback | 'dark' \| 'light' \| null | Fallback value while loading or on error. |

### Returns

```json
[
  {
    "name": "mode",
    "type": "'dark' | 'light' | null",
    "description": "Detected luminance mode of the image. Returns null while loading or if src is null/undefined."
  }
]
```

### Usage guidance

- Pair with MediaTheme to automatically adapt text color over dynamic background images.
- Use the region option to sample only the area where text overlays will appear for more accurate results.
- Avoid: Use for images that change rapidly (e.g., video frames); each src change triggers a new fetch and analysis.

## useIndicatorFocusRing

Draws the standard focus ring on the indicator of a control whose real input is visually hidden; a checkbox or radio focuses an opacity-0 input, so the ring has to appear on the picture beside it. The ring is painted imperatively on the indicator's own element, which is the only element whose border-radius can shape it, and only on :focus-visible, so pointer clicks stay quiet. Owning it here means a theme-supplied indicator cannot ship a control with no visible focus (WCAG 2.4.7) by ignoring a prop.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| containerRef | RefObject<HTMLElement \| null> | Ref to an element wrapping only the indicator, so its single element child is unambiguously the thing to ring. |
| isDisabled | boolean | Skip the ring; a disabled control is not focusable. |

### Returns

```json
[
  {
    "name": "focusProps",
    "type": "{onFocus: (event: FocusEvent<HTMLElement>) => void; onBlur: () => void}",
    "description": "Spread onto the element that owns the focusable input."
  }
]
```

### Usage guidance

- Wrap only the indicator in the ref'd element; a wrapper holding label text would ring the whole row.
- Spread focusProps on the element that contains the hidden input, not on the input itself.
- Avoid: Ask a themeable indicator to draw its own focus ring; a replacement that ignores the prop leaves the control with no visible focus.

## useInputContainer

Makes an input container wrapper clickable, delegating focus to the inner input/textarea when the user clicks non-interactive areas (icons, padding, status indicators). Built on top of useClickableContainer, so nested interactive elements (clear buttons, calendar toggles, links) are handled safely; clicking them does NOT steal focus from the input. Automatically detects input type: text-like inputs receive .focus(), while other types (checkbox, radio, file) receive .click().

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseInputContainerOptions | Configuration object for the input container. |
| options.containerRef | RefObject<HTMLElement \| null> | Ref to the outer container/wrapper element. |
| options.inputRef | RefObject<HTMLInputElement \| HTMLTextAreaElement \| HTMLElement \| null> | Ref to the inner input or textarea element. |
| options.disabled | boolean | Whether the input is disabled. |

### Returns

```json
[
  {
    "name": "onClick",
    "type": "(event: MouseEvent<HTMLElement>) => void",
    "description": "Click handler to attach to the container wrapper."
  },
  {
    "name": "onMouseUp",
    "type": "(event: MouseEvent<HTMLElement>) => void",
    "description": "Mouse up handler to attach to the container wrapper."
  }
]
```

### Usage guidance

- Use inside input wrapper components (TextInput, NumberInput, TimeInput, TextArea) to make the full container area clickable.
- Attach both onClick and onMouseUp to the wrapper div for full interaction handling.
- Avoid: Use on bare inputs without a wrapper; there is no benefit if the input already fills the full clickable area.

## useInputStatusIcon

Builds the on-field status affordance for a bordered input and its accessibility wiring, so every input in the family behaves the same for a given status. The attached variant renders a plain glyph and leaves the text to the message box; the detached variant renders nothing here, because the message box already carries its own icon; the tooltip variant renders a real focusable button whose tooltip is reachable by keyboard, pointer, touch and assistive tech. Use it when building a bordered input, not for field-level messaging.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseInputStatusIconOptions | Configuration object. |
| options.status | InputStatus | The input's status (type plus message), or undefined when there is none. |
| options.statusVariant | 'attached' \| 'detached' \| 'tooltip' | How the status is presented relative to the input. |
| options.isInGroup | boolean | Whether the input sits inside an InputGroup, which owns status rendering itself. |
| options.size | IconSize | Size of the on-field icon. |

### Returns

```json
[
  {
    "name": "statusIcon",
    "type": "ReactNode",
    "description": "The affordance to render inside the input container: a plain icon, or a focusable info-tip button with its tooltip. Null when no icon should render."
  },
  {
    "name": "describedBy",
    "type": "string | undefined",
    "description": "ID to add to the input's aria-describedby, present exactly when a tooltip element is in the DOM, so there is never a dangling reference."
  }
]
```

### Usage guidance

- Render statusIcon inside the input container and merge describedBy into the control's aria-describedby list.
- Let it decide when nothing should render; pass isInGroup and the variant through rather than branching at the call site.
- Avoid: Use it to convey the status by icon alone; the tooltip variant is the only one that carries the message, so the others still need a message box.

## useInteractiveRole

Resolves what a polymorphic component should render as, in one place: href wins, then onClick, then an interactive trigger context supplied by a parent (Popover, DropdownMenu and friends), then inert. Use it in any component that is sometimes a link, sometimes a button, and sometimes plain content; Token, Thumbnail, Item and ClickableCard all do. Because context is part of the resolution, a component built on it becomes a valid trigger for new surfaces without changing.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseInteractiveRoleOptions | The interactivity inputs the component received. |
| options.href | string | URL for navigation. Highest priority: with an href the component is a link. |
| options.onClick | ((...args: never[]) => unknown) \| null | Click handler. Resolves to a button, ahead of any context-provided role. |
| options.isDisabled | boolean | When true, href is ignored for role resolution (a disabled link is an anti-pattern), so the role comes from onClick, then context, then inert. |

### Returns

```json
[
  {
    "name": "role",
    "type": "'link' | 'button' | 'inert'",
    "description": "The element the component should render: an anchor, a button, or a non-interactive span/div."
  }
]
```

### Usage guidance

- Switch on the returned role to pick the element, and render an anchor only for "link" so keyboard and middle-click behavior come from the platform.
- Pass isDisabled through rather than dropping the href yourself; the hook already keeps disabled links out of the tab order.
- Avoid: Add another ad-hoc href/onClick precedence check in a component; new trigger contexts are added here so every consumer inherits them.

## useKeyboardHint

Shows an ephemeral "← → to navigate" hint anchored to the focused item the first time a roving-tabindex composite (Toolbar, TabList, SegmentedControl, etc.) receives keyboard focus. It teaches sighted keyboard users that arrow keys move within the group. The hint renders arrow keys with Kbd in the top layer (popover="manual") and is CSS-anchor-positioned to the focused element, so overflow containers never clip it. It auto-dismisses on the first arrow press, on timeout, or on blur, and does not re-show for that instance. Toolbar, TabList, and SegmentedControl wire this in automatically; reach for the hook directly only when building a custom roving-tabindex widget.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseKeyboardHintOptions | Configuration object for the keyboard hint. |
| options.orientation | 'horizontal' \| 'vertical' \| 'both' | Which arrow-key axis the composite navigates, controlling which arrow icons the hint shows (← → for horizontal, ↑ ↓ for vertical, all four for both). |
| options.dismissAfterMs | number | Milliseconds before the hint auto-dismisses after appearing. |
| options.isEnabled | boolean | Whether the hint is enabled. Set to false to suppress it for a specific instance (e.g. a disabled or read-only widget). |

### Returns

```json
[
  {
    "name": "hintElement",
    "type": "ReactNode",
    "description": "The popover hint element to render inside the composite container (as the last child). Portals to the top layer via popover=\"manual\", renders arrow keys with Kbd, and manages its own visibility; render it unconditionally."
  },
  {
    "name": "onFocus",
    "type": "(e: React.FocusEvent) => void",
    "description": "Attach to the container onFocus. Shows the hint on the first keyboard-focus (:focus-visible) entry from outside the composite."
  },
  {
    "name": "onBlur",
    "type": "(e: React.FocusEvent) => void",
    "description": "Attach to the container onBlur. Hides the hint when focus leaves the composite entirely, and re-anchors when focus moves within."
  },
  {
    "name": "onKeyDown",
    "type": "(e: React.KeyboardEvent) => void",
    "description": "Attach to the container onKeyDown. Dismisses the hint on the first arrow press (the user has discovered the interaction). Never prevents default or stops propagation."
  }
]
```

### Usage guidance

- Compose the returned onFocus/onKeyDown with your existing focus handlers rather than replacing them: call onKeyDown first (it only dismisses, never prevents), then your navigation handler.
- Render hintElement as the last child of the composite container; it is position:fixed in the top layer and aria-hidden, so it never affects layout or the accessibility tree.
- Match orientation to the arrow keys your widget actually responds to so the hint shows the correct icons.
- Avoid: Use for single controls or widgets without roving-tabindex navigation; the hint only makes sense where arrows move focus within a group.

## useListFocus

Manages keyboard navigation within a linear list following WAI-ARIA menu/listbox/toolbar patterns. Supports arrow key navigation (vertical, horizontal, or both), Home/End for boundaries, optional wrap-around, RTL, and Escape to close. Opt into hasRovingTabIndex for composite widgets (toolbars, segmented controls, tab strips) that own a single tab stop. Suitable for dropdown menus, toolbars, and any 1D focusable list.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseListFocusOptions | Configuration object for list focus behavior. All fields are optional. |
| options.itemSelector | string | Selector for focusable items within the list. |
| options.boundarySelector | string | Selector identifying a list boundary, for lists that contain nested lists of the same kind (e.g. a menu with submenu flyouts). When set, item collection and key handling are scoped to this level's own container. Typically '[role="menu"]'. |
| options.wrap | boolean | Whether arrow navigation wraps around at the ends. |
| options.onEscape | () => void | Callback when Escape key is pressed (e.g., close menu). Supplying it also consumes the key (preventDefault); without it Escape passes through to the surrounding layer. |
| options.orientation | 'horizontal' \| 'vertical' \| 'both' | Navigation orientation. 'horizontal' uses ArrowLeft/ArrowRight, 'vertical' uses ArrowUp/ArrowDown, 'both' accepts all four arrows. |
| options.hasHomeEnd | boolean | Whether Home/End jump to the first/last enabled item. |
| options.hasPaging | boolean | Whether PageDown/PageUp page through a scrolling list: PageDown moves to the last enabled item fully visible in the container, and pressed there again one viewport further; PageUp mirrors it. Neither wraps; in a list that does not scroll they act as End/Home. |
| options.hasRovingTabIndex | boolean | Opt into roving-tabindex ownership: the hook stamps a single tab stop (one item tabindex="0", the rest -1), repairs it as items mount/unmount or toggle disabled, and moves it with arrow navigation. When false, the hook only moves focus and never touches tabindex. |
| options.hasCaretGuard | boolean | When true, arrow keys are not stolen from a nested text input/textarea whose caret is not at the boundary in the direction of travel (or that has a selection), and are never stolen from a nested contenteditable (rich-text editor / chat composer). Preserves inline text editing within the list. |

### Returns

```json
[
  {
    "name": "listRef",
    "type": "React.RefObject<HTMLElement | null>",
    "description": "Ref to attach to the list container element."
  },
  {
    "name": "handleKeyDown",
    "type": "(e: React.KeyboardEvent) => void",
    "description": "Key down handler to attach to the list container."
  },
  {
    "name": "handleFocus",
    "type": "(e: React.FocusEvent) => void",
    "description": "Focus handler for the container. Keeps the roving tab stop in sync when hasRovingTabIndex is enabled; a no-op otherwise, so it is always safe to attach."
  },
  {
    "name": "focusItem",
    "type": "(index: number) => void",
    "description": "Focus a specific item by index (clamped to valid range)."
  },
  {
    "name": "focusFirst",
    "type": "() => boolean",
    "description": "Focus the first enabled item. Returns true when an item was focused."
  },
  {
    "name": "focusLast",
    "type": "() => boolean",
    "description": "Focus the last enabled item. Returns true when an item was focused."
  },
  {
    "name": "ownsEvent",
    "type": "(e: React.KeyboardEvent) => boolean",
    "description": "Whether a key event belongs to this list level rather than a nested list sharing the same boundarySelector. Always true when no boundarySelector is set. Use to guard consumer-added key handling (Enter/Space, typeahead)."
  },
  {
    "name": "getItems",
    "type": "() => HTMLElement[]",
    "description": "This level's focusable items in DOM order (already scoped by boundarySelector). Build typeahead targets from this instead of re-querying."
  }
]
```

### Usage guidance

- Set orientation to 'horizontal' for toolbars and tab bars, 'vertical' for dropdown menus.
- Provide an onEscape callback for menus/dropdowns to return focus to the trigger.
- Enable hasRovingTabIndex (and hasCaretGuard when the widget can contain text inputs) for toolbar-style composites that should be a single tab stop.
- Avoid: Use for 2D grid navigation; prefer useGridFocus for grids and calendars.

## useLongPress

Detects a single-finger press-and-hold and reports where it happened, so touch users can reach affordances that a pointer gets from right-click. iOS Safari never synthesizes a contextmenu event on long-press, which makes this the only touch route into a cursor-positioned surface such as ContextMenu. The press cancels on movement, lift, multi-touch or unmount.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseLongPressOptions | Configuration object. |
| options.onLongPress | (point: {x: number; y: number}) => void | Fired with the touch start point once the press is held for delayMs. |
| options.disabled | boolean | When true, the returned touch handlers are inert. |
| options.delayMs | number | Hold duration before the press fires, in milliseconds. |
| options.moveCancelPx | number | Movement past this distance on either axis cancels the press, treating it as a scroll or drag. |

### Returns

```json
[
  {
    "name": "handlers",
    "type": "UseLongPressHandlers",
    "description": "onTouchStart, onTouchMove, onTouchEnd and onTouchCancel to spread onto the target element."
  }
]
```

### Usage guidance

- Use the reported point to position the surface you open, so it appears under the finger.
- Pair it with the pointer contextmenu handler rather than replacing it; this hook only covers touch.
- Avoid: Use it for the primary action of a control; press-and-hold is undiscoverable as the only way to do something.

## useMediaQuery

SSR-safe media query hook that subscribes to window.matchMedia changes. Returns whether the given media query matches. Always returns false on first render for SSR compatibility.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| query | string | CSS media query string to evaluate. |

### Returns

```json
[
  {
    "name": "matches",
    "type": "boolean",
    "description": "Whether the media query currently matches. Always false on first render (SSR-safe)."
  }
]
```

### Usage guidance

- Use for responsive layout switching based on viewport width, color scheme, or motion preferences.
- Prefer Astryx responsive tokens and component props over manual breakpoint logic when possible.
- Avoid: Use for server-rendered content that must match on first paint; the hook always returns false initially.

## useMenuPress

The press model of macOS and iOS menus, for any pointer: the row under the pointer when it is RELEASED is the row that acts, the highlight follows the pointer while it is held, a mouse opens the menu on press and can drag straight into it, and a finger held on the trigger opens it with the finger still down. The pointer is tracked at document level by pointerId, so a finger that slid off the row it landed on is still followed; the click the browser reports at the end of a touch, aimed at the row where the touch began, is swallowed so nothing acts twice. DropdownMenu, ContextMenu, DropdownMenuSubMenu, Selector and the menu bottom sheet already mount it; reach for it directly only when building a menu-like surface of your own.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseMenuPressOptions | Configuration object. |
| options.menuRef | RefObject<HTMLElement \| null> | The menu or listbox root — the surface whose rows a press picks from. |
| options.itemSelector | string | Selector matching the ENABLED rows. A pointer over anything else inside the menu (a divider, a heading, a disabled row) highlights nothing. |
| options.triggerRef | RefObject<HTMLElement \| null> | The control that opens the menu, when a press may start there. |
| options.onTriggerPress | (pointerType: MenuPressPointerType) => boolean | A mouse pressed the trigger, or a finger rested on it for the long-press delay: open the menu under the held pointer and return whether it opened. Return false when the press closed an open menu instead. |
| options.onHighlight | (row: HTMLElement \| null) => void | Move the highlight; null clears it. Defaults to moving DOM focus with preventScroll onto the row, and onto the menu root when there is no row. A picker that highlights through aria-activedescendant supplies its own. |
| options.onActivate | (row: HTMLElement, release: PointerEvent) => void | Act on the row under the release. Defaults to dispatching a click on the row that carries the release button and modifier keys. |
| options.onDismiss | () => void | A MOUSE was released outside the menu with nothing acting: close it. A finger released outside leaves the menu open, so this is never called then. |
| options.getScroller | () => HTMLElement \| null | The element to scroll while a tracked pointer rests near its top or bottom edge. Defaults to the menu root when it overflows. |
| options.longPressDelayMs | number | How long a finger must rest on the trigger before the menu opens under it. |
| options.isEnabled | boolean | Whether the model is live. |

### Returns

```json
[
  {
    "name": "menuProps",
    "type": "{onPointerDown; \"data-astryx-menu-press\": \"\"}",
    "description": "Spread onto the menu root. Claims presses that begin inside it and marks the root as carrying the press model."
  },
  {
    "name": "triggerProps",
    "type": "{onPointerDown; onContextMenu}",
    "description": "Spread onto the trigger: a mouse press opens the menu at once; a finger held for the delay opens it with the finger still down."
  },
  {
    "name": "isTriggerClickFromPress",
    "type": "() => boolean",
    "description": "Whether the click reaching the trigger belongs to the gesture that just pressed it. That press already opened or closed the menu, so the click must neither toggle nor reopen."
  },
  {
    "name": "cancel",
    "type": "() => void",
    "description": "End the gesture in flight with nothing acting, for when the menu closes under it."
  }
]
```

### Usage guidance

- Pass a selector for ENABLED rows only, so a disabled row or a divider under the pointer clears the highlight instead of lighting up.
- Let the default highlight move focus in a menu; supply onHighlight only for a listbox that must keep focus on its combobox and highlight through aria-activedescendant.
- Declare touch-action on the menu root: pinch-zoom when its rows fit, pan-y pinch-zoom when it scrolls, so the browser — not the hook — decides when a finger is scrolling, and a two-finger pinch still zooms the page.
- Avoid: Act on a row from its own pointerdown or pointerup handler as well; the hook already activates the row under the release, and a second path acts twice.
- Avoid: Read a row click with detail 0 as a keyboard activation; the hook dispatches its pointer activation with detail 0 too. Use isMenuPressActivation() to tell them apart.

## useMergedRefs

Combines multiple object or callback refs into one stable callback ref. Use it when a component must forward a consumer ref while also attaching internal refs. Unlike calling mergeRefs during render, the callback identity stays stable across unrelated rerenders, so React does not detach and reattach the element.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| refs | Array<Ref<T> \| undefined> | Up to six refs that should all receive the same element. |

### Returns

```json
[
  {
    "name": "ref",
    "type": "RefCallback<T>",
    "description": "A merged callback ref that remains stable until an input ref changes."
  }
]
```

### Usage guidance

- Use useMergedRefs when one element must receive both a forwarded ref and one or more internal refs.
- Avoid: Call mergeRefs directly in a JSX ref prop; that creates a new callback on every render and forces unnecessary detach and attach work.

## useOverflow

Measures children rendered in a hidden container to determine how many fit in the available width without flickering. Uses ResizeObserver to react to container and measured-child size changes. The measurement container should hold all items plus an optional overflow indicator element (identified by a data-overflow-indicator attribute).

### Parameters

| Parameter | Type | Description |
|---|---|---|
| itemCount | number | Total number of items to measure for overflow. |
| options | UseOverflowOptions | Configuration object for overflow behavior. |
| options.gap | number | Gap between items in pixels. Used in width calculations. |
| options.minVisibleItems | number | Minimum number of items to always show, even if they don't fit. |
| options.collapseFrom | 'start' \| 'end' | which end to collapse items from. |
| options.behavior | 'observeParent' \| 'observeSelf' | Which element to observe for overflow calculations. 'observeParent' uses the container's parent element width, allowing the visible container to remain content-sized. |

### Returns

```json
[
  {
    "name": "containerRef",
    "type": "React.RefCallback<HTMLElement>",
    "description": "Ref callback to attach to the visible container element."
  },
  {
    "name": "measureRef",
    "type": "React.RefCallback<HTMLElement>",
    "description": "Ref callback to attach to the hidden measurement container that holds all items."
  },
  {
    "name": "visibleCount",
    "type": "number",
    "description": "Number of items that fit in the visible container."
  },
  {
    "name": "hasOverflow",
    "type": "boolean",
    "description": "Whether any items are overflowing (visibleCount < itemCount)."
  }
]
```

### Usage guidance

- Render all items into the measureRef container (hidden) and only the first visibleCount items into the containerRef container (visible).
- Include an overflow indicator (e.g., "+N more" button) as the last child of the measurement container with a data-overflow-indicator attribute.
- Avoid: Use for vertical overflow; this hook measures horizontal width only.

## usePressFeedback

The touch press model every Astryx pressable uses, for a local component that paints its own press. Under a finger, CSS `:active` paints on the touch itself and can outlive the start of a scroll; the press model instead waits 150 ms before believing a press, cancels it the moment the finger travels 10 px or a scroll claims the gesture, never brings it back inside that gesture, answers a quick tap at the lift, and fades the release over 200 ms — the clocks a native list uses. One document-level controller does this for every marked element (installed by the first pressable to mount, removed by the last to unmount), so calling the hook adds no listener or state to your element. Under a mouse nothing changes: keep your `:active` rule, and drop it under `@media (pointer: coarse)` the way the built-in components do.

### Returns

```json
[
  {
    "name": "pressableProps",
    "type": "{'data-astryx-pressable': ''}",
    "description": "The marker attribute to spread on the element that paints the press. Compose one of `interactionOverlayStyles` (from `@astryxdesign/core/utils`) on the same element — `backgroundColor` or `backgroundImage` for a surface whose hover and press are the system's overlays, `pressedBackgroundColor` for one whose hover is its own — and it paints the pressed token at the press's strength on the first frame of a believed press and fades it over the release, keeping `:active` for a mouse. A press is themed through `--color-overlay-pressed`."
  }
]
```

### Usage guidance

- Spread the result on the element whose background paints the press and compose one of `interactionOverlayStyles` on it; the composed style is the instant onset, the hold and the fade, and it is themed through `--color-overlay-pressed`.
- Let the composed style own the press on every pointer: it keeps `:active` for a mouse and drops it under `@media (pointer: coarse)`, so a finger sees only the press model. A rule of your own that paints `:active` would paint under a finger too, on the touch and through a scroll.
- Avoid: Add a pointer listener or React state to the element to track the press; the controller already writes the attribute, and per-element listeners are what a long list cannot afford.
- Avoid: Call it for a control rendered by an Astryx component; those are marked already, and the innermost marked element takes the press.

## useScrollLock

Locks body scroll when active by pinning the body with position: fixed. This prevents background scrolling behind modals and dialogs, which is necessary for iOS Safari where overscroll-behavior: contain does not work. Restores the original scroll position when unlocked. Pinning hides the document scrollbar, so where that scrollbar takes layout space (desktop) the hook holds its gutter open with scrollbar-gutter: stable for the duration of the lock. The page, including any position: fixed chrome, does not shift sideways.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| isLocked | boolean | whether body scroll should be locked. |

### Returns

```json
[]
```

### Usage guidance

- Use when opening full-screen modals or dialogs to prevent background content from scrolling.
- Pass the same boolean that controls dialog visibility (e.g., isOpen) as the isLocked parameter.
- Avoid: Use for non-modal overlays like popovers or tooltips; users should be able to scroll away from those.

## useScrollOverflow

Tracks scroll overflow state for a horizontally scrollable container. Returns a ref callback and state booleans that update as the user scrolls or the container resizes. Uses scroll event listeners and ResizeObserver for reactive updates. Tolerance of 1px is applied to avoid sub-pixel false positives.

### Returns

```json
[
  {
    "name": "scrollRef",
    "type": "React.RefCallback<HTMLElement>",
    "description": "Ref callback to attach to the horizontally scrollable container element."
  },
  {
    "name": "overflowStart",
    "type": "boolean",
    "description": "Whether content overflows the start edge (left in LTR, right in RTL)."
  },
  {
    "name": "overflowEnd",
    "type": "boolean",
    "description": "Whether content overflows the end edge (right in LTR, left in RTL)."
  },
  {
    "name": "hasOverflow",
    "type": "boolean",
    "description": "Whether the container has any scroll overflow at all (scrollWidth > clientWidth)."
  }
]
```

### Usage guidance

- Use to show/hide scroll navigation buttons or fade edges on carousels and horizontal lists.
- Apply the scrollRef to a container with overflow-x: auto or overflow-x: scroll.
- Avoid: Use for vertical scroll tracking; this hook only measures horizontal overflow.

## useScrollableArea

Adds canonical axis-aware scroll behavior to structure owned by the caller. An axis is effective only when its computed overflow is scroll-capable and geometry exceeds the shared 1px tolerance. Both viewport and content boxes are observed.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseScrollableAreaOptions | Logical scroll intent, fixed or automatic keyboard owner, overscroll policy, and fitting Sticky containment. |

### Returns

```json
[
  {
    "name": "getViewportProps",
    "type": "<E extends HTMLElement>(props?: ScrollableElementProps<E>) => ScrollableElementProps<E>",
    "description": "Consumes caller viewport props, xstyle, and refs; composes fitting/active overflow, Sticky containment, accessibility, chaining, and owner registration."
  },
  {
    "name": "getContentProps",
    "type": "<E extends HTMLElement>(props?: ScrollableElementProps<E>) => ScrollableElementProps<E>",
    "description": "Composes caller content-box props and refs with content observation."
  },
  {
    "name": "state",
    "type": "ScrollableAreaState",
    "description": "Stable inline and block effective-scroll and logical-edge state."
  }
]
```

### Usage guidance

- Pass already-resolved props and refs through both prop getters, then spread each returned object once.
- Use viewport keyboard ownership only when the viewport itself should enter the tab order; provide a concise accessible label.
- Use contentOrViewport to delegate forward Tab entry to the first sequential native link or button when it preserves native scroll keys. Inputs, composite widgets, and nested scroll areas retain the named viewport stop. Shift+Tab from the delegated first child skips the viewport; pointer and programmatic focus stay on it.
- Use content keyboard ownership when your integration already supplies keyboard access to the full scroll range. Automatic delegation checks current content at each keyboard entry without continuously tracking its focusability.
- Pass caller `xstyle` through `getViewportProps`; the getter composes it with fitting clip, active overflow, and Sticky containment.
- Avoid: Attach only the viewport getter. A real observed content box is required for live overflow changes.

## useStreamingText

Smooths bursty streamed text into a steady character-by-character reveal using requestAnimationFrame. Decouples arrival rate from display rate. Advances on word and syntax boundaries to avoid slicing mid-markdown or mid-word, preventing visual glitches with markdown renderers. Animation timing derives from Astryx motion tokens via useTheme when available, with sensible fallbacks outside a theme provider. Snaps to full text when isStreaming becomes false.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| targetText | string | The full target text to reveal. As new chunks arrive, update this value with the accumulated text. |
| isStreaming | boolean | Whether text is currently being streamed. When false, the hook returns the full targetText immediately. |
| options | UseStreamingTextOptions | Optional configuration for streaming behavior. |
| options.speed | 'natural' \| 'fast' \| 'instant' | Speed preset for text reveal. 'natural' is steady ~2 chars/frame, 'fast' scales with backlog ~4 chars/frame, 'instant' returns full text with no animation. |

### Returns

```json
[
  {
    "name": "displayedText",
    "type": "string",
    "description": "The portion of targetText to render. Grows steadily toward the full targetText during streaming, or equals targetText when not streaming."
  }
]
```

### Usage guidance

- Pass the accumulated text (not individual chunks) as targetText; the hook handles incremental reveal internally.
- Set isStreaming to false when the stream completes to snap to the final text.
- Use speed='instant' for non-animated contexts like search results or when reduced motion is preferred.
- Avoid: Use for static text that does not change; the hook adds unnecessary overhead for non-streaming content.

## useTreeFocus

Manages roving-tabindex focus and the WAI-ARIA tree keyboard model. ArrowUp/ArrowDown/Home/End roam linearly over the visible treeitems (skipping disabled ones), while ArrowRight/ArrowLeft carry tree semantics (expand/collapse, move to first-child/parent). Enter/Space activate, and printable characters trigger typeahead.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseTreeFocusOptions | Configuration object for tree focus behavior. |
| options.itemSelector | string | Selector for visible treeitems within the tree, in DOM order. |
| options.isItemDisabled | (item: HTMLElement) => boolean | Predicate for whether a treeitem is disabled and must be skipped during navigation. Defaults to reading `data-tree-disabled` / `aria-disabled`. |
| options.getLevel | (item: HTMLElement) => number | Reads the 1-based nesting level of a treeitem. Defaults to the `aria-level` attribute. |
| options.onToggleExpand | (id: string) => void | Called to expand/collapse the treeitem with the given id (ArrowRight on a collapsed parent, ArrowLeft on an expanded parent, Enter/Space on a parent without its own action). |
| options.onActivate | (item: HTMLElement, id: string \| undefined) => boolean \| void | Called when Enter/Space activates a treeitem. Return true when handled; return false/undefined to let the hook fall back to toggling expansion. |
| options.onActiveChange | (id: string \| undefined) => void | Notified when the hook moves focus to a treeitem. Consumers use this to move a single roving tab stop. |
| options.hasRovingTabIndex | boolean | When true, the hook owns a single roving tab stop across the visible treeitems (stamps tabindex 0/-1, repairs on mount, moves with navigation). Preserves an existing tabindex="0" seed on mount. Attach the returned `handleFocus` to keep the stop in sync after clicks. |
| options.typeahead | boolean | Whether typeahead (jump to next item whose text starts with the typed characters) is enabled. |

### Returns

```json
[
  {
    "name": "treeRef",
    "type": "React.RefObject<HTMLElement | null>",
    "description": "Ref to attach to the tree container element (role=\"tree\")."
  },
  {
    "name": "handleKeyDown",
    "type": "(e: React.KeyboardEvent) => void",
    "description": "Key down handler to attach to the tree container."
  },
  {
    "name": "handleFocus",
    "type": "(e: React.FocusEvent) => void",
    "description": "Focus handler to attach to the container's onFocus. Keeps the roving tab stop in sync when hasRovingTabIndex is enabled; a no-op otherwise, so always safe to attach."
  },
  {
    "name": "focusFirst",
    "type": "() => void",
    "description": "Focus the first enabled visible treeitem."
  },
  {
    "name": "focusLast",
    "type": "() => void",
    "description": "Focus the last enabled visible treeitem."
  }
]
```

### Usage guidance

- Use for hierarchical tree widgets: wire onToggleExpand to your expansion state and onActiveChange to a single roving tab stop.
- Attach both treeRef and handleKeyDown to the role="tree" container element.
- Avoid: Use for linear lists (prefer useListFocus) or 2D grids (prefer useGridFocus); those traversals differ from a tree.

## useTypeahead

Adds APG type-to-focus search to a collection: printable keystrokes are buffered (resetting after a pause), and the first item whose label starts with the buffer is reported through onMatch. Pressing the same letter repeatedly cycles through the matches rather than filtering deeper. It moves nothing itself; pair it with the collection's own focus management, most often useListFocus or useGridFocus.

### Parameters

| Parameter | Type | Description |
|---|---|---|
| options | UseTypeaheadOptions | Configuration object. |
| options.getItemLabels | () => ReadonlyArray<string \| null \| undefined> | Returns the item labels in DOM order. A null or empty entry marks a non-matchable slot and keeps indices aligned with the caller's items. |
| options.onMatch | (index: number) => void | Called with the index of the matched item so the caller can focus or select it; typically useListFocus's focusItem. |
| options.getCurrentIndex | () => number | The index to search from, usually the focused item, so repeated presses of one letter cycle through matches. A negative value means nothing is current. |
| options.resetMs | number | Milliseconds of inactivity after which the typed buffer resets. |
| options.isDisabled | (index: number) => boolean | Whether an index should be skipped, e.g. disabled items. |

### Returns

```json
[
  {
    "name": "onKeyDown",
    "type": "(e: React.KeyboardEvent | KeyboardEvent) => boolean",
    "description": "Keydown handler. Returns true when it consumed a printable character, so the caller can stop its own key handling."
  },
  {
    "name": "reset",
    "type": "() => void",
    "description": "Clears the pending buffer, e.g. when the collection closes."
  }
]
```

### Usage guidance

- Wire onMatch to the focus manager you already have (useListFocus.focusItem) instead of moving focus yourself.
- Let it see the key event first and fall through to arrow-key navigation only when it returns false.
- Pass getCurrentIndex so repeated presses of one letter walk through matches instead of sticking on the first.
- Avoid: Use it on a text input; the field already receives the characters, and typeahead would fight the value.


## Examples

- useContainerReveal — Reveal-on-hover Row Actions: File rows keep their edit/delete actions hidden at rest and reveal them on hover or keyboard focus via useContainerReveal; the actions stay mounted and in the tab order.
- useKeyboardHint — Arrow-key Hint: Toolbar shows an ephemeral "← → to navigate" hint on first keyboard focus via useKeyboardHint, teaching sighted keyboard users that arrows move within the group.
- useStreamingText — Streaming Response: Smooth bursty generated text into a steady reveal with useStreamingText.

## Complete example dependencies

Some examples import optional icons. The per-example install command in the examples list identifies exactly what that example needs. For all examples on this page:

```bash
npm install @heroicons/react@2.2.0
```

## Source StyleX compilation

useContainerRevealHookUsage contain stylex.create and require a StyleX source build pipeline. Precompiled Core CSS alone does not compile these examples. See [Styling](/resources/astryx/docs/styling) before copying them.

---

Index of all JGuang resources: https://jiguang132.com/llms.txt
