← Back to gallery
CSS

Grid Row Accordion Expand

An accordion that animates content height without measuring it — the outer wrapper is display: grid, and a CSS transition between grid-template-rows: 0fr and 1fr handles natural panel height. Three product contexts each tune the rhythm: an FAQ Stack (single-open help-centre disclosure), a Checkout Stepper (sequential summary rows), and a Filter Dock (multi-open sidebar groups with a count badge).

accordionexpand-collapsegrid-template-rows0fr-1frauto-height

Layout transition · grid-template-rows · 0fr → 1fr

Grid Row Accordion Expand

Accordion expand/collapse that animates content height without measuring it — the outer wrapper is display: grid, the inner panel sits in one grid row, and a CSS transition between grid-template-rows: 0fr and 1fr handles natural height. Three product contexts each tune the rhythm: an FAQ Stack (help-centre disclosure), a Checkout Stepper (sequential summary rows), and a Filter Dock (sidebar variable-height tag groups). Stage cards JS-cycle the open row every 2.4s; inspector preview is fully click-interactive and Tab cycles only between the row triggers.

Help centre · single-open disclosure

FAQ Stack

A stack of FAQ questions where each panel expands to show an answer, animating grid-template-rows from 0fr to 1fr without measuring height. Single-open behaviour keeps the page scrollable; the chevron rotates to show state.

  • FAQ
  • help-centre
  • chevron rotates

Sequential disclosure · summary rows

Checkout Stepper

A checkout stepper reveals one panel at a time with a grid-row expand. Collapsed rows keep their summary visible so users can scan the flow without re-opening every step. Active step is accented; completed steps fade.

  • checkout
  • stepper
  • summary row

Sidebar · variable-height groups

Filter Dock

Sidebar filter groups (Category, Price, Tags) expand in place while the page layout stays stable. Variable tag lists expand to natural height and shrink again smoothly without any height measurement.

  • sidebar
  • filters
  • variable height

Accordion expand inspector

FAQ Stack

click row

Request a reset link from the sign-in page and check your inbox. Links expire after 24 hours.

Share a workspace link from Settings → Team. Invited members land on a guided onboarding page.

Annual billing is available from Billing → Switch to yearly. The first month is prorated automatically.

  • FAQ
  • help-centre
  • chevron rotates

A stack of FAQ questions where each panel expands to show an answer, animating grid-template-rows from 0fr to 1fr without measuring height. Single-open behaviour keeps the page scrollable; the chevron rotates to show state.

Helped you ship something? 🐟 Send my cat a churu

/* FAQ stack — single-open accordion where the panel animates 0fr → 1fr on grid-template-rows. */
.accordion-item { border-bottom: 1px solid rgba(148, 163, 184, 0.18); }

.accordion-trigger {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: 12px 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

/* The collapsing wrapper. grid-template-rows is the animatable property:
   0fr → 1fr scales the row track between zero and natural height with
   no JS measurement. */
.accordion-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.30s ease;
}

.accordion-item[data-open='true'] .accordion-panel {
  grid-template-rows: 1fr;
}

/* The inner element clips to the row track. min-height: 0 lets the
   grid track collapse all the way to 0fr (default would be auto). */
.accordion-panel__inner {
  overflow: hidden;
  min-height: 0;
}

.accordion-chevron {
  transition: transform 0.30s ease;
}

.accordion-item[data-open='true'] .accordion-chevron {
  transform: rotate(180deg);
  color: #7dd3fc;
}

@media (prefers-reduced-motion: reduce) {
  .accordion-panel,
  .accordion-chevron { transition: none; }
}

How to make this

A CSS grid accordion animates natural height by transitioning a wrapper from grid-template-rows: 0fr to 1fr, with an inner overflow-hidden panel that can collapse to zero.

html
<section class="grid-acc">2  <input class="grid-acc__toggle" id="shipping" type="checkbox" checked>  <label class="grid-acc__trigger" for="shipping">    Shipping details <span aria-hidden="true">▾</span>  </label>6  <div class="grid-acc__panel">    <div class="grid-acc__inner">      <p>Ships in two days. Express delivery is available in 12 cities.</p>    </div>  </div></section> <style>.grid-acc {  width: min(100%, 360px);  color: #e2e8f0;  background: #0f172a;  border: 1px solid #334155;  border-radius: 14px;  padding: 0 .9rem;}.grid-acc__toggle {  position: absolute;  opacity: 0;}.grid-acc__trigger {  display: flex;  justify-content: space-between;  gap: 1rem;  padding: .9rem 0;  cursor: pointer;  font: 700 1rem/1.2 sans-serif;}.grid-acc__trigger span {  transition: transform 280ms ease;}37.grid-acc__panel {  display: grid;39  grid-template-rows: 0fr;  transition: grid-template-rows 280ms ease;}.grid-acc__toggle:checked ~ .grid-acc__panel {  grid-template-rows: 1fr;}.grid-acc__inner {46  overflow: hidden;  min-height: 0;}.grid-acc__inner p {  margin: 0 0 .9rem;  color: #cbd5e1;  line-height: 1.5;}.grid-acc__toggle:checked + .grid-acc__trigger span {55  transform: rotate(180deg);}@media (prefers-reduced-motion: reduce) {  .grid-acc__panel,  .grid-acc__trigger span { transition: none; }}</style>

Annotated snippet

  1. Line 2The checkbox keeps the snippet paste-and-run without JavaScript. In production, prefer a real button that updates aria-expanded and aria-controls so the disclosure state is explicit.
  2. Line 6The panel wrapper is separate from the content wrapper. The outer grid row animates; the inner element clips content as the row track shrinks.
  3. Line 37display: grid enables the 0fr to 1fr trick. Unlike height: auto, fractional grid tracks can transition between collapsed and natural-height states.

    Only the technique changes here. height: 0 to auto cannot interpolate, so the panel snaps open with no slide; grid-template-rows: 0fr to 1fr interpolates the track height and the same content opens smoothly.

    PitfallWhy use grid-template-rows instead of animating height: auto?

    Browsers cannot interpolate from height: 0 to height: auto in the way accordion content needs. The grid method moves between 0fr and 1fr, so the row can resolve to the content natural height while still giving the browser numeric states to animate.

  4. Line 390fr is the collapsed row. The content still exists in the DOM, but the row track has no height until the open selector changes it.
    PitfallWhy does my 0fr accordion still show content?

    The inner grid child probably needs min-height: 0, or the content wrapper is missing overflow: hidden. The outer wrapper owns grid-template-rows; the inner wrapper must be allowed to shrink and clip while the row track collapses.

  5. Line 46overflow: hidden clips the text during the collapse, and min-height: 0 lets the grid child shrink all the way to the 0fr track.

    Static comparison — both panels have grid-template-rows: 0fr (collapsed). Without min-height: 0 on the grid child, the row track tries to be 0 but the child clings to its intrinsic content height and the row never actually closes. min-height: 0 frees the child to shrink and the panel finally reaches zero.

    PitfallIs animating grid-template-rows performant?

    It is a layout animation, so it can cost more than transform or opacity. For a small FAQ or settings panel it is usually fine. Avoid animating dozens of large panels at once, and keep expensive images or embeds out of the collapsing region until needed.

  6. Line 55Reduced motion removes the transition, not the disclosure. The content should still open and close instantly through the same state selectors.

Other pitfalls

How should I make a grid accordion accessible?
Use a real button for the trigger, update aria-expanded, point aria-controls at the panel id, and keep keyboard order in visual order. The checkbox technique is acceptable for a tiny CSS-only demo, but production accordions need explicit state for assistive technology.
Which browsers support the grid row accordion trick?
Modern Chromium, Firefox, and Safari handle grid-template-rows transitions for this pattern. Older browsers may jump between states instead of animating. That fallback is acceptable as long as the content still opens, closes, and remains readable.

Notes

Overview

Animating an accordion’s height to auto is the canonical CSS frustration — you cannot directly transition height between zero and an unknown value. The grid-template-rows: 0fr 1fr trick sidesteps the problem: the outer wrapper is a grid track, and grid resolves the row size dynamically. Animating from 0fr to 1fr lets the panel breathe to its natural height with no JS measurement.

When to use it

Reach for the grid-rows accordion on FAQ sections, sidebar filter groups, checkout summaries, settings panels — anywhere a row of content needs to open and close without layout jank. Skip it for content that contains nested accordions of unknown depth (the nested transitions stack oddly) and for content with position: sticky children inside (sticky breaks across grid track changes).

How it works

The outer wrapper sets display: grid; grid-template-rows: 0fr (closed) or 1fr (open). Its only child is an inner wrapper with overflow: hidden; min-height: 0 — without the min-height: 0 the grid track refuses to shrink below the content’s intrinsic size. The actual content lives inside that inner wrapper. Toggling a class flips the grid-rows value, which is a transitionable length per the CSS Grid Level 2 spec, so the height interpolates smoothly. No JS measurement, no scrollHeight reads, no cleanup on collapse.

Production gotchas

Forgetting the min-height: 0 on the inner wrapper is the canonical bug — the grid track stays at the content’s intrinsic height and never collapses to zero, so closing the accordion does nothing. The animation can fight content with its own height animations (nested accordions, expanding images); resolve by giving the outer wrapper a longer transition than the children, or by suppressing nested transitions during the parent transition. Safari needs contain: layout on the inner wrapper to avoid a small flicker at the end of the transition on retina displays.

Accessibility

Use a real <button aria-expanded> for the toggle (or the native <details> + <summary> if styling permits) so screen-reader users hear the expanded/collapsed state. Set aria-controls on the trigger pointing to the panel ID. Under prefers-reduced-motion: reduce drop the transition so the panel snaps open and closed without the height tween — the disclosure pattern still works without motion.

References

Implementation depth

Use this as a disclosure layout primitive, not as a generic height tween. The grid-template-rows 0fr to 1fr trick works because the browser can resolve the intrinsic content height inside a fractional grid track without measuring in JavaScript.

The practical failure mode is nested content that keeps its own overflow visible. Keep the inner wrapper overflow hidden, keep focusable children reachable only when the panel is open, and verify that reduced motion still leaves the expanded state readable.