09 / 25
All 25 in 25 Tailwind CSS FAQ Accordion
- 01Tailwind Peer-Checked FAQ Accordion with No JavaScript
- 02Tailwind Native Details/Summary FAQ with Custom Styled Icons
- 03Tailwind Exclusive Accordion — Only One FAQ Open at a Time
- 04Tailwind Multi-Open FAQ Accordion with a Live CSS Counter
- 05Tailwind CSS FAQ Accordion Template (Free, Copy-Paste)
- 06Tailwind Dark Mode FAQ Accordion with light-dark() and No .dark Class
- 07Tailwind Glassmorphism FAQ Accordion with Frosted Backdrop Blur
- 08Tailwind Minimalist E-Commerce FAQ Accordion for Product Pages
- 09Tailwind Neumorphic Soft-UI FAQ Accordion Cards
- 10Tailwind Animate Height Auto FAQ with grid-rows 0fr to 1fr
- 11Tailwind FAQ Chevron Rotate 180 Degrees on Toggle
- 12Tailwind Animated Plus to Minus Icon Morph FAQ Accordion
- 13Tailwind Spring Bounce FAQ Accordion with linear() Easing
- 14Tailwind Two-Column FAQ Accordion Grid with Independent Columns
- 15Tailwind Horizontal FAQ Accordion with Sideways Expanding Panels
- 16Tailwind Categorised FAQ Help Centre with a Sticky Sidebar Menu
- 17Tailwind Card-Style FAQ Panels with a Layered Shadow Elevation System
- 18Tailwind FAQ Accordion with a Live Search Bar Filter
- 19Tailwind Expand All and Collapse All FAQ Accordion Buttons
- 20Tailwind FAQ with a Still Have Questions Contact CTA Card
- 21Tailwind Accessible ARIA FAQ Accordion with Keyboard Navigation
- 22Tailwind FAQ with FAQPage JSON-LD Schema for Rich Snippets and AI Search
- 23Bootstrap 5 Accordion Rebuilt in Tailwind (and the JavaScript You Delete)
- 24Tailwind FAQ Component in React, Vue, Svelte and Astro (One Port Recipe)
- 25Tailwind FAQ with Voice Search Using the Speech Recognition API
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.
Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49
Cruip · Affiliate link
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><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;
}
}@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.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@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;
}
}@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;
}
}<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">
<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@themeworks 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]):hoverbecomes[&: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.42and the accent0.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.
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;
}
}
```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-pxsells 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-shadowonly 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
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 129+ | not yet | not yet | 129+ |
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.