Input and navigation

Updated: Sep 27, 2026

Browser-managed directional navigation

Meta Ray-Ban Display glasses receive directional and activation input from the touchpad and, when available, Meta Neural Band. While the page is active, it receives standard keydown events for ArrowUp, ArrowDown, ArrowLeft, ArrowRight, and Enter. By default, Chromium WebView spatial navigation uses the arrow keys to move focus among eligible focusable elements according to their on-screen positions. When no focus candidate is available in a direction, it can scroll the page instead. Enter activates the focused element.
For ordinary pages, let Chromium WebView or UI Toolkit own directional focus movement and activation. Page-level key listeners remain available. If a listener moves focus without preventing the default arrow-key action, both custom and built-in traversal can run. A simple manual focus index built from DOM or query order ignores the controls’ two-dimensional positions, and synthesizing a click without preventing the default Enter activation can activate a control twice.

Build a navigable interface

Prefer native controls:
<nav aria-label="Primary">
  <a href="/messages/">Messages</a>
  <a href="/settings/">Settings</a>
</nav>

<button type="button" id="start">Start</button>
Native buttons, links, inputs, selects, and text areas already provide semantics, focusability, and activation behavior. Handle ordinary click, input, and change events instead of translating device inputs yourself.
Arrange the DOM and visual layout so nearby controls are sensible directional neighbors. Keep disabled or hidden controls out of navigation and preserve the native focus order instead of assigning positive tabindex values. Use tabindex="0" only for a custom widget that cannot be represented by a native element and that implements the complete corresponding semantics and interaction contract.
Every critical action must remain reachable through browser- or toolkit-managed directional navigation and activation.

Keep selection visible

Keep the browser’s focus indicator or provide an equivalent. A browser-neutral enhancement can use :focus-visible:
:where(button, a, input, select, textarea):focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 4px;
}
The wearable browser also provides spatial-selection feedback. Test custom styles alongside that feedback on the device, especially near viewport edges and inside scroll regions. Use browser focus and spatial selection as the source of truth rather than maintaining a second .focused state.

Advanced custom interactions

These keydown events target the focused element and bubble through the DOM, so listeners on document or window can observe and consume them when needed. This is an available low-level input path, not the recommended way to implement ordinary focus traversal.
For most interfaces, rely on the element traversal built into the browser or UI Toolkit instead of maintaining a global focus index or manually chaining controls together. When application flow needs to direct focus—for example, after opening a panel or rendering the next step—use standard focus APIs such as HTMLElement.focus()⁠.
Handle key events directly only for a custom interaction that cannot be expressed with ordinary controls or built-in traversal, such as a game board or specialized range editor. Scope the listener as narrowly as practical; a document or window listener is appropriate when the custom interaction owns the whole page. Call preventDefault() only when the custom behavior intentionally replaces the browser default, and always provide a clear way to move focus out of the interaction.

Activation

When Enter is not consumed by custom handling, the browser activates the focused element. Native controls therefore work with their normal click behavior:
const start = document.getElementById('start');

start.addEventListener('click', () => {
  beginSession();
});
Consume Enter only when custom behavior intentionally replaces browser activation. If a listener only observes the event, do not call preventDefault() or synthesize another click.
There is no free cursor in the default mode. Keep essential actions independent of hover and positioned click coordinates.

Continuous drag

Continuous drag is optional and useful for sliders, drawing, panning, and games. Opt in at page load by setting touch-action: none on body in the initial stylesheet:
body {
  touch-action: none;
}
The host reads this page-level value once per load. Setting it later with JavaScript or only on an individual element does not opt the page into wearable drag input.
Use standard pointer events and pointer capture:
const surface = document.getElementById('drawing-surface');
let dragging = false;

surface.addEventListener('pointerdown', (event) => {
  dragging = true;
  surface.setPointerCapture(event.pointerId);
  updatePosition(event.clientX, event.clientY);
});

surface.addEventListener('pointermove', (event) => {
  if (dragging) {
    updatePosition(event.clientX, event.clientY);
  }
});

surface.addEventListener('pointerup', (event) => {
  dragging = false;
  surface.releasePointerCapture(event.pointerId);
});

surface.addEventListener('pointercancel', () => {
  dragging = false;
});
Wearable drag availability depends on hardware and runtime configuration. Keep all essential actions usable through directional navigation and activation. Test device event coordinates before depending on a particular range or motion scale; desktop pointer simulation does not reproduce wearable input exactly.

Back navigation

The native shell handles Back. When browser history has a previous entry, it calls history.back(). When no previous entry remains, Back opens the native navigation overlay; pressing Back again exits to the native app boundary. Native transient UI, such as the text composer, is dismissed before page history is traversed.
Prefer browser history to an on-screen Back button. Reaching a Back button can require the user to navigate through every control that precedes it, while the system Back action traverses the history stack in one step from anywhere on the page. Add an on-screen Back control only when the interface requires an additional visible navigation affordance; it should call the same history action rather than maintain separate navigation state.
Add a history entry for a destination the user would expect Back to reverse, and restore application state during traversal:
const HOME_STATE = {screen: 'home'};
history.replaceState(HOME_STATE, '');

function openSettings() {
  history.pushState({screen: 'settings'}, '');
  renderScreen({screen: 'settings'});
}

window.addEventListener('popstate', (event) => {
  renderScreen(event.state ?? HOME_STATE);
});
The host limits pushState() history to five total entries, including the initial document. Keep that stack for meaningful destinations, not focus changes, tooltips, or other transient UI. Let history drain to the native app boundary instead of pushing a new entry while handling Back.
Use either popstate or the Navigation API to coordinate traversal, not both for the same state transition. Restore serializable application state after the destination has rendered.

Text input

Activating a supported text field opens the system composer for handwriting, dictation, and on-screen keyboard input. Use semantic fields; no custom input SDK is required.
Supported fields include:
  • text-like inputs: text, search, email, url, tel, and number;
  • textarea; and
  • contenteditable elements.
Password, date, checkbox, and radio inputs do not open the composer. Use native checkbox and radio behavior for selection rather than treating them as text fields.
<label for="search">Search</label>
<input id="search" type="search" placeholder="Write or speak a search">

<label for="note">Note</label>
<textarea id="note" placeholder="Write or speak a note"></textarea>
Read committed text through input and change:
const search = document.getElementById('search');
const results = document.getElementById('results');

search.addEventListener('input', () => {
  results.textContent = `Searching: ${search.value}`;
});
The composer manages its own text buffer and commits the result through input and change. Per-character keydown, keypress, and keyup events are not available for handwriting, dictation, or the on-screen keyboard.
The on-screen keyboard has a limited character set. Design device-entered values around the available characters or provide another input path. inputmode and enterkeyhint do not change the composer. Provide another path when text entry is unavailable or a required value cannot be entered through the supported fields.

Test on the device

Verify that:
  • every interactive control is reached in a sensible spatial order;
  • activation triggers each control once;
  • focus feedback remains visible near edges and while scrolling;
  • custom widgets do not trap directional navigation;
  • page-level key listeners consume only the intended inputs and leave browser or toolkit traversal working elsewhere;
  • Back reverses meaningful destinations and can reach the native app boundary;
  • essential actions remain available without continuous drag;
  • text fields open the composer and process committed input values; and
  • the app remains usable when optional input hardware or composer features are unavailable.