# Add focus and keyboard navigation

Pibbl can add logical keyboard interaction to explicit Canvas targets. The HTML
Canvas remains the one native DOM focus host. Pibbl does not create semantic DOM
descendants, ARIA controls, or a screen-reader accessibility tree.

## Opt in with policy components

```tsx
import {
  FocusManagement,
  KeyboardNavigation,
  Rectangle,
  useEventTarget,
  type PibblNode,
} from "@pibbl/core";

interface ButtonProps {
  onActivate: () => void;
}

function Button({ onActivate }: ButtonProps): PibblNode {
  const path = new Path2D();
  path.rect(20, 20, 140, 48);
  const focus = useEventTarget(
    {
      onClick: onActivate,
      onKeyDown: (event) => console.log(event.key),
    },
    {
      path,
      fill: true,
      cursor: "pointer",
      keyboardNavigationBounds: { x: 20, y: 20, width: 140, height: 48 },
    },
  );

  return (
    <Rectangle
      pointerEvents="none"
      style={{
        x: 20,
        y: 20,
        width: 140,
        height: 48,
        fill: "#2563eb",
        stroke: focus.isFocusVisible ? "#facc15" : undefined,
        strokeWidth: focus.isFocusVisible ? 4 : 0,
      }}
    />
  );
}

const scene = (
  <FocusManagement>
    <KeyboardNavigation mode="directional">
      <Button onActivate={() => console.log("activated")} />
    </KeyboardNavigation>
  </FocusManagement>
);
```

Each policy accepts exactly one Pibbl element after empty values are removed.
They are transparent preparation boundaries: they do not paint, lay out, own
hooks, or change the enhanced child's component identity. Directional
navigation requires focus management on the same root.

## Enrollment and interaction

Only logical event targets become candidates. A target enrolls automatically
with `onClick`, `onKeyDown`, or `onKeyUp`; set `keyboardFocusable: true` to add
another target or `false` to exclude it. Built-in Canvas drawing creates a
logical target by default, even when passive; `pointerEvents="none"` removes
that drawing from hit and focus
participation. Propagation-only structural listeners never become candidates.

When `useEventTarget()` supplies custom geometry and a later drawing primitive
only visualizes it, mark that drawing `pointerEvents="none"` as in the example.
Otherwise the default-participating drawing would be the topmost logical target.

The manager owns source-order Tab and Shift+Tab traversal, logical
focus/blur/focusin/focusout events, pointer-press focus defaults,
focus-visible modality, and Enter/Space activation. Tab exits naturally at
either end; it does not wrap. `KeyboardNavigation mode="directional"` ranks
finite `keyboardNavigationBounds` for non-wrapping arrow movement. Nested
directional boundaries contain their own edges.

`useEventTarget()` returns `{ isFocused, isFocusVisible }` for rendering. The
snapshot is inert and false without active focus management. Logical focus
state may invalidate a retained `Layer` containing the changed target.

A root `OffscreenCanvas` cannot host DOM focus and rejects focus management.
Offscreen layers under an HTML Canvas share the root manager.

Registered `@pibbl/three` targets derive directional-navigation bounds by
projecting their Three object bounds into the logical render layer. An explicit
`keyboardNavigationBounds` on a target is interpreted as a layer-local 2D
override. Source-order Tab behavior and the one native Canvas focus host stay
unchanged across the Three-to-Canvas bridge. See [Use Three.js inside
Pibbl](/guides/three-js/#share-events-without-wrapping-the-scene-graph).

## Accessibility responsibility

Canvas pixels and Pibbl's logical targets expose no names, roles, values,
relationships, reading order, text selection, or platform accessibility nodes.
Keyboard reachability is not equivalent to screen-reader accessibility.

For every meaningful Canvas interaction, provide an equivalent semantic DOM
experience that the application owns. Depending on the product, that can be a
real button/control beside the Canvas, a synchronized list or table, an
accessible form for editing, a summary plus data download, or an alternate
nonvisual workflow. Keep labels, state, order, validation, and actions derived
from the same application model. Do not hide the only operable controls in an
`aria-hidden` mirror or imply that logical focus alone makes the Canvas
accessible.

Pibbl intentionally has no hidden editable DOM adapter, semantic overlay, ARIA
mapping, live-region API, or generated accessibility tree. The Canvas owner is
also responsible for its accessible name and fallback/adjacent explanation.

See the normative [focus contract](https://github.com/benlesh/pibbl/blob/main/docs/design/focus-and-keyboard-navigation-contract.md)
and [Canvas accessibility guidance](https://github.com/benlesh/pibbl/blob/main/docs/design/canvas-accessibility-guidance.md).
Executable lessons cover [focus
management](https://github.com/benlesh/pibbl/blob/main/packages/scenarios/src/learning/keyboard-focus/focus-management.example.tsx)
and [directional
navigation](https://github.com/benlesh/pibbl/blob/main/packages/scenarios/src/learning/keyboard-focus/directional-navigation.example.tsx).

## Implementation guidance for agents

Read the [Input, focus, and native HTML companion](/agents/topics/input/) for ownership, adaptation, failure modes, and verification. [Agent start](/agents/) provides the version-selection workflow.

## Documentation version

Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.
