# useFocusWithin

Tracks whether focus is inside an element, for an item that moves focus when what the person was using goes or finishes. An item moves focus only when focus is inside it, or on its control that was removed, never because `document.activeElement` is `body`: a poll, a hold or a timer that finishes while the person is elsewhere, or nowhere, leaves focus alone (announce instead). A target held for the next render is dropped as soon as the person moves focus out, so it never pulls focus back later.

Focus leaves when it lands on an element outside, when the person presses outside, or when the element that had it blurs to nowhere and is still on the page; the window losing focus isn't leaving. Focus already inside when the item mounts (a control's `autoFocus`) counts as inside. Read `inside()` and `held()` in handlers and effects, not while rendering.

```
const focus = useFocusWithin<"heading" | "retry">(root)
// In the handler that removes what had focus:
if (focus.inside()) focus.hold("heading")
else announce(t("done"))
// In an effect, once the next render has drawn the target:
React.useEffect(() => {
  const target = focus.held()
  if (target === null) return
  focus.drop()
  ;({ heading: heading.current, retry: retry.current })[target]?.focus()
})
```

- Registry name: `use-focus-within` (`@candor/use-focus-within`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/use-focus-within

## Install

```bash
npx candor-ui add use-focus-within
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/use-focus-within`.
It brings in: tokens.

## Functions

### useFocusWithin

```ts
useFocusWithin(ref: React.RefObject<HTMLElement | null>)
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
