Skip to main content

On This Page

Mastering Multi-State UI with the CSS Radio State Machine

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

The Radio State Machine

The Radio State Machine utilizes a group of radio buttons to manage mutually exclusive UI states directly within the CSS layer. By leveraging the :checked pseudo-class and the :has() selector, developers can create complex multi-mode interactions without a single line of JavaScript.

Why This Matters

While JavaScript is the industry standard for business logic and data persistence, using it for purely visual UI states—such as panel toggles, theme switching, or layout modes—introduces unnecessary overhead. The Radio State Machine keeps behavior close to the presentation layer, reducing DOM-manipulation costs and providing a surprisingly elegant solution for visual systems that require more than a simple boolean toggle.

Key Insights

  • The :has() pseudo-class allows CSS to target elements preceding the state-controlling input in the DOM, removing the historical requirement to place inputs at the top of the document.
  • Accessible state management is achieved by using appearance: none on checkboxes and radio buttons, allowing them to remain focusable and interactive for screen readers without relying on hidden inputs.
  • CSS custom properties enable state centralization, where a single numeric —state variable can drive an entire visual system using calc() functions for position, scale, and opacity.
  • Bi-directional navigation is possible by using the adjacent sibling combinator (+) to target the ‘Next’ state and :has(+ :checked) to target the ‘Previous’ state.
  • The pattern can be restricted to linear flows by replacing position: fixed with display: none for inactive states, preventing keyboard users from focusing out-of-order steps.

Working Examples

HTML structure for a three-state radio machine.

<div class="state-button"><input type="radio" name="state" data-state="one" aria-label="state one" checked><input type="radio" name="state" data-state="two" aria-label="state two"><input type="radio" name="state" data-state="three" aria-label="state three"></div>

CSS logic to reveal the next state button in a sequence.

input[name="state"] { appearance: none; position: fixed; pointer-events: none; opacity: 0; &:checked + & { position: relative; pointer-events: all; opacity: 1; } }

Driving layout calculations using numeric state variables.

body:has([data-state="one"]:checked) { --state: 1; } .card { transform: translateX(calc((var(--i) - var(--state)) * 110%)); }

Practical Applications

  • Use case: Progressive onboarding flows or multi-step checkout progress bars. Pitfall: Using display: none on inactive inputs can break keyboard accessibility if not carefully managed.
  • Use case: Visual theme selectors and decorative interface filters. Pitfall: Forcing complex business logic or async data validation into CSS instead of using JavaScript.
  • Use case: Interactive steppers and view switchers. Pitfall: Failing to provide explicit wording for aria-labels when the default radio appearance is removed.

References:

Continue reading

Next article

TinyFish AI Launches Unified Web Infrastructure for AI Agents

Related Content