25 Tailwind CSS FAQ Accordion
09 / 25

All 25 in 25 Tailwind CSS FAQ Accordion

  1. 01Tailwind Peer-Checked FAQ Accordion with No JavaScript
  2. 02Tailwind Native Details/Summary FAQ with Custom Styled Icons
  3. 03Tailwind Exclusive Accordion — Only One FAQ Open at a Time
  4. 04Tailwind Multi-Open FAQ Accordion with a Live CSS Counter
  5. 05Tailwind CSS FAQ Accordion Template (Free, Copy-Paste)
  6. 06Tailwind Dark Mode FAQ Accordion with light-dark() and No .dark Class
  7. 07Tailwind Glassmorphism FAQ Accordion with Frosted Backdrop Blur
  8. 08Tailwind Minimalist E-Commerce FAQ Accordion for Product Pages
  9. 09Tailwind Neumorphic Soft-UI FAQ Accordion Cards
  10. 10Tailwind Animate Height Auto FAQ with grid-rows 0fr to 1fr
  11. 11Tailwind FAQ Chevron Rotate 180 Degrees on Toggle
  12. 12Tailwind Animated Plus to Minus Icon Morph FAQ Accordion
  13. 13Tailwind Spring Bounce FAQ Accordion with linear() Easing
  14. 14Tailwind Two-Column FAQ Accordion Grid with Independent Columns
  15. 15Tailwind Horizontal FAQ Accordion with Sideways Expanding Panels
  16. 16Tailwind Categorised FAQ Help Centre with a Sticky Sidebar Menu
  17. 17Tailwind Card-Style FAQ Panels with a Layered Shadow Elevation System
  18. 18Tailwind FAQ Accordion with a Live Search Bar Filter
  19. 19Tailwind Expand All and Collapse All FAQ Accordion Buttons
  20. 20Tailwind FAQ with a Still Have Questions Contact CTA Card
  21. 21Tailwind Accessible ARIA FAQ Accordion with Keyboard Navigation
  22. 22Tailwind FAQ with FAQPage JSON-LD Schema for Rich Snippets and AI Search
  23. 23Bootstrap 5 Accordion Rebuilt in Tailwind (and the JavaScript You Delete)
  24. 24Tailwind FAQ Component in React, Vue, Svelte and Astro (One Port Recipe)
  25. 25Tailwind FAQ with Voice Search Using the Speech Recognition API
Pure TailwindMIT licensed

Tailwind Neumorphic Soft-UI FAQ Accordion Cards

Soft-UI done responsibly: cards extruded from a single-material surface with the twin-shadow trick (light top-left, shade bottom-right), pressing physically inset when opened — the material metaphor doing the state communication. Both shadow colours are color-mix() derivations of one surface token, so retheming is a one-line edit, and the two fixes that make neumorphism shippable are baked in.

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="tfa-09 w-full min-h-svh grid place-items-center bg-tfa09-surface font-tfa09-sans text-tfa09-ink px-5 py-12">
  <div class="w-[min(600px,100%)]">
    <header>
      <h1 class="text-[clamp(26px,4.5vw,36px)] font-extrabold tracking-[-0.02em]">Device support</h1>
      <p class="mt-3 max-w-[50ch] text-[14.5px] leading-relaxed text-tfa09-mut">One material, one light source. Open a card and it presses into the surface.</p>
    </header>

    <div data-panel class="mt-8 flex flex-col gap-[18px]">
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none" open>
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>How do I pair a new device?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">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>
      </details>
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none">
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>Why won't it charge past 80%?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">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>
      </details>
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none">
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>Is it waterproof?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">IP68 — pool and shower safe, and rinsing after salt water is recommended. The charging contacts are corrosion-sealed, so there are no port covers to lose.</p>
      </details>
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none">
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>How do I factory reset?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">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>
      </details>
    </div>

    <p class="mt-6 text-[12px] text-tfa09-mut">Twin shadows out = raised · inset = pressed. Contrast held at AA despite the single-hue palette.</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tfa09-surface: #e6e9f0;
  --color-tfa09-surface: oklch(0.92 0.018 250);
  --color-tfa09-hi: #f7f9fc;
  --color-tfa09-hi: color-mix(in oklch, var(--color-tfa09-surface) 35%, white);
  --color-tfa09-lo: #c3c8d6;
  --color-tfa09-lo: color-mix(in oklch, var(--color-tfa09-surface) 72%, oklch(0.55 0.06 260));
  --color-tfa09-ink: #333a4d;
  --color-tfa09-ink: oklch(0.3 0.04 260);
  --color-tfa09-mut: #565e73;
  --color-tfa09-mut: oklch(0.42 0.035 258);
  --color-tfa09-acc: #4a5fd0;
  --color-tfa09-acc: oklch(0.5 0.17 262);
  --font-tfa09-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tfa-09 {
  width: 100%;
  min-height: 100svh;
}

.tfa-09 [data-panel] {
  interpolate-size: allow-keywords;
}

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

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

@media (prefers-reduced-motion: reduce) {
  .tfa-09 details::details-content {
    transition-duration: 1ms;
  }
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tfa09-surface: #e6e9f0;
  --color-tfa09-surface: oklch(0.92 0.018 250);
  --color-tfa09-hi: #f7f9fc;
  --color-tfa09-hi: color-mix(in oklch, var(--color-tfa09-surface) 35%, white);
  --color-tfa09-lo: #c3c8d6;
  --color-tfa09-lo: color-mix(in oklch, var(--color-tfa09-surface) 72%, oklch(0.55 0.06 260));
  --color-tfa09-ink: #333a4d;
  --color-tfa09-ink: oklch(0.3 0.04 260);
  --color-tfa09-mut: #565e73;
  --color-tfa09-mut: oklch(0.42 0.035 258);
  --color-tfa09-acc: #4a5fd0;
  --color-tfa09-acc: oklch(0.5 0.17 262);
  --font-tfa09-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tfa-09 {
  width: 100%;
  min-height: 100svh;
}

.tfa-09 [data-panel] {
  interpolate-size: allow-keywords;
}

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

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

@media (prefers-reduced-motion: reduce) {
  .tfa-09 details::details-content {
    transition-duration: 1ms;
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • color-mix() inside @theme works and is the right call here: the highlight and shade are derived tokens, so changing the surface retints both shadows and still mints utilities for all three.
  • .item:not([open]):hover becomes [&:not([open]):hover]:shadow-[...] — two composed variants instead of a selector you read twice, and the intent (only closed cards rise) is legible in the class list.
  • Arbitrary shadow values must reference tokens as var(--color-tfa09-hi): inside brackets Tailwind does no token resolution, and in v4 the custom property IS the public API.
  • UI upgrade from the CSS original: muted ink moved oklch(0.45) → 0.42 and the accent 0.55 → 0.5, which matters more here than anywhere else in the collection — a single-hue palette has no other contrast to lean on.
  • UI upgrade: the summary now changes ink colour on hover in addition to the extrusion lift. The original communicated pointer state purely through a 2px shadow change, which is invisible to anyone with reduced contrast sensitivity.
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 demo 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: Tailwind Neumorphic Soft-UI FAQ Accordion Cards
Source: https://codefronts.com/snippets/tailwind-css-faq-accordion/tailwind-neumorphic-soft-ui-faq-accordion-cards/

Soft-UI done responsibly: cards extruded from a single-material surface with the twin-shadow trick (light top-left, shade bottom-right), pressing physically inset when opened — the material metaphor doing the state communication. Both shadow colours are color-mix() derivations of one surface token, so retheming is a one-line edit, and the two fixes that make neumorphism shippable are baked in.
## HTML
```html
<section class="tfa-09 w-full min-h-svh grid place-items-center bg-tfa09-surface font-tfa09-sans text-tfa09-ink px-5 py-12">
  <div class="w-[min(600px,100%)]">
    <header>
      <h1 class="text-[clamp(26px,4.5vw,36px)] font-extrabold tracking-[-0.02em]">Device support</h1>
      <p class="mt-3 max-w-[50ch] text-[14.5px] leading-relaxed text-tfa09-mut">One material, one light source. Open a card and it presses into the surface.</p>
    </header>

    <div data-panel class="mt-8 flex flex-col gap-[18px]">
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none" open>
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>How do I pair a new device?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">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>
      </details>
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none">
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>Why won't it charge past 80%?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">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>
      </details>
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none">
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>Is it waterproof?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">IP68 — pool and shower safe, and rinsing after salt water is recommended. The charging contacts are corrosion-sealed, so there are no port covers to lose.</p>
      </details>
      <details class="group rounded-[20px] bg-tfa09-surface shadow-[-8px_-8px_18px_var(--color-tfa09-hi),8px_8px_18px_var(--color-tfa09-lo)] [&:not([open]):hover]:shadow-[-10px_-10px_22px_var(--color-tfa09-hi),10px_10px_22px_var(--color-tfa09-lo)] open:shadow-[inset_-6px_-6px_14px_var(--color-tfa09-hi),inset_6px_6px_14px_var(--color-tfa09-lo)] transition-[box-shadow] duration-350 ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none">
        <summary class="flex min-h-11 cursor-pointer list-none items-center gap-3.5 rounded-[20px] px-5 py-[18px] text-[15px] font-bold tracking-[-0.01em] transition-[translate,color] duration-250 hover:text-tfa09-acc focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-tfa09-acc group-open:translate-x-px group-open:translate-y-px [&::-webkit-details-marker]:hidden">
          <span aria-hidden="true" class="size-2.5 shrink-0 rounded-full bg-tfa09-acc shadow-[-2px_-2px_4px_var(--color-tfa09-hi),2px_2px_4px_var(--color-tfa09-lo)]"></span>How do I factory reset?
          <i aria-hidden="true" class="ml-auto size-2.5 shrink-0 -translate-y-0.5 rotate-45 rounded-[1px] border-b-[2.5px] border-r-[2.5px] border-tfa09-mut transition-[rotate,translate,border-color] duration-350 group-hover:border-tfa09-acc group-open:translate-y-0.5 group-open:rotate-[225deg] group-open:border-tfa09-acc motion-reduce:transition-none"></i>
        </summary>
        <p class="max-w-[56ch] pb-[19px] pl-[42px] pr-5 text-[13.5px] leading-[1.75] text-tfa09-mut">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>
      </details>
    </div>

    <p class="mt-6 text-[12px] text-tfa09-mut">Twin shadows out = raised · inset = pressed. Contrast held at AA despite the single-hue palette.</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tfa09-surface: #e6e9f0;
  --color-tfa09-surface: oklch(0.92 0.018 250);
  --color-tfa09-hi: #f7f9fc;
  --color-tfa09-hi: color-mix(in oklch, var(--color-tfa09-surface) 35%, white);
  --color-tfa09-lo: #c3c8d6;
  --color-tfa09-lo: color-mix(in oklch, var(--color-tfa09-surface) 72%, oklch(0.55 0.06 260));
  --color-tfa09-ink: #333a4d;
  --color-tfa09-ink: oklch(0.3 0.04 260);
  --color-tfa09-mut: #565e73;
  --color-tfa09-mut: oklch(0.42 0.035 258);
  --color-tfa09-acc: #4a5fd0;
  --color-tfa09-acc: oklch(0.5 0.17 262);
  --font-tfa09-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}

.tfa-09 {
  width: 100%;
  min-height: 100svh;
}

.tfa-09 [data-panel] {
  interpolate-size: allow-keywords;
}

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

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

@media (prefers-reduced-motion: reduce) {
  .tfa-09 details::details-content {
    transition-duration: 1ms;
  }
}
```

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 — a light one offset up-left, a shade offset down-right — and the open state inverts both to inset.

Users decode a pressed surface instantly with no icon required — the chevron here is garnish, not signal. Both shadow colours derive from the surface inside @theme:

--color-tfa09-surface: oklch(0.92 0.018 250);
--color-tfa09-hi: color-mix(in oklch, var(--color-tfa09-surface) 35%, white);
--color-tfa09-lo: color-mix(in oklch, var(--color-tfa09-surface) 72%, oklch(0.55 0.06 260));

— so changing one token retints every shadow in the demo.

The honesty section, because your users are not Dribbble judges: pure neumorphism has famously weak affordances (everything is one colour) and fails focus visibility. The fixes cost nothing visually — ink at oklch 0.3 for roughly 7:1, a real 2px focus-visible:outline, and [&:not([open]):hover]: raising the extrusion 2px so pointers get feedback. That last one is the port's small win: in CSS it was .item:not([open]):hover, a selector you read twice.

Make it yours

  • Material: change the surface token once; both shadows recompute through color-mix(). Cool grey, warm sand and sage all read well; avoid saturated surfaces.
  • Depth: the 8px offset and 18px blur are the extrusion height. Halve for subtle, double for chunky toy-UI.
  • Light direction: flip the sign on both x offsets to move the source top-right — consistency across every element is the style's one hard rule.
  • Pressed content: group-open:translate-x-px group-open:translate-y-px sells the press; delete both if it reads as 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 native <details> semantics; the press is reinforcement.
  • Soft shadows stack cost fast. Transition box-shadow only on state change, as here — never on scroll.
  • Skip embossed text (an inset shadow on the type itself): 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+.

Twin-lighting box-shadows are universal; the floor is the oklch()/color-mix() token math and Tailwind v4's own sheet. The animated panel enhances progressively.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…