# useKeyboardHint

## Usage

Shows an ephemeral "← → to navigate" hint anchored to the focused item the first time a roving-tabindex composite receives keyboard focus. It teaches sighted keyboard users that arrow keys move within the group. The hint renders arrow keys with `Kbd`, is positioned `fixed` next to the focused element, so overflow containers never clip it, and is `aria-hidden`. It dismisses on the first arrow press, on timeout, or when focus leaves the group, and after a dismissal it does not show again for that instance. Reach for the hook directly when building a custom roving-tabindex widget; `Toolbar`, `TabList`, and `SegmentedControl` do not mount it for you.

The `Hint` component returned by the hook is rendered inside the composite container, as its last child.

```ts
import { useKeyboardHint } from '@astryx-vue/core'
```

## Best practices

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

## Examples

Common configurations, variations, and states.

### useKeyboardHint — Arrow-key Hint

A custom roving-tabindex toolbar shows an ephemeral arrow-key hint on first keyboard focus via useKeyboardHint, teaching sighted keyboard users that arrows move within the group.

```vue
<script setup lang="ts">
import type { ComponentPublicInstance } from 'vue'
import { useKeyboardHint, useListFocus } from '@astryx-vue/core'
import { Button } from '@astryx-vue/core/Button'
import { Text } from '@astryx-vue/core/Text'

const actions = ['Bold', 'Italic', 'Underline', 'Strikethrough']

const list = useListFocus({
  itemSelector: '[data-hint-item]',
  orientation: 'horizontal',
  hasRovingTabIndex: true,
})
const hint = useKeyboardHint({ orientation: 'horizontal' })

// 模板引用写进 hook 的 listRef，listRef 只由 hook 持有。
function bindList(el: Element | ComponentPublicInstance | null): void {
  list.listRef.value = el instanceof HTMLElement ? el : null
}

function onFocus(event: FocusEvent): void {
  hint.onFocus(event)
}

function onBlur(event: FocusEvent): void {
  hint.onBlur(event)
}

function onKeyDown(event: KeyboardEvent): void {
  hint.onKeyDown(event)
  list.handleKeyDown(event)
}
</script>

<template>
  <div class="hint-preview">
    <Text type="supporting" color="secondary">
      {{ 'Tab into the group, then press the arrow keys.' }}
    </Text>
    <!-- eslint-disable-next-line vue-a11y/no-static-element-interactions -- role="toolbar" 的焦点停在内部按钮上，容器只接冒泡上来的焦点与方向键。 -->
    <div
      :ref="bindList"
      class="hint-preview__group"
      role="toolbar"
      aria-label="Text formatting"
      @focusin="onFocus"
      @focusout="onBlur"
      @keydown="onKeyDown"
    >
      <Button
        v-for="action in actions"
        :key="action"
        :label="action"
        data-hint-item
        size="sm"
        variant="ghost"
      />
      <component :is="hint.Hint" />
    </div>
  </div>
</template>

<style scoped>
.hint-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.hint-preview__group {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--astryx-color-border);
  border-radius: var(--astryx-radius-container, 0.75rem);
  background: var(--astryx-color-background-surface);
}
</style>
```

## Parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `UseKeyboardHintOptions` | `{}` | Configuration object for the keyboard hint. Optional. |
| `options.orientation` | `'horizontal' \| 'vertical' \| 'both'` | `'horizontal'` | 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` | `3000` | Milliseconds before the hint auto-dismisses after appearing. |
| `options.isEnabled` | `boolean` | `true` | Whether the hint is enabled. Set to false to suppress it for a specific instance (e.g. a disabled or read-only widget). |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `Hint` | `Component` | The hint element to render inside the composite container as the last child, e.g. `<component :is="hint.Hint" />`. It positions itself `fixed`, renders arrow keys with `Kbd`, and manages its own visibility. Render it unconditionally. |
| `visible` | `Ref<boolean>` | Whether the hint is currently shown. `Hint` reads it; a custom hint element can too. |
| `anchor` | `Ref` | The focused item the hint is anchored to. `null` before the first show. |
| `orientation` | `ComputedRef` | The resolved orientation, with the default applied. |
| `onFocus` | `(event: FocusEvent) => void` | Bind to the container's `focusin` (`@focusin`), because focus does not bubble. Shows the hint on the first keyboard-focus (`:focus-visible`) entry from outside the composite. |
| `onBlur` | `(event: FocusEvent) => void` | Bind to the container's `focusout` (`@focusout`). Hides the hint when focus leaves the composite entirely and counts that as a dismissal, so it does not come back for this instance; focus moves within the composite only re-anchor it. |
| `onKeyDown` | `(event: KeyboardEvent) => void` | Bind to the container's `keydown`. Dismisses the hint on the first arrow press (the user has discovered the interaction). Never prevents default or stops propagation. |
