25 CSS FAQ Accordions11 / 25

Pure CSSMIT licensed

Chevron/Arrow Rotate 180° on Toggle FAQ

The definitive treatment of the web's most-copied micro-interaction. Three chevron builds side by side in one FAQ — inline SVG (crisp at any size), a two-border CSS chevron (zero assets), and a Unicode arrow (one character) — each rotating 180° on toggle with the same shared timing, plus the UX rules for which direction a closed chevron should point and why the rotation must match the panel's duration.

Published

Live Demo
Try it

The code

<section class="cfa-11" aria-label="Chevron rotate on toggle FAQ">
  <div class="cfa-11__wrap">
    <header class="cfa-11__head">
      <h1>The 180° chevron, three ways</h1>
      <p>Same rotation, three icon builds — SVG, pure CSS borders, Unicode. One <code>--speed</code> token keeps icon and panel in sync.</p>
    </header>
    <div class="cfa-11__list">
      <details class="cfa-11__item" open>
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">A · inline SVG</span>Which icon build should production use?
          <span class="cfa-11__chev cfa-11__chev--svg" aria-hidden="true"><svg viewBox="0 0 16 16" width="16" height="16"><path d="M3 6l5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
        </summary>
        <div class="cfa-11__a"><p>This one. <code>stroke: currentColor</code> inherits your text color automatically, the rounded caps stay crisp at every DPI, and rotating the whole svg element avoids every alignment quirk the other two builds carry.</p></div>
      </details>
      <details class="cfa-11__item">
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">B · CSS borders</span>How does the zero-asset chevron work?
          <span class="cfa-11__chev cfa-11__chev--css" aria-hidden="true"></span>
        </summary>
        <div class="cfa-11__a"><p>A 9px square with only its right and bottom borders, pre-rotated 45° — so “rotate 180°” actually means <code>rotate: 225deg</code>, and a 2px <code>translate</code> nudge re-centers its optical middle. Zero assets, two gotchas.</p></div>
      </details>
      <details class="cfa-11__item">
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">C · Unicode</span>When is a text arrow good enough?
          <span class="cfa-11__chev cfa-11__chev--uni" aria-hidden="true">⌄</span>
        </summary>
        <div class="cfa-11__a"><p>Prototypes and plain-text contexts. It costs one character, but its baseline position depends on the font stack — the <code>line-height: 1</code> and explicit sizing here tame it, mostly. Ship SVG when it matters.</p></div>
      </details>
      <details class="cfa-11__item">
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">UX</span>Which way should a closed chevron point?
          <span class="cfa-11__chev cfa-11__chev--svg" aria-hidden="true"><svg viewBox="0 0 16 16" width="16" height="16"><path d="M3 6l5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
        </summary>
        <div class="cfa-11__a"><p>Down when closed (“content continues below”), up when open. Right-pointing chevrons signal navigation into another screen — using one on an accordion misleads screen-magnifier and low-vision users especially. And match the rotation duration to the panel: mismatched timings read as jank.</p></div>
      </details>
    </div>
    <p class="cfa-11__foot">All three rotate with <code>rotate: 180deg</code> (B: 225deg) over the shared <code>--speed: .4s</code>. CodeFronts collection.</p>
  </div>
</section>
.cfa-11,
.cfa-11 *,
.cfa-11 *::before,
.cfa-11 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cfa-11 {
  --bg: oklch(0.97 0.008 250);
  --panel: oklch(1 0 0);
  --ink: oklch(0.23 0.03 255);
  --mut: oklch(0.5 0.02 252);
  --line: oklch(0.89 0.012 250);
  --acc: oklch(0.55 0.18 250);
  --speed: .4s;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(20px,4vw,56px);
  background: var(--bg);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
}

.cfa-11__wrap {
  width: min(100%,660px);
}

.cfa-11__head h1 {
  font-size: clamp(26px,4.5cqi,38px);
  font-weight: 800;
  letter-spacing: -.025em;
}

.cfa-11__head p {
  margin-top: 8px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--mut);
}

.cfa-11__head code,
.cfa-11__a code,
.cfa-11__foot code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: .87em;
  background: color-mix(in oklch,var(--acc) 9%,transparent);
  padding: 2px 6px;
  border-radius: 5px;
  color: var(--acc);
}

.cfa-11__list {
  margin-top: 26px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  interpolate-size: allow-keywords;
}

.cfa-11__item {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 2px 8px oklch(0.4 0.05 250/.05);
  transition: border-color .3s;
}

.cfa-11__item[open] {
  border-color: color-mix(in oklch,var(--acc) 35%,var(--line));
}

.cfa-11__q {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 15px 18px;
  cursor: pointer;
  font-size: 15px;
  font-weight: 650;
  list-style: none;
  transition: color .2s;
}

.cfa-11__q::-webkit-details-marker {
  display: none;
}

.cfa-11__q:hover {
  color: var(--acc);
}

.cfa-11__q:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: -2px;
  border-radius: 14px;
}

.cfa-11__tagchip {
  flex: none;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--acc);
  padding: 4px 8px;
  background: color-mix(in oklch,var(--acc) 10%,transparent);
  border-radius: 6px;
}

.cfa-11__chev {
  margin-left: auto;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--mut);
  transition: rotate var(--speed) cubic-bezier(.4,0,.2,1),color .25s;
}

.cfa-11__item[open] .cfa-11__chev {
  color: var(--acc);
}

.cfa-11__item[open] .cfa-11__chev--svg {
  rotate: 180deg;
}

.cfa-11__chev--css {
  width: 9px;
  height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  rotate: 45deg;
  translate: 0 -2px;
}

.cfa-11__item[open] .cfa-11__chev--css {
  rotate: 225deg;
  translate: 0 2px;
}

.cfa-11__chev--uni {
  font-size: 18px;
  line-height: 1;
  font-weight: 700;
  translate: 0 -2px;
}

.cfa-11__item[open] .cfa-11__chev--uni {
  rotate: 180deg;
  translate: 0 2px;
}

.cfa-11__a p {
  padding: 0 18px 17px;
  font-size: 14px;
  line-height: 1.7;
  color: var(--mut);
  max-width: 60ch;
}

@supports (interpolate-size: allow-keywords) {
  .cfa-11__item::details-content {
    height: 0;
    overflow: clip;
    opacity: 0;
    transition: height var(--speed) cubic-bezier(.4,0,.2,1),opacity .32s,content-visibility var(--speed) allow-discrete;
  }

  .cfa-11__item[open]::details-content {
    height: auto;
    opacity: 1;
  }
}

.cfa-11__foot {
  margin-top: 22px;
  font-size: 12.5px;
  color: var(--mut);
}

@media (prefers-reduced-motion: reduce) {
  .cfa-11 *,
    .cfa-11 .cfa-11__item::details-content {
    transition-duration: .01ms !important;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS FAQ Accordion from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Chevron/Arrow Rotate 180° on Toggle FAQ
Source: https://codefronts.com/snippets/css-faq-accordion/chevron-arrow-rotate-180-on-toggle-faq/

The definitive treatment of the web's most-copied micro-interaction. Three chevron builds side by side in one FAQ — inline SVG (crisp at any size), a two-border CSS chevron (zero assets), and a Unicode arrow (one character) — each rotating 180° on toggle with the same shared timing, plus the UX rules for which direction a closed chevron should point and why the rotation must match the panel's duration.
## HTML
```html
<section class="cfa-11" aria-label="Chevron rotate on toggle FAQ">
  <div class="cfa-11__wrap">
    <header class="cfa-11__head">
      <h1>The 180° chevron, three ways</h1>
      <p>Same rotation, three icon builds — SVG, pure CSS borders, Unicode. One <code>--speed</code> token keeps icon and panel in sync.</p>
    </header>
    <div class="cfa-11__list">
      <details class="cfa-11__item" open>
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">A · inline SVG</span>Which icon build should production use?
          <span class="cfa-11__chev cfa-11__chev--svg" aria-hidden="true"><svg viewBox="0 0 16 16" width="16" height="16"><path d="M3 6l5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
        </summary>
        <div class="cfa-11__a"><p>This one. <code>stroke: currentColor</code> inherits your text color automatically, the rounded caps stay crisp at every DPI, and rotating the whole svg element avoids every alignment quirk the other two builds carry.</p></div>
      </details>
      <details class="cfa-11__item">
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">B · CSS borders</span>How does the zero-asset chevron work?
          <span class="cfa-11__chev cfa-11__chev--css" aria-hidden="true"></span>
        </summary>
        <div class="cfa-11__a"><p>A 9px square with only its right and bottom borders, pre-rotated 45° — so “rotate 180°” actually means <code>rotate: 225deg</code>, and a 2px <code>translate</code> nudge re-centers its optical middle. Zero assets, two gotchas.</p></div>
      </details>
      <details class="cfa-11__item">
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">C · Unicode</span>When is a text arrow good enough?
          <span class="cfa-11__chev cfa-11__chev--uni" aria-hidden="true">⌄</span>
        </summary>
        <div class="cfa-11__a"><p>Prototypes and plain-text contexts. It costs one character, but its baseline position depends on the font stack — the <code>line-height: 1</code> and explicit sizing here tame it, mostly. Ship SVG when it matters.</p></div>
      </details>
      <details class="cfa-11__item">
        <summary class="cfa-11__q"><span class="cfa-11__tagchip">UX</span>Which way should a closed chevron point?
          <span class="cfa-11__chev cfa-11__chev--svg" aria-hidden="true"><svg viewBox="0 0 16 16" width="16" height="16"><path d="M3 6l5 5 5-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
        </summary>
        <div class="cfa-11__a"><p>Down when closed (“content continues below”), up when open. Right-pointing chevrons signal navigation into another screen — using one on an accordion misleads screen-magnifier and low-vision users especially. And match the rotation duration to the panel: mismatched timings read as jank.</p></div>
      </details>
    </div>
    <p class="cfa-11__foot">All three rotate with <code>rotate: 180deg</code> (B: 225deg) over the shared <code>--speed: .4s</code>. CodeFronts collection.</p>
  </div>
</section>
```
## CSS
```css
.cfa-11,
.cfa-11 *,
.cfa-11 *::before,
.cfa-11 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cfa-11 {
  --bg: oklch(0.97 0.008 250);
  --panel: oklch(1 0 0);
  --ink: oklch(0.23 0.03 255);
  --mut: oklch(0.5 0.02 252);
  --line: oklch(0.89 0.012 250);
  --acc: oklch(0.55 0.18 250);
  --speed: .4s;
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(20px,4vw,56px);
  background: var(--bg);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
}

.cfa-11__wrap {
  width: min(100%,660px);
}

.cfa-11__head h1 {
  font-size: clamp(26px,4.5cqi,38px);
  font-weight: 800;
  letter-spacing: -.025em;
}

.cfa-11__head p {
  margin-top: 8px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--mut);
}

.cfa-11__head code,
.cfa-11__a code,
.cfa-11__foot code {
  font-family: ui-monospace,Menlo,monospace;
  font-size: .87em;
  background: color-mix(in oklch,var(--acc) 9%,transparent);
  padding: 2px 6px;
  border-radius: 5px;
  color: var(--acc);
}

.cfa-11__list {
  margin-top: 26px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  interpolate-size: allow-keywords;
}

.cfa-11__item {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 2px 8px oklch(0.4 0.05 250/.05);
  transition: border-color .3s;
}

.cfa-11__item[open] {
  border-color: color-mix(in oklch,var(--acc) 35%,var(--line));
}

.cfa-11__q {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 15px 18px;
  cursor: pointer;
  font-size: 15px;
  font-weight: 650;
  list-style: none;
  transition: color .2s;
}

.cfa-11__q::-webkit-details-marker {
  display: none;
}

.cfa-11__q:hover {
  color: var(--acc);
}

.cfa-11__q:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: -2px;
  border-radius: 14px;
}

.cfa-11__tagchip {
  flex: none;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--acc);
  padding: 4px 8px;
  background: color-mix(in oklch,var(--acc) 10%,transparent);
  border-radius: 6px;
}

.cfa-11__chev {
  margin-left: auto;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--mut);
  transition: rotate var(--speed) cubic-bezier(.4,0,.2,1),color .25s;
}

.cfa-11__item[open] .cfa-11__chev {
  color: var(--acc);
}

.cfa-11__item[open] .cfa-11__chev--svg {
  rotate: 180deg;
}

.cfa-11__chev--css {
  width: 9px;
  height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  rotate: 45deg;
  translate: 0 -2px;
}

.cfa-11__item[open] .cfa-11__chev--css {
  rotate: 225deg;
  translate: 0 2px;
}

.cfa-11__chev--uni {
  font-size: 18px;
  line-height: 1;
  font-weight: 700;
  translate: 0 -2px;
}

.cfa-11__item[open] .cfa-11__chev--uni {
  rotate: 180deg;
  translate: 0 2px;
}

.cfa-11__a p {
  padding: 0 18px 17px;
  font-size: 14px;
  line-height: 1.7;
  color: var(--mut);
  max-width: 60ch;
}

@supports (interpolate-size: allow-keywords) {
  .cfa-11__item::details-content {
    height: 0;
    overflow: clip;
    opacity: 0;
    transition: height var(--speed) cubic-bezier(.4,0,.2,1),opacity .32s,content-visibility var(--speed) allow-discrete;
  }

  .cfa-11__item[open]::details-content {
    height: auto;
    opacity: 1;
  }
}

.cfa-11__foot {
  margin-top: 22px;
  font-size: 12.5px;
  color: var(--mut);
}

@media (prefers-reduced-motion: reduce) {
  .cfa-11 *,
    .cfa-11 .cfa-11__item::details-content {
    transition-duration: .01ms !important;
  }
}
```

How this works

The mechanism is one transition + one state selector, whatever the icon is made of:

.chev{ transition:rotate .4s cubic-bezier(.4,0,.2,1) }
details[open] .chev{ rotate:180deg }

Note rotate, the individual transform property — it composes with any translate/scale the icon already has without the classic "transform overwrote my centering" bug, and it is animatable on the compositor. The three builds:

/* A · inline SVG — production choice */
<svg viewBox="0 0 16 16"><path d="M3 6l5 5 5-5" fill="none"
     stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>

/* B · pure CSS — two borders, rotated 45° */
.chev-css{ width:9px; height:9px;
  border-right:2px solid; border-bottom:2px solid; rotate:45deg }

/* C · Unicode — ⌄ costs one character */

SVG wins in production: stroke:currentColor inherits text color, stroke-linecap rounds the joints, and it scales crisp on any DPI. The CSS chevron needs a translate nudge because its visual center is not its box center (build B carries the fix). The Unicode arrow is for prototypes — its baseline alignment varies per font.

UX rules worth stealing: closed points down ("more below"), open points up — never right-to-down, which signals nav-drilldown, not disclosure. And the rotation duration must equal the panel's height duration; a 200ms flick over a 500ms panel reads broken, which is why both live on one --speed token here.

Make it yours

  • One token: --speed drives chevron and panel together — the demo's whole point.
  • 270° twirl: rotate:270deg on [open] adds playfulness; keep under 300ms or it upstages the content.
  • Color shift on open: pair the rotate with color:var(--acc) — state reads even in peripheral vision.
  • Circle-chip chevron: wrap build A in a 28px bordered circle (demo 02 does this) for a stronger hit-target affordance.

Gotchas — read before shipping

  • transform:rotate(180deg) in a rule that also needs translate centers WILL overwrite it — use the individual rotate/translate properties and they compose.
  • Rotating a border-built chevron 180° from 45° means going to 225deg, not 180 — the pre-rotation offsets; SVG avoids this entire class of bug.
  • Keep the chevron OUTSIDE the text flow (flex end-aligned), or long questions wrap under it.
  • aria-hidden="true" on the icon always — the state is already announced by details/aria-expanded; a decorative arrow must not be read out.

Browser support

ChromeSafariFirefoxEdge
129+not yetnot yet129+

Floor set by oklch(), color-mix(), interpolate-size as this demo is written. The core technique itself goes back further — Chrome 104+.

Individual transform properties (rotate/translate) are 2022-settled everywhere. Everything else is universal details + transitions; oklch cosmetics as usual.

Techniques used in this demo

Search CodeFronts

Loading…