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.

Live Demo
Plain CSSTailwind v4
Try it

Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49

Cruip · Affiliate link

Browse Cruip templates

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…