Skip to content
Home Theme Gallery

Focus

Focus is always visible and always the same: a 3px translucent ring in --hui-ring, outside a border that also takes --hui-ring.

--hui-focus-ring-width: 3px;
--hui-focus-ring-offset: 0px;
--hui-ring: oklch(0.62 0.019 323.02); /* light */

The ring is drawn with box-shadow, not outline, for two reasons: a shadow can be translucent, and it follows the element’s border radius on every side.

Only :focus-visible is styled, never :focus. A pointer click produces no ring; a keyboard tab does. That is the platform’s own heuristic, so a component does not have to guess how focus arrived.

In a composite — an input with a leading icon and a clear button, say — the ring belongs to the element a reader would call the control, not to the focusable element inside it. A bare rule lands on whatever receives focus, which is correct for an atom (hui-input’s internal <input> is the control) and wrong for a wrapper, where the ring would be drawn inside the control’s own border. Such a component suppresses the ring on the input and redraws it on the control. That correction was ISS-006.

aria-invalid is the single invalid signal across every control. A host that renders a server validation error sets that attribute; there is no component-specific error attribute. It drives a destructive border and a destructive-tinted ring, so the signal is not colour alone.

Invalid and default controls

The error text is marked by colour, position and an icon, and is wired to the control with aria-describedby plus aria-invalid by hui-field. See Screen readers.

Under forced-colors: active, the token sheet remaps the boundary and ring tokens to system colours:

@media (forced-colors: active) {
:root {
--hui-border: CanvasText;
--hui-input: CanvasText;
--hui-ring: Highlight;
}
}

Components never suppress the system focus indicator in forced-colours mode. Transitions collapse under prefers-reduced-motion: reduce.