Handling UI state in CSS with a radio state machine

CSS state management gets less credit than it deserves. The default advice in modern WordPress development is to reach for a heavy JavaScript framework the second a panel needs to slide or a toggle needs to flip, and the performance cost of that habit is real. I recently refactored a client’s WooCommerce site where 200kb of React was loading to run a three-way pricing toggle. That is not architecture, it is a bottleneck.

Business logic belongs in JavaScript, whether that means validation or writing to the database. Purely visual UI state does not. If all you are doing is moving things around on screen, the presentation layer is usually the right place for that logic. That is the job of the Radio State Machine.

From the checkbox hack to :has()

If you have been writing WordPress CSS for a while, you remember the “checkbox hack.” Before we had better APIs, you would drop a hidden checkbox near the top of the document and use sibling combinators to trigger styles. It worked. It was also fragile, because CSS could only select elements that came after the checkbox in the DOM.

Once :has() became widely supported, that constraint went away. State carriers can sit anywhere in the markup, and you can target parent containers or distant siblings from them. Nesting a whole component just to satisfy an old selector limitation writes legacy code into a feature that has not even shipped yet.

On keeping a stylesheet clean in general, I wrote up some alternatives to !important.

Where these hacks break accessibility

Most “clever” CSS hacks fall apart on accessibility. Setting display: none or the hidden attribute on an input pulls it out of the accessibility tree, so screen readers never see it and the elegant solution turns into a barrier. I have watched sites fail audits for exactly this, because a developer got creative with hidden inputs.

Repurpose the input instead of hiding it. Strip the browser’s default styling with appearance: none and turn the radio or checkbox itself into the thing people click. It stays focusable and it stays semantic.

Building the radio state machine

The Radio State Machine stretches the checkbox idea across several mutually exclusive states. Only one radio in a named group can be checked at a time, which hands you an active-state tracker without writing any logic for it. This is how I wire it up in custom blocks and WooCommerce components.

<div class=\"state-container\">
  <input type=\"radio\" name=\"ui-state\" id=\"state-1\" checked aria-label=\"Mode One\">
  <input type=\"radio\" name=\"ui-state\" id=\"state-2\" aria-label=\"Mode Two\">
  <input type=\"radio\" name=\"ui-state\" id=\"state-3\" aria-label=\"Mode Three\">
  
  <div class=\"canvas\">
    <!-- Your UI elements here -->
  </div>
</div>

The CSS then reads the :checked state of those radios to drive the visuals. Target the parent container with :has() and you can update any child based on which radio is active.

.state-container:has(#state-1:checked) .canvas {
  --theme-color: #ff7a18;
  --layout-shift: 0;
}

.state-container:has(#state-2:checked) .canvas {
  --theme-color: #af002d;
  --layout-shift: 100px;
}

Math-driven UI logic

Rather than writing a separate style block for every state, treat the active state as a number. CSS calc() can then drive layout shifts, opacity and color relative to that value.

That pays off for multi-step onboarding or product swatches, where the visual flow runs in a line. If you want more in this direction, read my write-up on CSS View Transitions for WordPress UX.

If wiring this up is eating your dev hours, I take on this kind of work. I have been wrestling with WordPress since the 4.x days.

When to reach for it

This is not about proving that CSS can replace JavaScript. Use the Radio State Machine when the state is local and visual. When you need to fetch async data or validate a checkout, that is JavaScript’s job. For a UI flip, ship it in CSS and keep the page fast.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.