Skip to content

Instantly share code, notes, and snippets.

@dragontheory
Created January 19, 2026 14:40
Show Gist options
  • Select an option

  • Save dragontheory/3dbd4bc81339cd58a3fe552dd9fbf069 to your computer and use it in GitHub Desktop.

Select an option

Save dragontheory/3dbd4bc81339cd58a3fe552dd9fbf069 to your computer and use it in GitHub Desktop.
Practical, copy‑paste CSS state system patterns built on hidden‑checkbox/label toggles, `:has()`, style queries, and other modern CSS features.

Image

Practical, copy‑paste CSS state system patterns (no JS) built on hidden‑checkbox/label toggles, :has(), style queries, and other modern CSS features:


1) Hidden‑Checkbox Toggle (classic “CSS checkbox hack”)

Use a hidden <input type="checkbox"> as a state driver. Clicking the <label> toggles :checked which styles siblings. Works for menus, accordions, modals, etc.(CSS-Tricks)

<input type="checkbox" id="toggle" hidden>
<label for="toggle">Menu</label>

<nav class="menu">
  <a href="#">Home</a>
  <a href="#">About</a>
</nav>

<style>
  .menu { display: none; }
  #toggle:checked + label + .menu {
    display: block;
  }
</style>

Pattern:

#stateElement:checked + label + .target { /* active style */ }

2) Accessible <details> Accordion (native state)

Use <details> + <summary> — built‑in toggle with no CSS/JS. You can style based on the open attribute.(web.dev)

<details>
  <summary>Section 1</summary>
  <p>Hidden content</p>
</details>

<style>
  details[open] summary { font-weight: bold; }
</style>

Pattern:

details[open] .child { /* open styles */ }

3) Parent Selectors with :has() (replace JS class toggling)

Use :has() to style a parent based on a child state (e.g., open menus/tabs) — ideal for complex panel toggles.(Medium)

<nav class="nav">
  <button class="btn">Toggle</button>
  <ul class="links">
    <li>Item</li>
  </ul>
</nav>

<style>
  .nav:has(.btn:focus + .links) {
    background: rgba(0,0,0,.1);
  }
</style>

Pattern:

.container:has(.trigger:active) .target { /* active styles */ }

4) Style Queries (conditional CSS based on element size)

Use CSS style queries (@when, @else) to adapt UI based on container dimension — can replace many JS layout checks.

Note: relatively new / experimental spec (@when etc. is evolving).(YouTube)

@container (min-width: 400px) {
  .card { padding: 2rem; }
}

Pattern:

@container (condition) { selector { styles } }

5) Pure CSS Tabs (hidden radios + label)

Radios ensure mutually exclusive states (tabs) without JS.(CSS-Tricks)

<input type="radio" name="tab" id="tab1" hidden checked>
<label for="tab1">Tab 1</label>

<input type="radio" name="tab" id="tab2" hidden>
<label for="tab2">Tab 2</label>

<section id="content1"></section>
<section id="content2"></section>

<style>
  #tab1:checked ~ #content1 { display: block; }
  #tab2:checked ~ #content2 { display: block; }
  section { display: none; }
</style>

6) Custom States with :state() (for custom elements)

If using custom elements with a CustomStateSet, :state() lets CSS react to custom states — useful for web components.(MDN Web Docs)

my-toggle:state(on) { background: lime; }
my-toggle:state(off) { background: grey; }

7) Animations Driven by :checked (CSS UI feedback)

Checkbox state can trigger CSS transitions/animations — no JS timers needed.(CSS-Tricks)

#toggle:checked ~ .box {
  transform: translateX(100px);
  transition: transform .3s ease;
}

Quick patterns summary

Pattern CSS Trigger Use Cases
Hidden checkbox :checked Menus, modals, overlays
<details> open attribute Disclosure/accordion
:has() parent selector child state Tabs, dropdowns
Style queries @container Responsive UI logic
Radio tabs :checked excl. Tab systems
Custom states :state() Web components
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment