· 5 min read
The browser should move focus
Chrome 150 turns roving tabindex into a focusgroup primitive, putting arrow-key movement, tab-stop memory, and writing-mode rules where application code has been impersonating the browser.

This site has a tab component whose visible job is tiny: switch between two code samples. The interesting part lives underneath. It keeps one button in the page's tab order, listens for left and right arrows, wraps at both ends, changes the selected panel, and pushes DOM focus onto the next button. None of that has anything to do with code samples.
The ARIA Authoring Practices tabs pattern asks for exactly this shape because a tablist should be one stop in sequential keyboard navigation while arrow keys move inside it. For years, the answer has been roving tabindex: authors keep one child at 0, put the rest at -1, and write the movement logic themselves. Chrome 150 shipped focusgroup, a proposed HTML primitive that finally makes the browser volunteer for that part of the job.
const focusTab = (index: number) => { const next = (index + labels.length) % labels.length; setActive(next); document.getElementById(`${id}-tab-${next}`)?.focus();};<button role="tab" tabIndex={index === active ? 0 : -1} onKeyDown={(event) => { if (event.key === "ArrowRight") focusTab(index + 1); if (event.key === "ArrowLeft") focusTab(index - 1); }}/><div focusgroup="tablist" aria-label="Alternatives"> {labels.map((label, index) => ( <button aria-selected={index === active} aria-controls={`panel-${index}`} onFocus={() => setActive(index)} > {label} </button> ))}</div>There is a small joke in rendering that comparison with the component being criticized. Click the tabs above and the current JavaScript still does the movement. The proposed version only sketches the part focusgroup can own; selection and panel visibility remain application state.
The state machine was generic all along#
W3C's keyboard-interface guidance describes roving tabindex as a general focus-management strategy for composite controls. Toolbars, tablists, listboxes, and trees all end up solving the same plumbing problem before they can get to their own behavior. One child must be reachable by Tab. Directional input moves focus within the group. Re-entering the group should often return to the item that had focus before.
The current Open UI focusgroup proposal takes that common machinery seriously. A focusgroup supplies directional navigation, a guaranteed tab stop, last-focused memory, optional wrapping, and logical-axis control. It also accounts for hidden or disabled descendants and can follow the page's writing direction rather than assuming that right arrow always means “next.” That last detail is where hand-written handlers start to look faintly ridiculous.
Look again at this site's handler. It maps ArrowRight to index + 1 and ArrowLeft to index - 1. Fine for the current horizontal, left-to-right presentation. Change the writing mode or direction and the component still believes the keyboard was manufactured for one layout. Why should every tab implementation separately rediscover which physical key advances along a logical inline axis?
Movement is only half the widget#
This proposal gets more interesting because it stops before taking over the domain state. Open UI is explicit that focusgroup handles focus navigation rather than selection. A formatting toolbar still owns whether Bold is pressed. A tab component still decides which panel is visible and updates aria-selected. The browser can move the cursor through the control while your component decides what focusing or activating an item means.
That boundary fits the existing APG advice. Tabs may activate automatically when focus arrives if their panel can appear without noticeable delay, while slower interfaces can wait for Space or Enter. A browser primitive cannot know whether moving onto “Logs” should start an expensive fetch. The application can. Focusgroup removes the generic movement code without pretending that all composite widgets share one selection model.
I like that restraint. The platform can sometimes own a whole native control. Here it only owns the boring substrate, and that narrower job is useful because trying to infer every tab, menu, tree, and toolbar action would make focusgroup mysterious exactly where products differ.
One engine is still one engine#
Chrome 150 made focusgroup stable on June 30. Mozilla's standards-position issue is closed with a positive position, which is encouraging. WebKit's position issue is still open and carries a device-independence concern: a web primitive framed around arrow keys can accidentally bake one hardware model into an interaction system used by keyboards, remotes, game controllers, assistive technology, and devices nobody in this discussion has on their desk.
That concern improves the proposal rather than weakening the case for it. The browser is the layer that actually knows the input modality, writing mode, sequential focus order, visibility state, and platform conventions. Application JavaScript sees an event.key string and starts guessing. If the web wants directional focus to work beyond the familiar desktop keyboard, moving the mechanism downward is the sensible direction.
For now, app/components/code-tabs.tsx still contains the modulo arithmetic, the changing tabIndex, and the two event.key checks. Chrome 150 can make most of those lines redundant. The other engines are why those lines are still in the file.