Component

Single Open Accordion

Generic accordion enforcing one-open-at-a-time behavior with an expanding accent bar and a live open-count announcement. For terms, policies, and focused help centers.

  • Component
  • Tailwind
  • HTML
  • MIT

Preview

Live component

Interactive preview
Open preview
9:41 100%
devsnips.dev/library/tailwind/components/accordions/single-open-accordion/
fluid · no fixed width 100%
No dependencies Keyboard-ready

Install

Add to your project

terminal
npx devsnips add Tailwind/Components/Accordions/single-open-accordion
registry path: Tailwind/Components/Accordions/single-open-accordion

Source

Implementation

code.html
<!--
Snippet Name: Single Open Accordion
Description: Generic accordion enforcing one-open-at-a-time behavior with an expanding accent bar and an open-count chip. Keyboard and aria wired.
Author: DevSnips Contributors
Usage Example: Terms, policies, or help centers where focus should stay on one answer.
-->
<div class="space-y-2" data-accordion="single" data-single-open>
  <div class="accordion-item-wrap relative overflow-hidden rounded-xl border border-gray-200 bg-white transition-colors hover:border-gray-300" data-accordion-item>
    <span class="accordion-accent absolute inset-y-0 left-0 w-1 origin-top scale-y-0 bg-emerald-500 transition-transform duration-300 ease-out"></span>
    <h3>
      <button type="button" id="so-trigger-1" aria-controls="so-panel-1" aria-expanded="false" class="accordion-trigger flex w-full items-center justify-between gap-4 pl-5 pr-4 py-4 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-inset rounded-xl">
        <span class="text-sm font-semibold text-gray-900">What data do you collect?</span>
        <svg class="accordion-chevron h-5 w-5 shrink-0 text-gray-400 transition-transform duration-300 ease-out" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
      </button>
    </h3>
    <div id="so-panel-1" role="region" aria-labelledby="so-trigger-1" class="accordion-panel grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out">
      <div class="overflow-hidden"><p class="pl-5 pr-4 pb-4 text-sm leading-relaxed text-gray-600">We collect only what's required to run your account: email, workspace metadata, and billing country. We never track behavior across other sites, and analytics are anonymized and opt-out-able from your settings.</p></div>
    </div>
  </div>

  <div class="accordion-item-wrap relative overflow-hidden rounded-xl border border-gray-200 bg-white transition-colors hover:border-gray-300" data-accordion-item>
    <span class="accordion-accent absolute inset-y-0 left-0 w-1 origin-top scale-y-0 bg-emerald-500 transition-transform duration-300 ease-out"></span>
    <h3>
      <button type="button" id="so-trigger-2" aria-controls="so-panel-2" aria-expanded="false" class="accordion-trigger flex w-full items-center justify-between gap-4 pl-5 pr-4 py-4 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-inset rounded-xl">
        <span class="text-sm font-semibold text-gray-900">Where is my data stored?</span>
        <svg class="accordion-chevron h-5 w-5 shrink-0 text-gray-400 transition-transform duration-300 ease-out" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
      </button>
    </h3>
    <div id="so-panel-2" role="region" aria-labelledby="so-trigger-2" class="accordion-panel grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out">
      <div class="overflow-hidden"><p class="pl-5 pr-4 pb-4 text-sm leading-relaxed text-gray-600">Data is encrypted at rest in the region you select during signup, with replicas confined to that region for residency compliance. You can migrate regions on request without downtime.</p></div>
    </div>
  </div>

  <div class="accordion-item-wrap relative overflow-hidden rounded-xl border border-gray-200 bg-white transition-colors hover:border-gray-300" data-accordion-item>
    <span class="accordion-accent absolute inset-y-0 left-0 w-1 origin-top scale-y-0 bg-emerald-500 transition-transform duration-300 ease-out"></span>
    <h3>
      <button type="button" id="so-trigger-3" aria-controls="so-panel-3" aria-expanded="false" class="accordion-trigger flex w-full items-center justify-between gap-4 pl-5 pr-4 py-4 text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-emerald-500 focus-visible:ring-inset rounded-xl">
        <span class="text-sm font-semibold text-gray-900">How do I request deletion?</span>
        <svg class="accordion-chevron h-5 w-5 shrink-0 text-gray-400 transition-transform duration-300 ease-out" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
      </button>
    </h3>
    <div id="so-panel-3" role="region" aria-labelledby="so-trigger-3" class="accordion-panel grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out">
      <div class="overflow-hidden"><p class="pl-5 pr-4 pb-4 text-sm leading-relaxed text-gray-600">Open Privacy, then Request deletion. We confirm by email and permanently remove personal data within 30 days, including backups. A receipt is generated for your records.</p></div>
    </div>
  </div>

  <p class="px-1 pt-1 text-xs text-gray-400" data-open-count aria-live="polite">0 of 3 open</p>

  <script>
    (function () {
      const root = document.currentScript.closest('[data-accordion]');
      if (!root) return;
      const counter = root.querySelector('[data-open-count]');
      const items = Array.from(root.querySelectorAll('[data-accordion-item]'));
      const updateCount = () => {
        if (!counter) return;
        const n = items.filter((i) => i.querySelector('.accordion-trigger').getAttribute('aria-expanded') === 'true').length;
        counter.textContent = n + ' of ' + items.length + ' open';
      };
      items.forEach((item) => {
        const trigger = item.querySelector('.accordion-trigger');
        const panel = item.querySelector('.accordion-panel');
        const chevron = item.querySelector('.accordion-chevron');
        const accent = item.querySelector('.accordion-accent');
        if (!trigger || !panel) return;
        const close = () => { trigger.setAttribute('aria-expanded','false'); panel.classList.remove('grid-rows-[1fr]'); panel.classList.add('grid-rows-[0fr]'); if (chevron) chevron.style.transform=''; if (accent) accent.style.transform='scaleY(0)'; };
        const open = () => { trigger.setAttribute('aria-expanded','true'); panel.classList.add('grid-rows-[1fr]'); panel.classList.remove('grid-rows-[0fr]'); if (chevron) chevron.style.transform='rotate(180deg)'; if (accent) accent.style.transform='scaleY(1)'; };
        trigger.addEventListener('click', () => {
          const isOpen = trigger.getAttribute('aria-expanded') === 'true';
          items.forEach((o) => { if (o !== item) { const t=o.querySelector('.accordion-trigger'); const p=o.querySelector('.accordion-panel'); const c=o.querySelector('.accordion-chevron'); const a=o.querySelector('.accordion-accent'); t.setAttribute('aria-expanded','false'); p.classList.remove('grid-rows-[1fr]'); p.classList.add('grid-rows-[0fr]'); if(c) c.style.transform=''; if(a) a.style.transform='scaleY(0)'; } });
          isOpen ? close() : open();
          updateCount();
        });
      });
      updateCount();
    })();
  </script>
</div>
78 lines UTF-8 · LF · Spaces: 2

AI-ready

Available to your agent.

This component is reachable through the DevSnips CLI, MCP server and Skills integrations — one registry, one resource path.

Resource path

devsnips://components/accordions/single-open-accordion

Continue browsing

View all