docs

Focus Inspector

+
UniversalWorks with Expo & React Native CLI

This tool works with both Expo and React Native CLI projects. Just install and go.

Inspect observed focus transitions, the current focus target, and the scanned focusable elements in a React Native TV app. Flags identify patterns worth testing, such as repeated unsuccessful moves or an element that has not been reached.

Treat flags as diagnostic evidence. Confirm a suspected bug with the same remote sequence and your intended focus behavior.

where focus goes, in 71 seconds

Installation

Complete TV Quick Start first, including core, @buoy-gg/external-sync, your account key, and a working Desktop connection. Then add this tool.

npm install @buoy-gg/focus-inspector
tsx
import { FloatingDevTools } from "@buoy-gg/core";

export default function App() {
  return (
    <>
      {/* your app */}
      <FloatingDevTools headless licenseKey={process.env.EXPO_PUBLIC_BUOY_KEY} />  {/* TV apps run headless — no bubble */}
    </>
  );
}
import { FloatingDevTools } from "@buoy-gg/core";

export default function App() {
  return (
    <>
      {/* your app */}
      <FloatingDevTools headless licenseKey={process.env.EXPO_PUBLIC_BUOY_KEY} />  {/* TV apps run headless — no bubble */}
    </>
  );
}

Then open Focus in Buoy Desktop and drive your app with the remote — or with the TV Remote tool, which presses it for you.

Requires react-native-tvos and the New Architecture. On a phone build the tool reports "not a TV build" and attaches nothing.


What you see

Now

The element that currently holds focus: its testID, component name, native tag, how much of the screen it occupies, and how long it has been focused.

Two warnings live here, and they are the ones worth knowing about:

  • Focus is parked on a TVFocusGuideView. A guide with destinations is a genuine focus stop with no highlight. To whoever is holding the remote, focus just disappeared.
  • Focus was lost. A view blurred and nothing took focus after it — focus left the React Native tree entirely (a system dialog, a native view, an unmounted subtree). This is reported only when the tool watched it happen; "we have not seen focus yet" is a separate, calmer message that never claims anything is wrong.

Observed exits

A D-pad compass showing where each direction has been seen to lead from the focused element.

A direction not yet pressed from the current element remains unknown. Observed exits describe transitions that occurred; they do not predict untested paths through guides, overrides, or traps.

Transitions

Every focus move, newest first: the direction credited to it, where focus came from, where it went, and how long it had been sitting there. A move to LOST is focus leaving the tree.

Focusables

Everything the focus engine could land on, in reading order — including elements below a ScrollView's fold and the invisible guide stops. Each row shows whether focus has ever reached it, the props that make it focusable (hasTVPreferredFocus, nextFocus*, guide settings), and a Focus this button.

That button is the tool's most useful interaction: put focus somewhere, then press directions and watch what actually happens from there. It is also the only thing this tool ever writes.


The flags

FlagWhat it means
Dead endYou pressed one direction three or more times in a row from the same element and focus never moved — and there is something focusable further along that way.
Focus trapA region focus was never observed escaping in any direction where something focusable exists outside it. Usually a TVFocusGuideView and everything inside it.
Invisible stopFocus landed on a guide, or on an element the scan has never seen. This is what "the highlight vanished" looks like.
Focus lostA view blurred and nothing took focus afterwards.
Suspected unreachableNever focused, nothing observed pointing at it, and a visited element sits right next to it.

Two things the flags will not do.

They will not call anything unreachable. Never having reached an element is evidence, not proof — tvOS's focus engine is non-deterministic enough that certainty is not available without native support that does not exist. So the strong word is reserved for nothing, and the panel tells you when you have not traversed enough for absence to mean anything yet.

They will not blame the screen edge. If nothing focusable exists past an element in that direction, focus refusing to move is the screen ending, not a bug — that gets counted and set aside, not flagged.

Flags belong to the screen the current inventory describes. Navigate away and they reset with the rescan, rather than turning every element of the old screen into a mystery.


Platform differences worth knowing

TVFocusGuideView and trapFocus* are documented for both platforms in react-native-tvos. A trapped region may be intentional on Android TV as well as tvOS. Compare the flag with your props, framework version, and observed navigation before classifying it as a defect.

Focus and blur arrive in different orders. tvOS delivers the new element's focus first and the old element's blur second; Android does the opposite. The tool handles both, to distinguish transitions from focus loss.

Platform.isTVOS is undefined on Android TV, not false — a detail that has cost more than one team an afternoon.


What it costs your app

Two event listeners, always, and the expensive part only on demand.

The focus listeners start with the app rather than when you open the panel, because focus cannot be asked about on either platform — it can only be watched arriving. A tool that started listening when you opened it would know nothing until focus next moved, and would be blind in the exact situation you opened it for: focus is stuck, you press a direction, nothing moves, so nothing is learned. What that costs is two event-emitter subscriptions and a bounded ring-buffer append per focus change.

The fiber scan — the part with real cost — only runs while Buoy Desktop is actually watching. Use Pause recording in the panel to stop the rest.

The tool renders nothing on the device, and never will. On Android TV any focusable view in an overlay becomes a D-pad stop in your app's own focus order — a visible focus tool would change the thing it is measuring. Buoy Desktop is the surface.

There is no native code in this package: no podspec, no Gradle, nothing to link. The inventory is a read-only walk of the React tree, and the same key sequence produces the same focus path with the tool running and with it removed.


Pairs well with

  • TV Remote — press the D-pad from your desktop and record the sequence as a macro. Replay a macro while the Focus Inspector records, and you have a repeatable focus regression test.
  • Routes — when focus "vanishes" on navigation, the route timeline usually says why.

Web support

Browser capture measures DOM focusable elements and observes real focus and keyboard events. It uses the existing sync adapter for remote inspection. Import it from the package's /web entry (7.0.41 or later). See Web installation for registration, dependencies, and browser boundaries.