Whack-a-Mole: The CSS Edition

Discover how to build a fully functional Whack-a-Mole game using nothing but HTML and CSS--no JavaScript required. Master the checkbox hack, CSS animations, and state machine selectors.

The Impossible Made Possible

Remember that classic arcade game where moles pop up from holes and you have to whack them before they disappear? What if we told you that you could build a fully functional version of that game using nothing but HTML and CSS--no JavaScript required?

It might sound impossible, but CSS has evolved into a surprisingly powerful tool for creating interactive experiences that push the boundaries of what many developers think the language is capable of. In this guide, we'll explore techniques like the checkbox hack for state management, CSS animations for timing-based gameplay, and clever selector patterns that let you create game logic without a single line of JavaScript.

Whether you're looking to understand the limits of CSS, create a fun demo, or simply expand your frontend toolkit, this tutorial will give you a solid foundation in CSS-only interactive development. For developers working with modern JavaScript frameworks, understanding these pure CSS techniques provides valuable insight into how frameworks manage state and interactivity under the hood.

What You'll Learn

Master these core CSS techniques for interactive development

The Checkbox Hack

Learn how to use form elements to create state management purely in CSS, enabling click tracking and game logic without JavaScript.

CSS Animation Timing

Master animation-timing-function: step-end to create precise, timing-based gameplay elements that pop in and out predictably.

State Machine Selectors

Build complex game logic using sibling combinators and pseudo-classes to detect and respond to game states.

Position vs Transform

Understand when to use position properties for reliable hit testing versus transforms for optimal performance.

The Checkbox Hack: CSS State Management

At the heart of any interactive game is state--tracking what's been clicked, what the score is, and what should happen next. In traditional web development, we'd use JavaScript variables to manage this state. But CSS has a hidden superpower: the ability to track state through form elements, specifically checkboxes and radio buttons.

The checkbox hack is a technique that leverages the fact that checkboxes and radio buttons have a checked state that CSS can detect and respond to. When you click a label connected to a checkbox, the checkbox's checked state toggles, and CSS can use the :checked pseudo-class to apply different styles based on that state.

<input type="checkbox" id="game-state">
<label for="game-state">Start Game</label>

<style>
 #game-state:checked ~ .game-board {
 /* Styles when game is running */
 }
</style>

The sibling combinator (~ or +) allows us to style elements elsewhere in the document based on a checkbox's state. When a player clicks a mole, they're actually clicking a label that toggles a hidden checkbox. The :checked state then triggers styles that make the mole appear "hit"--it shrinks, changes color, or plays an animation.

This approach works because form elements are part of the browser's native UI, and CSS has long had pseudo-classes for interacting with them. What makes this powerful for game development is that these state changes happen instantly and can trigger cascading style changes throughout your document. By combining multiple checkboxes, you can create surprisingly complex state machines that track multiple game elements simultaneously.

These same principles apply to modern CSS responsive design where state-based styling creates adaptive layouts without JavaScript.

Radio Buttons for Exclusive States

While checkboxes are perfect for tracking which moles have been hit (each mole has its own checkbox that can be independently checked), radio buttons excel at managing mutually exclusive game states. For example, you might use radio buttons to track which level the player is on, or whether the game is in progress, paused, or ended.

Radio buttons in a group share a name attribute, meaning only one can be checked at a time. This makes them ideal for tracking game modes, difficulty levels, or any state where only one option should be active at once.

This pattern of using form elements for state management demonstrates the same principles behind CSS-only custom select components, where native form behavior is leveraged to create custom interactive elements.

CSS Animations for Game Timing

The second pillar of our CSS-only game is animation timing. Whack-a-Mole isn't just about clicking--it's about timing. Moles need to pop up and down at unpredictable intervals, and players need to react quickly. CSS animations provide exactly the mechanism we need for this timing-based behavior.

Step-End Timing for Popping Behavior

The key technique is using animation-timing-function: step-end (or steps(1, end)). Unlike the default ease or linear timing functions that create smooth transitions, step-end causes animations to jump instantly between keyframes. This is perfect for game elements that need to appear or disappear suddenly.

.mole {
 animation: popUp 2s infinite step-end;
}

@keyframes popUp {
 0% { transform: translateY(-100%); }
 50% { transform: translateY(0); }
}

In this example, the mole would instantly appear at the "up" position, stay there for a moment, then instantly disappear back to the "down" position. By adjusting the keyframe percentages and animation duration, you create different patterns of mole behavior.

For more advanced animation techniques, explore our guide on CSS hover effects and 3D background masks that demonstrate creative animation combinations. Additionally, understanding CSS preprocessors and postprocessors can help you organize and manage complex animation code efficiently.

Why Position Properties Matter More Than Transform

Here's a crucial detail that can make or break your CSS game: when implementing mole movement, using the left or top property often works better than transform: translate(). This might seem counterintuitive since transform is generally recommended for performance.

The reason has to do with how browsers handle hit testing. When you use transform, the visual representation of an element moves, but the browser's understanding of where the element is for click purposes might not update immediately--or might not update at all in some browsers. Firefox, in particular, has had issues where transforms don't trigger proper layout recalculations for hit testing.

By using left or top to move the mole, you force the browser to recalculate layout, ensuring that clicks always register on the element that's visually in that position. The performance cost is higher, but for a simple game like this, it's a worthwhile trade-off for reliable interaction.

Understanding these trade-offs is essential for frontend performance optimization and creating responsive interfaces that feel snappy across devices.

PropertyPerformanceHit TestingUse Case
transformExcellentSometimes unreliableNon-interactive animations
left/topPoorAlways reliableInteractive game elements

Building the Game Board

Now let's put these concepts together to build our game. The HTML structure forms the foundation of our CSS-only game logic. Each hole in the game board contains both a hole element (for the visual hole) and a mole element (which will animate up and down). Each mole is actually a label connected to a hidden checkbox.

<div class="game-board">
 <input type="checkbox" id="mole-1" class="mole-checkbox">
 <div class="hole hole-1">
 <label for="mole-1" class="mole"></label>
 </div>

 <input type="checkbox" id="mole-2" class="mole-checkbox">
 <div class="hole hole-2">
 <label for="mole-2" class="mole"></label>
 </div>
 <!-- More holes... -->
</div>

The mole (the label) sits inside the hole (a container), and the mole's animation moves it up and down relative to the hole. When the mole is "down," it's hidden behind the hole's overflow. When it animates "up," it rises into view. Because the mole is a label connected to a checkbox, clicking it when it's visible will check the box, which we style to show the mole as "hit."

The same principles of clever DOM structure and CSS selection apply to building accessible data tables where semantic HTML enables powerful CSS-based interactions.

Multiple Moles with Different Timings

To make the game challenging, each mole should have a different animation timing. We achieve this by giving each mole a different animation-delay. This creates the unpredictability that makes Whack-a-Mole fun.

.mole-1 { animation-delay: 0s; }
.mole-2 { animation-delay: -0.7s; }
.mole-3 { animation-delay: -1.3s; }
.mole-4 { animation-delay: -0.3s; }
.mole-5 { animation-delay: -1.7s; }
.mole-6 { animation-delay: -0.9s; }

Using negative animation delays is a powerful technique--it starts the animation partway through its cycle, so all moles don't start in the same position. This immediately creates visual variety and prevents the "synchronized swimming" effect that would make the game too easy.

This technique of using negative delays to desynchronize animations is also useful when implementing complex CSS animations for visual effects.

State Machine Selectors for Game Logic

As your CSS game becomes more complex, you'll need sophisticated selectors to manage game logic. The general sibling combinator (~) allows us to style elements based on the state of other elements that come before them in the DOM.

For example, we might want to hide all moles once all checkboxes are checked (meaning the player has won):

.mole-checkbox:checked ~ .mole-checkbox:checked ~ .game-board .mole {
 display: none;
}

This selector is verbose because we need to check each checkbox individually. For a game with six holes, you need a chain of selectors to detect when all are checked. This is where preprocessor scripts become valuable--you can write a small script to generate these repetitive selectors automatically.

Advanced CSS selectors like these form the foundation of maintainable CSS architectures where systematic selector patterns enable complex styling systems without excessive specificity wars.

Python or Script-Based Selector Generation

Writing complex state machine selectors by hand is tedious and error-prone. Most developers who build sophisticated CSS-only games use small scripts to generate their CSS. A Python script, for example, can generate all the necessary selectors for checking game states, then output a complete CSS file.

This approach separates the logic (what states we need to check) from the implementation (the generated CSS selectors). You can define your game rules in a simple format, run the script, and get production-ready CSS as output. This makes the game easier to maintain and modify--you change the rules in your script, regenerate the CSS, and your game updates automatically.

Example Python approach:

def generate_win_selectors(num_moles):
 selectors = []
 for i in range(num_moles):
 base = ".mole-checkbox:checked"
 pattern = " ~ ".join([base] * (i + 1))
 selectors.append(f"{pattern} ~ .game-board .mole")
 return "\n".join(selectors)

The same principle of programmatically generating CSS is applied in modern CSS preprocessing workflows where build tools transform source code into production CSS.

Performance Considerations

CSS-only games, while impressive, require careful attention to performance. Here are key considerations for keeping your CSS game running smoothly.

Optimize Your Animations

Prefer transform and opacity for animations when hit testing accuracy isn't critical. These properties are handled by the GPU in most browsers and don't trigger expensive layout recalculations. Only fall back to position properties when you need reliable hit testing.

Manage Animation Count

Be mindful of how many animations are running simultaneously. A Whack-a-Mole game with six moles is fine. But if you try to animate dozens of elements, you might see frame drops on lower-powered devices.

Use will-change Strategically

Use will-change to hint to the browser which properties will be animated:

.mole {
 will-change: transform;
}

However, use will-change sparingly--overuse can actually hurt performance by causing the browser to maintain optimization that isn't needed.

These same performance principles apply to all performance-critical web applications where smooth animations and interactions are essential for user experience.

Accessibility Considerations

A pure CSS game like this has significant accessibility limitations. The checkbox hack relies on visual interaction, and screen readers will announce these elements differently than expected.

Known Limitations

  • Screen readers announce label text as if it were a checkbox
  • Users won't understand they're clicking on a "mole" that's part of a game
  • Hidden checkboxes with display: none won't be announced in some browsers
  • The game is completely inaccessible to screen reader users

Potential Improvements

If accessibility is important for your project:

  • Add hidden but focusable checkboxes using visually-hidden classes
  • Use ARIA attributes to explain the game interaction
  • Provide a keyboard-only version that uses focus management instead of click detection
  • Consider adding a summary alternative that describes the game state

For pure demo or educational purposes, these limitations may be acceptable. For production applications, consider a hybrid approach with JavaScript for full accessibility. Building accessible web experiences is a core principle of our web development services, ensuring all users can interact with your digital products.

When CSS-Only Is Appropriate

CSS-only interactive elements work best for:

  • Educational demonstrations and learning tools
  • Creative portfolio pieces and experiments
  • Progressive enhancement where JavaScript fails
  • Performance-focused demos showing CSS capabilities

For production applications where accessibility and user experience are paramount, combining CSS for visual interactions with JavaScript for logic and ARIA management provides the best results.

Ready to Build More Interactive CSS Experiences?

Our team specializes in pushing the boundaries of web development. From creative CSS experiments to full-featured web applications, we bring innovation to every project.

Frequently Asked Questions

Sources

  1. CSS-Tricks - Whack-a-Mole: The CSS Edition - The definitive guide to building a CSS-only Whack-a-Mole game
  2. CSS-Tricks - The Checkbox Hack - Foundation for CSS state management using form elements
  3. CSS-Tricks - A Complete State Machine Made with HTML Checkboxes and CSS - Advanced state machine patterns
  4. CSS-Tricks - CSS Animation Tricks - Animation techniques for game-like experiences
  5. CSS Triggers - Layout vs Compositing Properties - Understanding browser rendering performance for animations