CSS :has() Character Select Screen
See the Pen CSS :has() Character Select Screen.
Tech & Dependencies
Features
- ✓ CSS State Management
- ✓ No JavaScript Logic
- ✓ Responsive Grid
Browser Support
Core
This is a CSS :has() Character Select Screen. It replicates the classic user interface of a fighting game’s selection menu, using modern CSS to handle all state logic. Its function is to demonstrate how the :has() pseudo-class can completely replace JavaScript for managing visibility in a selection-based UI, triggering changes in the main content area based on the state of a radio button group.
Specs
- Weight: ~75 KB (Vue dependency for templating). The core logic is pure CSS.
- Performance: Exceptionally high. Because the state is managed directly by the CSS engine, there is zero latency between selecting a character and the main image updating.
- Theming / Customization: Easily customizable by updating the
charactersarray in the Vue data object. - Responsiveness: Uses a CSS Grid layout with media queries to switch from an 8-column to a 4-column layout for the character portraits on smaller screens.
- Graceful Degradation: [!] Relies entirely on the
:has()pseudo-class. In older browsers, all large character images will be hidden, and the selection will not work.
Anatomy
The component uses hidden radio inputs as a state controller for the main display area.
- HTML (The Skeleton): The Vue template generates the structure. An
<aside>contains a list of<li>elements, each with a hidden<input type="radio">and a<label>(the character portrait). A separate<section>contains all the large character<figure>elements. - CSS (The Skin): Uses SCSS for styling. The core logic is handled by CSS, not JavaScript.
mix-blend-mode: multiplyon the main image creates a rich color interaction with the background texture. - JS (The Nervous System): Vue.js is used only as a templating engine to generate the repetitive HTML. There is no JavaScript logic for handling clicks, state, or visibility.
Logic
The entire interaction is powered by the “Parent State Selection” logic of :has():
/* Hide all main images by default when any radio is checked */
aside:has(:checked) + section figure {
display: none;
}
/* Then, only show the one that matches the checked ID */
aside:has(#Ryu:checked) + section figure[data-name="Ryu"] {
display: block;
}
This is a two-step CSS declaration that is incredibly efficient. First, it says, “If the <aside> element has any checked radio button inside it, then select the adjacent <section> and hide all <figure> elements within it.” Immediately after, a more specific rule says, “If the <aside> element has the specific radio button with the ID #Ryu checked, then find the adjacent <section> and display the one <figure> that has the matching data-name='Ryu'.
Feel
Instant, snappy, and retro. The interaction feels like a native application or a classic arcade console menu. The hover effect, where unselected portraits blur and desaturate (filter: grayscale(1) blur(0.05em)), expertly guides the user’s focus to their current selection without any distracting animations.


