32 CSS Accordions — Vertical & Horizontal19 / 32

Pure CSSMIT licensed

Code Comment Accordion — Editor-Themed Collapsible Doc Blocks

An accordion cosplaying as your editor: summaries are // comment lines with fold arrows in a line-numbered gutter, and opening one 'unfolds' a syntax-highlighted code block beneath — exactly how code folding looks in VS Code. Made for API docs, developer landing pages and technical blogs.

Published Updated

Live Demo
Try it

The code

<section class="acc-27" aria-label="Code comment editor accordion demo">
  <div class="acc-27__win">
    <div class="acc-27__bar"><span class="acc-27__dots"><i></i><i></i><i></i></span><span class="acc-27__tab">quickstart.js</span></div>
    <div class="acc-27__editor">
      <details class="acc-27__fold" open>
        <summary class="acc-27__sum">// how do I authenticate?</summary>
        <pre class="acc-27__code"><code><span class="acc-27__ln"><b class="acc-27__kw">import</b> { Client } <b class="acc-27__kw">from</b> <i class="acc-27__str">'@acme/sdk'</i>;</span>
<span class="acc-27__ln"></span>
<span class="acc-27__ln"><b class="acc-27__kw">const</b> client = <b class="acc-27__kw">new</b> <em class="acc-27__fn">Client</em>({</span>
<span class="acc-27__ln">  apiKey: process.env.<em class="acc-27__fn">ACME_KEY</em>  <s class="acc-27__cm">// server-side only</s></span>
<span class="acc-27__ln">});</span></code></pre>
      </details>
      <details class="acc-27__fold">
        <summary class="acc-27__sum">// how do I fetch a record?</summary>
        <pre class="acc-27__code"><code><span class="acc-27__ln"><b class="acc-27__kw">const</b> order = <b class="acc-27__kw">await</b> client.orders.<em class="acc-27__fn">get</em>(<i class="acc-27__str">'ord_8h2k'</i>);</span>
<span class="acc-27__ln">console.<em class="acc-27__fn">log</em>(order.status); <s class="acc-27__cm">// "shipped"</s></span></code></pre>
      </details>
      <details class="acc-27__fold">
        <summary class="acc-27__sum">// how do I handle errors?</summary>
        <pre class="acc-27__code"><code><span class="acc-27__ln"><b class="acc-27__kw">try</b> {</span>
<span class="acc-27__ln">  <b class="acc-27__kw">await</b> client.orders.<em class="acc-27__fn">create</em>(payload);</span>
<span class="acc-27__ln">} <b class="acc-27__kw">catch</b> (err) {</span>
<span class="acc-27__ln">  <b class="acc-27__kw">if</b> (err.code === <i class="acc-27__str">'rate_limited'</i>) <b class="acc-27__kw">return</b> <em class="acc-27__fn">retry</em>(err.after);</span>
<span class="acc-27__ln">  <b class="acc-27__kw">throw</b> err; <s class="acc-27__cm">// everything else is yours</s></span>
<span class="acc-27__ln">}</span></code></pre>
      </details>
    </div>
  </div>
</section>
.acc-27,
.acc-27 *,
.acc-27 *::before,
.acc-27 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.acc-27 {
  --bg: #1a1d27;
  --gutter: #252937;
  --tok-kw: #c792ea;
  --tok-str: #c3e88d;
  --tok-fn: #82aaff;
  --tok-cm: #8b93ab;
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #10121a;
  padding: 44px 24px;
}

.acc-27__win {
  width: min(640px,100%);
  background: var(--bg);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 30px 70px -30px rgba(0,0,0,.8);
  border: 1px solid #2c3040;
}

.acc-27__bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  background: #151823;
  border-bottom: 1px solid #2c3040;
}

.acc-27__dots {
  display: flex;
  gap: 7px;
}

.acc-27__dots i {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #3d4254;
}

.acc-27__dots i:first-child {
  background: #e5695e;
}

.acc-27__dots i:nth-child(2) {
  background: #e0b055;
}

.acc-27__dots i:last-child {
  background: #5fb96a;
}

.acc-27__tab {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 12px;
  color: #aab2c8;
  background: var(--bg);
  padding: 5px 14px;
  border-radius: 7px 7px 0 0;
  translate: 0 6px;
  border: 1px solid #2c3040;
  border-bottom-color: var(--bg);
}

.acc-27__editor {
  padding: 16px 0 20px;
  counter-reset: acc-27-line;
  font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
}

.acc-27__sum {
  list-style: none;
  display: block;
  position: relative;
  padding: 6px 18px 6px 64px;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--tok-cm);
  font-style: italic;
  cursor: pointer;
  counter-increment: acc-27-line;
  transition: background .15s,color .15s;
}

.acc-27__sum::-webkit-details-marker {
  display: none;
}

.acc-27__sum::marker {
  content: "";
}

.acc-27__sum:hover {
  background: #20242f;
  color: #b8c0d8;
}

.acc-27__sum::before {
  content: counter(acc-27-line);
  position: absolute;
  left: 0;
  width: 40px;
  text-align: right;
  color: #4a5065;
  font-style: normal;
  font-size: 12px;
}

.acc-27__sum::after {
  content: "";
  position: absolute;
  left: 48px;
  top: 50%;
  translate: 0 -50%;
  border-left: 5px solid #6d7590;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: rotate .2s;
}

.acc-27__fold[open] > .acc-27__sum::after {
  rotate: 90deg;
}

.acc-27__fold[open] > .acc-27__sum {
  color: #b8c0d8;
}

.acc-27__code {
  overflow-x: auto;
}

.acc-27__code code {
  display: block;
}

.acc-27__ln {
  display: block;
  position: relative;
  padding: 0 18px 0 64px;
  font-size: 13.5px;
  line-height: 1.7;
  color: #d6dcee;
  counter-increment: acc-27-line;
  white-space: pre;
}

.acc-27__ln::before {
  content: counter(acc-27-line);
  position: absolute;
  left: 0;
  width: 40px;
  text-align: right;
  color: #3c4257;
  font-size: 12px;
}

.acc-27__kw {
  color: var(--tok-kw);
  font-weight: 400;
  font-style: italic;
}

.acc-27__str {
  color: var(--tok-str);
  font-style: normal;
}

.acc-27__fn {
  color: var(--tok-fn);
  font-style: normal;
}

.acc-27__cm {
  color: var(--tok-cm);
  text-decoration: none;
}

.acc-27__fold[open] .acc-27__code {
  animation: acc-27-in .25s ease-out;
}

@keyframes acc-27-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.acc-27__sum:focus-visible {
  outline: 2px solid var(--tok-fn);
  outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
  .acc-27__fold[open] .acc-27__code {
    animation: none;
  }

  .acc-27__sum::after {
    transition: none;
  }
}
/* No JavaScript — summaries and code lines share one CSS counter, so line numbers renumber live as folds open, exactly like an editor; syntax colors are hand-spanned token classes. */
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 Accordions — Vertical & Horizontal 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: Code Comment Accordion — Editor-Themed Collapsible Doc Blocks
Source: https://codefronts.com/navigation/css-accordions/code-comment/

An accordion cosplaying as your editor: summaries are // comment lines with fold arrows in a line-numbered gutter, and opening one 'unfolds' a syntax-highlighted code block beneath — exactly how code folding looks in VS Code. Made for API docs, developer landing pages and technical blogs.
## HTML
```html
<section class="acc-27" aria-label="Code comment editor accordion demo">
  <div class="acc-27__win">
    <div class="acc-27__bar"><span class="acc-27__dots"><i></i><i></i><i></i></span><span class="acc-27__tab">quickstart.js</span></div>
    <div class="acc-27__editor">
      <details class="acc-27__fold" open>
        <summary class="acc-27__sum">// how do I authenticate?</summary>
        <pre class="acc-27__code"><code><span class="acc-27__ln"><b class="acc-27__kw">import</b> { Client } <b class="acc-27__kw">from</b> <i class="acc-27__str">'@acme/sdk'</i>;</span>
<span class="acc-27__ln"></span>
<span class="acc-27__ln"><b class="acc-27__kw">const</b> client = <b class="acc-27__kw">new</b> <em class="acc-27__fn">Client</em>({</span>
<span class="acc-27__ln">  apiKey: process.env.<em class="acc-27__fn">ACME_KEY</em>  <s class="acc-27__cm">// server-side only</s></span>
<span class="acc-27__ln">});</span></code></pre>
      </details>
      <details class="acc-27__fold">
        <summary class="acc-27__sum">// how do I fetch a record?</summary>
        <pre class="acc-27__code"><code><span class="acc-27__ln"><b class="acc-27__kw">const</b> order = <b class="acc-27__kw">await</b> client.orders.<em class="acc-27__fn">get</em>(<i class="acc-27__str">'ord_8h2k'</i>);</span>
<span class="acc-27__ln">console.<em class="acc-27__fn">log</em>(order.status); <s class="acc-27__cm">// "shipped"</s></span></code></pre>
      </details>
      <details class="acc-27__fold">
        <summary class="acc-27__sum">// how do I handle errors?</summary>
        <pre class="acc-27__code"><code><span class="acc-27__ln"><b class="acc-27__kw">try</b> {</span>
<span class="acc-27__ln">  <b class="acc-27__kw">await</b> client.orders.<em class="acc-27__fn">create</em>(payload);</span>
<span class="acc-27__ln">} <b class="acc-27__kw">catch</b> (err) {</span>
<span class="acc-27__ln">  <b class="acc-27__kw">if</b> (err.code === <i class="acc-27__str">'rate_limited'</i>) <b class="acc-27__kw">return</b> <em class="acc-27__fn">retry</em>(err.after);</span>
<span class="acc-27__ln">  <b class="acc-27__kw">throw</b> err; <s class="acc-27__cm">// everything else is yours</s></span>
<span class="acc-27__ln">}</span></code></pre>
      </details>
    </div>
  </div>
</section>
```
## CSS
```css
.acc-27,
.acc-27 *,
.acc-27 *::before,
.acc-27 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.acc-27 {
  --bg: #1a1d27;
  --gutter: #252937;
  --tok-kw: #c792ea;
  --tok-str: #c3e88d;
  --tok-fn: #82aaff;
  --tok-cm: #8b93ab;
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #10121a;
  padding: 44px 24px;
}

.acc-27__win {
  width: min(640px,100%);
  background: var(--bg);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 30px 70px -30px rgba(0,0,0,.8);
  border: 1px solid #2c3040;
}

.acc-27__bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  background: #151823;
  border-bottom: 1px solid #2c3040;
}

.acc-27__dots {
  display: flex;
  gap: 7px;
}

.acc-27__dots i {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #3d4254;
}

.acc-27__dots i:first-child {
  background: #e5695e;
}

.acc-27__dots i:nth-child(2) {
  background: #e0b055;
}

.acc-27__dots i:last-child {
  background: #5fb96a;
}

.acc-27__tab {
  font-family: ui-monospace,Menlo,monospace;
  font-size: 12px;
  color: #aab2c8;
  background: var(--bg);
  padding: 5px 14px;
  border-radius: 7px 7px 0 0;
  translate: 0 6px;
  border: 1px solid #2c3040;
  border-bottom-color: var(--bg);
}

.acc-27__editor {
  padding: 16px 0 20px;
  counter-reset: acc-27-line;
  font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
}

.acc-27__sum {
  list-style: none;
  display: block;
  position: relative;
  padding: 6px 18px 6px 64px;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--tok-cm);
  font-style: italic;
  cursor: pointer;
  counter-increment: acc-27-line;
  transition: background .15s,color .15s;
}

.acc-27__sum::-webkit-details-marker {
  display: none;
}

.acc-27__sum::marker {
  content: "";
}

.acc-27__sum:hover {
  background: #20242f;
  color: #b8c0d8;
}

.acc-27__sum::before {
  content: counter(acc-27-line);
  position: absolute;
  left: 0;
  width: 40px;
  text-align: right;
  color: #4a5065;
  font-style: normal;
  font-size: 12px;
}

.acc-27__sum::after {
  content: "";
  position: absolute;
  left: 48px;
  top: 50%;
  translate: 0 -50%;
  border-left: 5px solid #6d7590;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: rotate .2s;
}

.acc-27__fold[open] > .acc-27__sum::after {
  rotate: 90deg;
}

.acc-27__fold[open] > .acc-27__sum {
  color: #b8c0d8;
}

.acc-27__code {
  overflow-x: auto;
}

.acc-27__code code {
  display: block;
}

.acc-27__ln {
  display: block;
  position: relative;
  padding: 0 18px 0 64px;
  font-size: 13.5px;
  line-height: 1.7;
  color: #d6dcee;
  counter-increment: acc-27-line;
  white-space: pre;
}

.acc-27__ln::before {
  content: counter(acc-27-line);
  position: absolute;
  left: 0;
  width: 40px;
  text-align: right;
  color: #3c4257;
  font-size: 12px;
}

.acc-27__kw {
  color: var(--tok-kw);
  font-weight: 400;
  font-style: italic;
}

.acc-27__str {
  color: var(--tok-str);
  font-style: normal;
}

.acc-27__fn {
  color: var(--tok-fn);
  font-style: normal;
}

.acc-27__cm {
  color: var(--tok-cm);
  text-decoration: none;
}

.acc-27__fold[open] .acc-27__code {
  animation: acc-27-in .25s ease-out;
}

@keyframes acc-27-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.acc-27__sum:focus-visible {
  outline: 2px solid var(--tok-fn);
  outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
  .acc-27__fold[open] .acc-27__code {
    animation: none;
  }

  .acc-27__sum::after {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — summaries and code lines share one CSS counter, so line numbers renumber live as folds open, exactly like an editor; syntax colors are hand-spanned token classes. */
```

How this works

The frame is a dark editor window: traffic-light dots, a tab bar, and a line-number gutter rendered as a CSS counter — each summary and each code line carries counter-increment: acc-27-line, so line numbers stay sequential across opened and closed states automatically, exactly like a real editor re-numbering as folds open.

Summaries are styled as comment lines (// how do I authenticate?) with the editor's fold-triangle in the gutter. The revealed body is a <pre> block with token colors applied via spans — string/keyword/fn classes matching a Night-Owl-ish palette. Because the whole thing is semantic details/summary, you get find-in-page over the code and copy-paste that preserves indentation.

Techniques used: CSS counters for live line renumbering across folds · details-as-code-folding metaphor · token-class syntax palette on real <pre> text · editor chrome (tabs, gutter, traffic lights) in pure CSS · monospace ligature-safe stack

Make it yours

  • Token palette = four --tok-* custom properties.
  • The filename tab is markup; add more tabs for multi-file docs.
  • Line numbers restart per demo via counter-reset on the window.
  • Fold triangle = the gutter ::before; swap for VS Code's chevron style.

Gotchas — read before shipping

  • Use counter-increment on BOTH summaries and code lines or numbers skip when folds open — the counter is what sells the editor illusion.
  • Keep code in real <pre><code> with actual newlines — recreating indentation with divs breaks copy-paste, the one thing devs will definitely do.
  • Contrast in dark editors: comments at 45% grey fail WCAG against #1a1d27 — these sit at 55%+ which passes for large mono text; check yours.
  • Don't syntax-highlight with JS libraries for 4 snippets — hand-spanning tokens is smaller than highlight.js by 100×.

Browser support

ChromeSafariFirefoxEdge
allallallall

CSS counters and details — baseline everywhere.

Techniques used in this demo

Search CodeFronts

Loading…