The indicator is not optional
Without it, a keyboard user has no idea where they are. If the default ring is too ugly for the design, replace it — do not remove it. WCAG 2.2 added a criterion about how visible it has to be: at least a 2px-thick line around the control, at 3:1 contrast against what is behind it.
*:focus { outline: none; } /* every year, in every codebase */
:focus-visible {
outline: 2px solid #00eadc;
outline-offset: 2px;
border-radius: 4px;
}
/* and for a dark control on a light page, the double-ring trick */
:focus-visible { outline: 2px solid #000; box-shadow: 0 0 0 4px #fff; }
:focus-visible is the answer to "it looks wrong on click"
The reason people remove outlines is that they appear when a mouse user clicks a button. :focus-visible is the browser's own judgement of whether the focus came from a keyboard: style that, and a mouse click stays clean while Tab shows the ring.
Keep a :focus fallback only if you support very old browsers; every current one implements :focus-visible.
For a composite control — a search box with a button inside a bordered container — :focus-within on the container is what makes the whole thing look focused.
Moving focus on purpose
Focus is the screen reader's "you are here". When the page changes under somebody — a route change in a single-page app, a panel opening, a step completing — focus has to follow, or they are still reading the old thing while the screen shows a new one.
- After a route change — focus the new
h1, or a wrapper with the page name. - When a dialog opens — focus the dialog, or the first control in it. On close, focus what opened it.
- After deleting a row — focus the next row, or the list itself. Never leave focus on a removed element: it falls back to
<body>and the user is at the top of the page. - After a validation failure — focus the first field with an error.
- Never on page load. Focusing a search box on load moves a screen reader past the heading and the navigation.
function navigate(url, title) {
render(url);
document.title = `${title} — DOMVYBE`;
const heading = document.querySelector("main h1");
heading.setAttribute("tabindex", "-1"); // focusable from script, not in the tab order
heading.focus(); // announced, and Tab continues from here
}
Do not steal it
Focus belongs to the user. Do not move it while they are typing, do not pull it into a newly-loaded advert, do not re-focus a field on every keystroke — a "helpful" focus() inside an input handler is why some forms are impossible to use with a screen reader.