Pure CSSMIT licensed

Neumorphic Soft-UI FAQ Accordion Cards

Soft-UI done responsibly: FAQ cards extruded from a single-material surface using the twin-shadow trick (light source top-left, shade bottom-right), pressing physically inset when opened — the material metaphor doing the state communication. The demo bakes in the two fixes that make neumorphism shippable rather than a Dribbble liability: real borders on focus, and text contrast held at WCAG AA even though the palette is all one hue.

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-09" aria-label="Neumorphic soft-UI FAQ accordion">
  <div class="cfa-09__wrap">
    <header class="cfa-09__head">
      <h1>Device support</h1>
      <p>One material, one light source. Open a card and it presses into the surface.</p>
    </header>
    <div class="cfa-09__list">
      <details class="cfa-09__item" open>
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>How do I pair a new device?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>Hold the side button for 3 seconds until the ring pulses, then open the app — pairing is automatic within arm's reach. Up to 8 devices can stay paired to one account.</p></div>
      </details>
      <details class="cfa-09__item">
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>Why won't it charge past 80%?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>That is battery care mode, on by default — it doubles cell lifespan. Toggle “Full charge” in the app for trips; it reverts automatically after 48 hours.</p></div>
      </details>
      <details class="cfa-09__item">
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>Is it waterproof?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>IP68 — pool and shower safe, and rinsing after salt water is recommended. The charging contacts are corrosion-sealed, so no port covers to lose.</p></div>
      </details>
      <details class="cfa-09__item">
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>How do I factory reset?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>Settings → Device → Reset in the app, or hold both buttons for 10 seconds until three pulses. Your data stays in the cloud and restores on next pairing.</p></div>
      </details>
    </div>
    <p class="cfa-09__foot">Twin shadows out = raised · inset = pressed. Contrast held at AA despite the single-hue palette.</p>
  </div>
</section>
.cfa-09,
.cfa-09 *,
.cfa-09 *::before,
.cfa-09 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cfa-09 {
  --surface: oklch(0.92 0.018 250);
  --hi: color-mix(in oklch,var(--surface) 35%,white);
  --lo: color-mix(in oklch,var(--surface) 72%,oklch(0.55 0.06 260));
  --ink: oklch(0.3 0.04 260);
  --mut: oklch(0.45 0.035 258);
  --acc: oklch(0.55 0.16 262);
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(20px,4vw,56px);
  background: var(--surface);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
}

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

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

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

.cfa-09__list {
  margin-top: 28px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  interpolate-size: allow-keywords;
}

.cfa-09__item {
  background: var(--surface);
  border-radius: 20px;
  box-shadow: -8px -8px 18px var(--hi),8px 8px 18px var(--lo);
  transition: box-shadow .35s cubic-bezier(.4,0,.2,1);
}

.cfa-09__item:not([open]):hover {
  box-shadow: -10px -10px 22px var(--hi),10px 10px 22px var(--lo);
}

.cfa-09__item[open] {
  box-shadow: inset -6px -6px 14px var(--hi),inset 6px 6px 14px var(--lo);
}

.cfa-09__q {
  display: flex;
  align-items: center;
  gap: 13px;
  min-height: 44px;
  padding: 18px 20px;
  cursor: pointer;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.01em;
  list-style: none;
  border-radius: 20px;
  transition: translate .25s;
}

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

.cfa-09__item[open] .cfa-09__q {
  translate: 1px 1px;
}

.cfa-09__q:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 3px;
}

.cfa-09__dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--acc);
  box-shadow: -2px -2px 4px var(--hi),2px 2px 4px var(--lo);
  transition: background .3s;
}

.cfa-09__chev {
  margin-left: auto;
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 2.5px solid var(--mut);
  border-bottom: 2.5px solid var(--mut);
  border-radius: 1px;
  rotate: 45deg;
  translate: 0 -2px;
  transition: rotate .35s,border-color .3s;
}

.cfa-09__item[open] .cfa-09__chev {
  rotate: 225deg;
  translate: 0 2px;
  border-color: var(--acc);
}

.cfa-09__a p {
  padding: 0 20px 19px 42px;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--mut);
  max-width: 56ch;
}

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

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

.cfa-09__foot {
  margin-top: 24px;
  font-size: 12px;
  color: var(--mut);
}

@media (prefers-reduced-motion: reduce) {
  .cfa-09 *,
    .cfa-09 .cfa-09__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: Neumorphic Soft-UI FAQ Accordion Cards
Source: https://codefronts.com/snippets/css-faq-accordion/neumorphic-soft-ui-faq-accordion-cards/

Soft-UI done responsibly: FAQ cards extruded from a single-material surface using the twin-shadow trick (light source top-left, shade bottom-right), pressing physically inset when opened — the material metaphor doing the state communication. The demo bakes in the two fixes that make neumorphism shippable rather than a Dribbble liability: real borders on focus, and text contrast held at WCAG AA even though the palette is all one hue.
## HTML
```html
<section class="cfa-09" aria-label="Neumorphic soft-UI FAQ accordion">
  <div class="cfa-09__wrap">
    <header class="cfa-09__head">
      <h1>Device support</h1>
      <p>One material, one light source. Open a card and it presses into the surface.</p>
    </header>
    <div class="cfa-09__list">
      <details class="cfa-09__item" open>
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>How do I pair a new device?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>Hold the side button for 3 seconds until the ring pulses, then open the app — pairing is automatic within arm's reach. Up to 8 devices can stay paired to one account.</p></div>
      </details>
      <details class="cfa-09__item">
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>Why won't it charge past 80%?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>That is battery care mode, on by default — it doubles cell lifespan. Toggle “Full charge” in the app for trips; it reverts automatically after 48 hours.</p></div>
      </details>
      <details class="cfa-09__item">
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>Is it waterproof?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>IP68 — pool and shower safe, and rinsing after salt water is recommended. The charging contacts are corrosion-sealed, so no port covers to lose.</p></div>
      </details>
      <details class="cfa-09__item">
        <summary class="cfa-09__q"><span class="cfa-09__dot" aria-hidden="true"></span>How do I factory reset?<i class="cfa-09__chev" aria-hidden="true"></i></summary>
        <div class="cfa-09__a"><p>Settings → Device → Reset in the app, or hold both buttons for 10 seconds until three pulses. Your data stays in the cloud and restores on next pairing.</p></div>
      </details>
    </div>
    <p class="cfa-09__foot">Twin shadows out = raised · inset = pressed. Contrast held at AA despite the single-hue palette.</p>
  </div>
</section>
```
## CSS
```css
.cfa-09,
.cfa-09 *,
.cfa-09 *::before,
.cfa-09 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cfa-09 {
  --surface: oklch(0.92 0.018 250);
  --hi: color-mix(in oklch,var(--surface) 35%,white);
  --lo: color-mix(in oklch,var(--surface) 72%,oklch(0.55 0.06 260));
  --ink: oklch(0.3 0.04 260);
  --mut: oklch(0.45 0.035 258);
  --acc: oklch(0.55 0.16 262);
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(20px,4vw,56px);
  background: var(--surface);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--ink);
}

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

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

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

.cfa-09__list {
  margin-top: 28px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  interpolate-size: allow-keywords;
}

.cfa-09__item {
  background: var(--surface);
  border-radius: 20px;
  box-shadow: -8px -8px 18px var(--hi),8px 8px 18px var(--lo);
  transition: box-shadow .35s cubic-bezier(.4,0,.2,1);
}

.cfa-09__item:not([open]):hover {
  box-shadow: -10px -10px 22px var(--hi),10px 10px 22px var(--lo);
}

.cfa-09__item[open] {
  box-shadow: inset -6px -6px 14px var(--hi),inset 6px 6px 14px var(--lo);
}

.cfa-09__q {
  display: flex;
  align-items: center;
  gap: 13px;
  min-height: 44px;
  padding: 18px 20px;
  cursor: pointer;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.01em;
  list-style: none;
  border-radius: 20px;
  transition: translate .25s;
}

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

.cfa-09__item[open] .cfa-09__q {
  translate: 1px 1px;
}

.cfa-09__q:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 3px;
}

.cfa-09__dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--acc);
  box-shadow: -2px -2px 4px var(--hi),2px 2px 4px var(--lo);
  transition: background .3s;
}

.cfa-09__chev {
  margin-left: auto;
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 2.5px solid var(--mut);
  border-bottom: 2.5px solid var(--mut);
  border-radius: 1px;
  rotate: 45deg;
  translate: 0 -2px;
  transition: rotate .35s,border-color .3s;
}

.cfa-09__item[open] .cfa-09__chev {
  rotate: 225deg;
  translate: 0 2px;
  border-color: var(--acc);
}

.cfa-09__a p {
  padding: 0 20px 19px 42px;
  font-size: 13.5px;
  line-height: 1.75;
  color: var(--mut);
  max-width: 56ch;
}

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

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

.cfa-09__foot {
  margin-top: 24px;
  font-size: 12px;
  color: var(--mut);
}

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

How this works

Neumorphism is one lighting model: everything is the same material as the background, shaped only by light from a consistent corner. Two shadows fake the extrusion —

.card{
  background:var(--surface);
  border-radius:20px;
  box-shadow:-8px -8px 18px var(--hi),   /* light, top-left  */
              8px  8px 18px var(--lo);   /* shade, bottom-right */
}
.card[open]{
  box-shadow:inset -6px -6px 14px var(--hi),
             inset  6px  6px 14px var(--lo);  /* pressed in */
}

The open state inverts to inset shadows — the card is pressed into the surface, a physical metaphor users decode instantly with no icon required (the chevron here is a garnish, not the signal). Both shadow colors derive from the surface with color-mix, so re-theming stays a one-token edit:

--surface:oklch(0.92 0.02 250);
--hi:color-mix(in oklch, var(--surface) 40%, white);
--lo:color-mix(in oklch, var(--surface) 75%, oklch(0.55 0.05 260));

And the honesty section, because your users aren't Dribbble judges: pure neumorphism has famously weak affordances (everything is the same color) and fails focus-visibility. The fixes here — ink at oklch 0.3 for 7:1 contrast, a real 2px outline on :focus-visible, and hover raising the extrusion by 2px so pointers get feedback — cost nothing visually and make the style production-legal.

Make it yours

  • Material: change --surface once; both shadows recompute via color-mix. Cool gray, warm sand and sage all read well — avoid saturated surfaces.
  • Depth: the 8px/18px offset/blur pair is the extrusion height; halve both for subtle, double for chunky toy-UI.
  • Light direction: to move the light source top-right, swap the signs on the x offsets in both shadow pairs — consistency across ALL elements is the one hard rule of the style.
  • Pressed content: the inner padding shifts 1px down-right when open, selling the press — remove the two translate lines if too cute.

Gotchas — read before shipping

  • Neumorphism needs a mid-lightness surface (oklch 0.88–0.94): on white the highlight vanishes, on dark the shade does — the style physically cannot work at the extremes.
  • Never communicate state by shadow alone for critical UI — this demo keeps the chevron and the aria semantics of details; the press is reinforcement, not the only signal.
  • Soft shadows stack blur fast: 6 cards × 4 shadows is fine, but avoid animating box-shadow on scroll-linked effects — transition only on state change, as here.
  • Skip inset shadows on the text itself (embossed text) — it fails contrast the moment it looks good.

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 111+.

Box-shadow twin-lighting is universal; the floor is oklch/color-mix token math. Height animation via ::details-content enhances progressively; panels snap open elsewhere.

Techniques used in this demo

Search CodeFronts

Loading…