Using CSS :checked for Pure-CSS Accordions and Toggles

The CSS :checked pseudo-class enables developers to create interactive, state-driven user interface components—such as toggle switches, collapsible panels, and multi-item accordions—entirely without JavaScript. By pairing native HTML <input type="checkbox"> or <input type="radio"> elements with their corresponding <label> tags, developers can exploit browser-level state management to show, hide, or transform adjacent content using combinator selectors. This article breaks down the underlying mechanics, structural requirements, and styling techniques needed to implement robust, pure-CSS interactive components.

The Core Mechanism: The "Checkbox Hack"

Traditional pure-CSS styling is inherently stateless; styles respond dynamically only to transient user events, such as hovering (:hover) or focusing (:focus). The :checked pseudo-class bypasses this limitation by leveraging the built-in binary state of HTML form inputs.

This pattern relies on three interdependent elements:

  1. A State Container (<input>): An <input type="checkbox"> or <input type="radio"> tracks whether the component is active (open/on) or inactive (closed/off).
  2. A State Controller (<label>): Using the for attribute referencing the input's id, a <label> can trigger the input state from anywhere in the document when clicked. This allows the input itself to be visually hidden while preserving clickability.
  3. A Sibling Combinator (+ or ~): CSS cannot traverse upwards or target parent elements based on child state without modern selectors like :has(). Sibling combinators—adjacent sibling (+) or general sibling (~)—allow changes in the input to alter subsequent elements directly.
<input type="checkbox" id="toggle" class="visually-hidden">
<label for="toggle" class="toggle-button">Toggle Content</label>
<div class="collapsible-content">
  <p>This content is controlled by the input state.</p>
</div>
/* Visually hide the input while keeping it accessible */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Default state: content hidden */
.collapsible-content {
  display: none;
}

/* Checked state: content revealed */
.visually-hidden:checked ~ .collapsible-content {
  display: block;
}

Creating a Pure-CSS Toggle Switch

A toggle switch requires transforming a standard checkbox into a sliding interface. By hiding the raw checkbox and styling its label as the switch track and knob, the :checked pseudo-class drives the transition.

HTML Structure

<input type="checkbox" id="switch" class="switch-input">
<label for="switch" class="switch-label">
  <span class="switch-button"></span>
</label>

CSS Styling

.switch-input {
  position: absolute;
  opacity: 0;
}

.switch-label {
  display: inline-block;
  width: 50px;
  height: 26px;
  background-color: #ccc;
  border-radius: 13px;
  cursor: pointer;
  position: relative;
  transition: background-color 0.2s ease-in-out;
}

.switch-button {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: #fff;
  transition: transform 0.2s ease-in-out;
}

/* Active Switch States */
.switch-input:checked + .switch-label {
  background-color: #0070f3;
}

.switch-input:checked + .switch-label .switch-button {
  transform: translateX(24px);
}

When the label is activated, the checkbox toggles to :checked, immediately updating the label's background color and translating the inner .switch-button span along the X-axis.

Creating Accordions: Checkboxes vs. Radios

Accordions follow the same operational principle as toggles, but the choice of input type determines whether multiple panels can remain open simultaneously.

Multi-Panel Accordions (Checkboxes)

Using <input type="checkbox"> allows every panel to operate independently. Users can expand several sections simultaneously because each checkbox manages its own state isolated from other checkboxes.

Mutually Exclusive Accordions (Radio Buttons)

Using <input type="radio"> with a shared name attribute forces mutual exclusivity. When a user clicks a panel header, any previously opened panel within the same group collapses automatically because standard HTML only permits a single radio button per name group to be :checked at a time.

<div class="accordion-item">
  <input type="radio" name="faq" id="panel-1" class="accordion-input">
  <label for="panel-1" class="accordion-header">Section 1</label>
  <div class="accordion-body">
    <p>Content for the first section.</p>
  </div>
</div>

<div class="accordion-item">
  <input type="radio" name="faq" id="panel-2" class="accordion-input">
  <label for="panel-2" class="accordion-header">Section 2</label>
  <div class="accordion-body">
    <p>Content for the second section.</p>
  </div>
</div>

Animating Collapsible Content

A common limitation of the display: none to display: block pattern is that display cannot be animated with CSS transitions. To create smooth expansion effects, modern CSS offers two primary methods:

Method 1: CSS Grid (grid-template-rows)

Animating height from 0 to an unknown, dynamic height can be achieved smoothly by animating CSS Grid row templates:

.accordion-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.3s ease-out;
}

.accordion-body > div {
  overflow: hidden;
}

.accordion-input:checked ~ .accordion-body {
  grid-template-rows: 1fr;
}

Method 2: Maximum Height Transitions

A classic alternative utilizes max-height and overflow: hidden:

.accordion-body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-in-out;
}

.accordion-input:checked ~ .accordion-body {
  max-height: 500px; /* Must exceed expected inner content height */
}

Accessibility and Structural Considerations

While :checked-based components eliminate runtime dependencies on JavaScript, they introduce distinct accessibility and structural concerns:

  • DOM Order Dependence: Sibling combinators (+, ~) require the <input> to precede the target content in the DOM tree. This can limit complex layout decoupling unless modern :has() parent selectors (e.g., .card:has(.toggle:checked)) are adopted.
  • Screen Reader Semantics: Pure-CSS accordions convey state via standard input controls (checkbox or radio), not standard accordion roles (aria-expanded and aria-controls). Users of assistive technologies will hear form control interactions rather than disclosure widgets.
  • Native HTML Alternative: For accordions specifically, the native HTML <details> and <summary> elements provide built-in collapsible behavior with appropriate accessibility semantics out of the box, reducing the necessity of using the :checked hack for non-form disclosure patterns.