25 Tailwind CSS FAQ Accordion
16 / 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 Categorised FAQ Help Centre with a Sticky Sidebar Menu

The help-centre layout: questions grouped under category headings with a sticky sidebar that jumps between sections. Every piece of navigation is native — sticky top-6 self-start pins the menu, scroll-mt-6 stops headings hiding under the top edge, scroll-smooth makes jumps glide — and the two rules people always miss are now visible in the class list rather than buried in a stylesheet.

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-16 w-full min-h-svh font-tfa16-sans text-tfa16-ink">
  <div class="@container h-svh w-full overflow-y-auto scroll-smooth bg-tfa16-bg">
    <div class="mx-auto grid w-[min(900px,100%)] gap-6 px-5 py-8 @3xl:grid-cols-[220px_minmax(0,1fr)] @3xl:gap-12 @3xl:px-10 @3xl:py-12">

      <nav aria-label="FAQ categories" class="sticky top-0 z-10 flex flex-row flex-wrap items-center gap-1.5 bg-tfa16-bg/90 py-2.5 backdrop-blur-sm @3xl:top-6 @3xl:z-auto @3xl:flex-col @3xl:items-stretch @3xl:self-start @3xl:bg-transparent @3xl:py-0 @3xl:backdrop-blur-none">
        <span class="w-full font-tfa16-mono text-[11px] font-extrabold uppercase tracking-[0.14em] text-tfa16-mut @3xl:mb-1.5">Help centre</span>
          <a href="#tfa16-account" class="flex min-h-11 items-center justify-between gap-2.5 rounded-[11px] px-3.5 text-[14px] font-semibold text-tfa16-ink no-underline transition-colors duration-200 hover:bg-tfa16-tint hover:text-tfa16-acc focus-visible:bg-tfa16-tint focus-visible:text-tfa16-acc focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc aria-[current=true]:bg-tfa16-tint aria-[current=true]:text-tfa16-acc">Account<span class="rounded-full border border-tfa16-edge bg-white px-2 py-0.5 font-tfa16-mono text-[11px] font-bold text-tfa16-mut">3</span></a>
          <a href="#tfa16-billing" class="flex min-h-11 items-center justify-between gap-2.5 rounded-[11px] px-3.5 text-[14px] font-semibold text-tfa16-ink no-underline transition-colors duration-200 hover:bg-tfa16-tint hover:text-tfa16-acc focus-visible:bg-tfa16-tint focus-visible:text-tfa16-acc focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc aria-[current=true]:bg-tfa16-tint aria-[current=true]:text-tfa16-acc">Billing<span class="rounded-full border border-tfa16-edge bg-white px-2 py-0.5 font-tfa16-mono text-[11px] font-bold text-tfa16-mut">2</span></a>
          <a href="#tfa16-privacy" class="flex min-h-11 items-center justify-between gap-2.5 rounded-[11px] px-3.5 text-[14px] font-semibold text-tfa16-ink no-underline transition-colors duration-200 hover:bg-tfa16-tint hover:text-tfa16-acc focus-visible:bg-tfa16-tint focus-visible:text-tfa16-acc focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc aria-[current=true]:bg-tfa16-tint aria-[current=true]:text-tfa16-acc">Privacy<span class="rounded-full border border-tfa16-edge bg-white px-2 py-0.5 font-tfa16-mono text-[11px] font-bold text-tfa16-mut">2</span></a>
        <p class="mt-3 hidden rounded-[10px] border border-dashed border-tfa16-edge px-3 py-2.5 text-[11.5px] leading-[1.55] text-tfa16-mut @3xl:block">Sticky via <code class="font-tfa16-mono text-tfa16-acc">sticky top-6 self-start</code> — no JS scroll-spy for the jump pattern.</p>
      </nav>

      <div data-panel class="flex flex-col gap-9">
        <section id="tfa16-account" class="scroll-mt-6 target:[&>h2]:border-tfa16-acc target:[&>h2]:text-tfa16-acc">
          <h2 class="border-b-2 border-tfa16-edge pb-2.5 text-[19px] font-extrabold tracking-[-0.02em] transition-colors duration-300">Account</h2>
          <div class="mt-2.5 flex flex-col gap-2.5">
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]" open>
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">How do I change my email address?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Account → Email. We send a confirmation link to both the old and new address; the change applies when the new one is verified. Login sessions stay active.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Can I merge two accounts?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Yes — from the older account, Settings → Advanced → Merge. Projects, history and integrations move over; the source account becomes an alias that still accepts logins for 90 days.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">How do I delete my account?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Advanced → Delete. There is a 14-day grace period during which logging back in cancels the deletion; after that everything is purged, including backups, within 30 days.</p>
          </details>
          </div>
        </section>
        <section id="tfa16-billing" class="scroll-mt-6 target:[&>h2]:border-tfa16-acc target:[&>h2]:text-tfa16-acc">
          <h2 class="border-b-2 border-tfa16-edge pb-2.5 text-[19px] font-extrabold tracking-[-0.02em] transition-colors duration-300">Billing</h2>
          <div class="mt-2.5 flex flex-col gap-2.5">
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Where do I find my invoices?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Billing → History lists every invoice as a PDF with your tax details. Accountants can be added as billing-only members who see nothing else.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Why was my card declined?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Nine times out of ten it is a 3-D Secure prompt your bank never delivered. Retry from Billing → Payment method and we trigger a fresh authentication. Failing that, the decline code from your bank tells the real story.</p>
          </details>
          </div>
        </section>
        <section id="tfa16-privacy" class="scroll-mt-6 target:[&>h2]:border-tfa16-acc target:[&>h2]:text-tfa16-acc">
          <h2 class="border-b-2 border-tfa16-edge pb-2.5 text-[19px] font-extrabold tracking-[-0.02em] transition-colors duration-300">Privacy</h2>
          <div class="mt-2.5 flex flex-col gap-2.5">
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Can I export everything you hold on me?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Privacy → Export builds a machine-readable archive (JSON plus attachments) within 24 hours — the full GDPR/CCPA portability package, self-serve.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Do you sell or share my data?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">No, and the DPA says so contractually. Subprocessors are listed publicly with 30-day change notices; analytics run first-party and cookieless.</p>
          </details>
          </div>
        </section>
        <p class="text-[12.5px] text-tfa16-mut">Delete the stage's <code class="font-tfa16-mono text-tfa16-acc">overflow-y-auto</code> and this layout runs on document scroll.</p>
      </div>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tfa16-bg: #f9f8fd;
  --color-tfa16-bg: oklch(0.975 0.006 285);
  --color-tfa16-ink: #23202e;
  --color-tfa16-ink: oklch(0.23 0.03 285);
  --color-tfa16-mut: #5b566b;
  --color-tfa16-mut: oklch(0.47 0.022 285);
  --color-tfa16-edge: #e4e1ee;
  --color-tfa16-edge: oklch(0.91 0.01 285);
  --color-tfa16-accedge: #a793e3;
  --color-tfa16-accedge: oklch(0.73 0.1 295);
  --color-tfa16-tint: #efeafd;
  --color-tfa16-tint: oklch(0.94 0.03 295);
  --color-tfa16-acc: #6b3fd4;
  --color-tfa16-acc: oklch(0.48 0.2 295);
  --font-tfa16-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-tfa16-mono: 'JetBrains Mono', ui-monospace, monospace;
}

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

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

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

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

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

  .tfa-16 .overflow-y-auto {
    scroll-behavior: auto;
  }
}

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-tfa16-bg: #f9f8fd;
  --color-tfa16-bg: oklch(0.975 0.006 285);
  --color-tfa16-ink: #23202e;
  --color-tfa16-ink: oklch(0.23 0.03 285);
  --color-tfa16-mut: #5b566b;
  --color-tfa16-mut: oklch(0.47 0.022 285);
  --color-tfa16-edge: #e4e1ee;
  --color-tfa16-edge: oklch(0.91 0.01 285);
  --color-tfa16-accedge: #a793e3;
  --color-tfa16-accedge: oklch(0.73 0.1 295);
  --color-tfa16-tint: #efeafd;
  --color-tfa16-tint: oklch(0.94 0.03 295);
  --color-tfa16-acc: #6b3fd4;
  --color-tfa16-acc: oklch(0.48 0.2 295);
  --font-tfa16-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-tfa16-mono: 'JetBrains Mono', ui-monospace, monospace;
}

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

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

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

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

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

  .tfa-16 .overflow-y-auto {
    scroll-behavior: auto;
  }
}
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&family=JetBrains+Mono:wght@400;500;600&display=swap">

What changed in the port

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

  • self-start as a utility is the best documentation this pattern ever had: the CSS version's align-self:start sat three rules away from the position:sticky it rescued, and readers deleted it constantly. Side by side in sticky top-6 self-start, the dependency is obvious.
  • --stick: 24px driving both the pin and every scroll-margin-top is the one thing the CSS version did better — utilities force you to keep top-6 and scroll-mt-6 in sync by hand. If a project has several such layouts, a @theme spacing token is worth minting.
  • target:[&>h2]: is a variant plus a child selector, which is as far as this should be pushed — anything deeper belongs in CSS.
  • UI upgrade from the CSS original: muted copy oklch(0.5) → 0.47, accent 0.55 → 0.48; the sidebar links now hit 7:1 and the count chips 4.6:1, where the original's chips were borderline at 11px.
  • UI upgrade: the narrow-container chip row keeps its blurred translucent backing and a proper z-10, so category links stay readable over scrolling content — the original's mobile bar had a color-mix background that went transparent over white cards.
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 Categorised FAQ Help Centre with a Sticky Sidebar Menu
Source: https://codefronts.com/snippets/tailwind-css-faq-accordion/tailwind-categorised-faq-help-centre-sticky-sidebar/

The help-centre layout: questions grouped under category headings with a sticky sidebar that jumps between sections. Every piece of navigation is native — sticky top-6 self-start pins the menu, scroll-mt-6 stops headings hiding under the top edge, scroll-smooth makes jumps glide — and the two rules people always miss are now visible in the class list rather than buried in a stylesheet.
## HTML
```html
<section class="tfa-16 w-full min-h-svh font-tfa16-sans text-tfa16-ink">
  <div class="@container h-svh w-full overflow-y-auto scroll-smooth bg-tfa16-bg">
    <div class="mx-auto grid w-[min(900px,100%)] gap-6 px-5 py-8 @3xl:grid-cols-[220px_minmax(0,1fr)] @3xl:gap-12 @3xl:px-10 @3xl:py-12">

      <nav aria-label="FAQ categories" class="sticky top-0 z-10 flex flex-row flex-wrap items-center gap-1.5 bg-tfa16-bg/90 py-2.5 backdrop-blur-sm @3xl:top-6 @3xl:z-auto @3xl:flex-col @3xl:items-stretch @3xl:self-start @3xl:bg-transparent @3xl:py-0 @3xl:backdrop-blur-none">
        <span class="w-full font-tfa16-mono text-[11px] font-extrabold uppercase tracking-[0.14em] text-tfa16-mut @3xl:mb-1.5">Help centre</span>
          <a href="#tfa16-account" class="flex min-h-11 items-center justify-between gap-2.5 rounded-[11px] px-3.5 text-[14px] font-semibold text-tfa16-ink no-underline transition-colors duration-200 hover:bg-tfa16-tint hover:text-tfa16-acc focus-visible:bg-tfa16-tint focus-visible:text-tfa16-acc focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc aria-[current=true]:bg-tfa16-tint aria-[current=true]:text-tfa16-acc">Account<span class="rounded-full border border-tfa16-edge bg-white px-2 py-0.5 font-tfa16-mono text-[11px] font-bold text-tfa16-mut">3</span></a>
          <a href="#tfa16-billing" class="flex min-h-11 items-center justify-between gap-2.5 rounded-[11px] px-3.5 text-[14px] font-semibold text-tfa16-ink no-underline transition-colors duration-200 hover:bg-tfa16-tint hover:text-tfa16-acc focus-visible:bg-tfa16-tint focus-visible:text-tfa16-acc focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc aria-[current=true]:bg-tfa16-tint aria-[current=true]:text-tfa16-acc">Billing<span class="rounded-full border border-tfa16-edge bg-white px-2 py-0.5 font-tfa16-mono text-[11px] font-bold text-tfa16-mut">2</span></a>
          <a href="#tfa16-privacy" class="flex min-h-11 items-center justify-between gap-2.5 rounded-[11px] px-3.5 text-[14px] font-semibold text-tfa16-ink no-underline transition-colors duration-200 hover:bg-tfa16-tint hover:text-tfa16-acc focus-visible:bg-tfa16-tint focus-visible:text-tfa16-acc focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc aria-[current=true]:bg-tfa16-tint aria-[current=true]:text-tfa16-acc">Privacy<span class="rounded-full border border-tfa16-edge bg-white px-2 py-0.5 font-tfa16-mono text-[11px] font-bold text-tfa16-mut">2</span></a>
        <p class="mt-3 hidden rounded-[10px] border border-dashed border-tfa16-edge px-3 py-2.5 text-[11.5px] leading-[1.55] text-tfa16-mut @3xl:block">Sticky via <code class="font-tfa16-mono text-tfa16-acc">sticky top-6 self-start</code> — no JS scroll-spy for the jump pattern.</p>
      </nav>

      <div data-panel class="flex flex-col gap-9">
        <section id="tfa16-account" class="scroll-mt-6 target:[&>h2]:border-tfa16-acc target:[&>h2]:text-tfa16-acc">
          <h2 class="border-b-2 border-tfa16-edge pb-2.5 text-[19px] font-extrabold tracking-[-0.02em] transition-colors duration-300">Account</h2>
          <div class="mt-2.5 flex flex-col gap-2.5">
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]" open>
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">How do I change my email address?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Account → Email. We send a confirmation link to both the old and new address; the change applies when the new one is verified. Login sessions stay active.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Can I merge two accounts?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Yes — from the older account, Settings → Advanced → Merge. Projects, history and integrations move over; the source account becomes an alias that still accepts logins for 90 days.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">How do I delete my account?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Advanced → Delete. There is a 14-day grace period during which logging back in cancels the deletion; after that everything is purged, including backups, within 30 days.</p>
          </details>
          </div>
        </section>
        <section id="tfa16-billing" class="scroll-mt-6 target:[&>h2]:border-tfa16-acc target:[&>h2]:text-tfa16-acc">
          <h2 class="border-b-2 border-tfa16-edge pb-2.5 text-[19px] font-extrabold tracking-[-0.02em] transition-colors duration-300">Billing</h2>
          <div class="mt-2.5 flex flex-col gap-2.5">
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Where do I find my invoices?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Billing → History lists every invoice as a PDF with your tax details. Accountants can be added as billing-only members who see nothing else.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Why was my card declined?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Nine times out of ten it is a 3-D Secure prompt your bank never delivered. Retry from Billing → Payment method and we trigger a fresh authentication. Failing that, the decline code from your bank tells the real story.</p>
          </details>
          </div>
        </section>
        <section id="tfa16-privacy" class="scroll-mt-6 target:[&>h2]:border-tfa16-acc target:[&>h2]:text-tfa16-acc">
          <h2 class="border-b-2 border-tfa16-edge pb-2.5 text-[19px] font-extrabold tracking-[-0.02em] transition-colors duration-300">Privacy</h2>
          <div class="mt-2.5 flex flex-col gap-2.5">
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Can I export everything you hold on me?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">Settings → Privacy → Export builds a machine-readable archive (JSON plus attachments) within 24 hours — the full GDPR/CCPA portability package, self-serve.</p>
          </details>
          <details class="group rounded-[13px] border border-tfa16-edge bg-white transition-[border-color,box-shadow] duration-300 open:border-tfa16-accedge open:shadow-[0_10px_26px_-18px_oklch(0.45_0.15_295/0.4)]">
            <summary class="flex min-h-11 cursor-pointer list-none items-center justify-between gap-3.5 px-4 py-3.5 text-[14.5px] font-semibold transition-colors duration-200 hover:text-tfa16-acc focus-visible:rounded-[13px] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-tfa16-acc [&::-webkit-details-marker]:hidden">Do you sell or share my data?
              <i aria-hidden="true" class="size-2 shrink-0 -translate-y-0.5 rotate-45 border-b-2 border-r-2 border-tfa16-acc transition-[rotate,translate] duration-350 group-open:translate-y-0.5 group-open:rotate-[225deg] motion-reduce:transition-none"></i>
            </summary>
            <p class="max-w-[60ch] px-4 pb-3.5 text-[13.5px] leading-[1.7] text-tfa16-mut">No, and the DPA says so contractually. Subprocessors are listed publicly with 30-day change notices; analytics run first-party and cookieless.</p>
          </details>
          </div>
        </section>
        <p class="text-[12.5px] text-tfa16-mut">Delete the stage's <code class="font-tfa16-mono text-tfa16-acc">overflow-y-auto</code> and this layout runs on document scroll.</p>
      </div>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tfa16-bg: #f9f8fd;
  --color-tfa16-bg: oklch(0.975 0.006 285);
  --color-tfa16-ink: #23202e;
  --color-tfa16-ink: oklch(0.23 0.03 285);
  --color-tfa16-mut: #5b566b;
  --color-tfa16-mut: oklch(0.47 0.022 285);
  --color-tfa16-edge: #e4e1ee;
  --color-tfa16-edge: oklch(0.91 0.01 285);
  --color-tfa16-accedge: #a793e3;
  --color-tfa16-accedge: oklch(0.73 0.1 295);
  --color-tfa16-tint: #efeafd;
  --color-tfa16-tint: oklch(0.94 0.03 295);
  --color-tfa16-acc: #6b3fd4;
  --color-tfa16-acc: oklch(0.48 0.2 295);
  --font-tfa16-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-tfa16-mono: 'JetBrains Mono', ui-monospace, monospace;
}

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

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

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

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

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

  .tfa-16 .overflow-y-auto {
    scroll-behavior: auto;
  }
}
```

How this works

Four native scroll features replace a scroll-spy plugin. The stage is the scroll container with scroll-smooth; the sidebar is sticky top-6 self-start; each category carries scroll-mt-6. That is the whole navigation layer.

The two forgotten rules, now legible. self-start matters because a sticky element stretched to its grid column's full height has no room to move — the number-one cause of my sidebar will not stick. And scroll-mt-* belongs on the targets, not the container: without it the category heading kisses the pane's top edge on arrival. Both are one utility each, on the element that needs them.

What JS is still genuinely required. Highlighting the section you are scrolled to (as opposed to jumped to) is an IntersectionObserver job in every browser in 2026. The honest version here uses target: for the clicked case and pre-styles aria-[current=true]: on the menu links, so wiring an observer later is ten lines and no CSS changes. Scroll-driven animations can fake it in Chromium only; this collection does not pretend otherwise.

Make it yours

  • Categories: each section is independent — add one plus its sidebar link. The count chips are hand-written; wire them to your CMS.
  • Sticky offset: top-6 and scroll-mt-6 must agree. Change both or headings land under the pin.
  • Real scroll-spy: ten lines of IntersectionObserver toggling aria-current — the aria-[current=true]: styles are already in place.
  • Document scroll: delete the stage's overflow-y-auto and h-svh and the same layout runs on the page instead.

Gotchas — read before shipping

  • sticky fails silently inside any ancestor with overflow-hidden. Check every wrapper before blaming the utility.
  • scroll-smooth applies to the scroll CONTAINER — on a real page that is the html element, here it is the stage div.
  • Sticky sidebars must fit the viewport: if categories outgrow it, add max-h-[calc(100svh-3rem)] overflow-y-auto or the bottom links become unreachable.
  • target: only matches after a hash navigation — it is a click-path highlight, not a scroll-position one.

Browser support

ChromeSafariFirefoxEdge
129+not yetnot yet129+

Floor set by oklch(), @container, interpolate-size as this demo is written. The core technique itself goes back further — Chrome 111+.

Sticky positioning, scroll-margin and smooth scrolling are 2019-era everywhere; container queries drive the narrow layout. The animated panel enhances progressively.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…