We need to talk about CSS state management. For some reason, the standard advice for modern WordPress development has become reaching for a heavy JavaScript framework the moment a panel needs to slide or a toggle needs to flip. Honestly, it is killing performance. I recently refactored a client’s WooCommerce site where they were loading 200kb of React just to handle a three-way pricing toggle. That’s not architecture; that’s a bottleneck.
While business logic—like validation or database persistence—absolutely belongs in JavaScript, purely visual UI state does not. In cases where you’re just moving things around the screen, keeping the logic in the presentation layer is usually the right call. That’s where the Radio State Machine comes into play.
The Evolution: From the Checkbox Hack to :has()
If you have been in the WordPress ecosystem as long as I have, you probably remember the “checkbox hack.” It was the “mischievous” way we handled toggles before we had better APIs. You’d drop a hidden checkbox at the top of the document and use sibling combinators to trigger styles. It worked, but it was fragile because CSS only allowed us to select elements that came after the checkbox in the DOM.
But since the :has() pseudo-class became widely supported, the game has changed. We can now place our state-carriers anywhere and target parent containers or distant siblings. If you’re still nesting everything just to satisfy an old selector limitation, you’re creating legacy code before you even ship it.
For more on maintaining a clean stylesheet, check out my guide on alternatives to !important.
The Accessibility War Story
Here is where things get messy. Most “clever” CSS hacks fail because they ignore accessibility. Using display: none or the hidden attribute on an input removes it from the accessibility tree. Screen readers won’t find it, and your “elegant” solution becomes a barrier. I’ve seen sites fail audits specifically because a dev thought they were being smart with hidden inputs.
The fix? Don’t hide the input—repurpose it. Use appearance: none to strip the browser’s default styling and turn the radio or checkbox itself into the interactive button. It stays focusable, it stays semantic, and it stays accessible.
Building the Radio State Machine
The Radio State Machine extends the checkbox concept to handle multiple mutually exclusive states. Since only one radio button in a named group can be checked at once, we get a built-in “active” state tracker for free. Here is the technical breakdown of how I implement this in custom blocks or 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>
In the CSS, we use the :checked state of these radios to drive the visual logic. By targeting the parent container with :has(), we can update any child element 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
If you want to feel like a real architect, stop writing individual style blocks for every state. Instead, treat the active state as a numeric variable. You can use CSS calc() to drive layout shifts, opacities, and colors relative to that state.
This is extremely useful for things like multi-step onboarding or product swatches where the visual flow is linear. If you’re interested in more advanced UI techniques, you should read about CSS View Transitions for WordPress UX.
Look, if this Radio State Machine stuff is eating up your dev hours, let me handle it. I’ve been wrestling with WordPress since the 4.x days.
The Pragmatic Takeaway
The real win isn’t proving that CSS can do everything. It’s knowing exactly where it shines. Use the Radio State Machine when the state is local and visual. If you need to fetch async data or validate a checkout, stick to JavaScript. But for that “clever” UI flip? Ship it in CSS and keep your site fast.