CSS Accordion Generator

Configure your accordion

Choose a preset, customize questions, styling, and behavior, then preview or export your responsive accordion code.

Accordion content

Add up to eight questions or content sections. Edit the plain-text titles and answers, duplicate items, and move them into the order you need.

Preview your accordion

Updates instantly

Export HTML and CSS

Copy your code, download a standalone HTML page, or get a ZIP with HTML, CSS and a README.

Build Responsive CSS Accordions with Clear Markup and Reliable Behavior

A CSS accordion is a group of headings that reveal or hide associated content, often used for FAQs, product information and help sections. Toolslight's CSS Accordion Generator helps developers, designers and students configure that interface, preview it at different widths, and export editable HTML, CSS and vanilla JavaScript.

Start with Forest, Ocean, Midnight, Sunset, Lavender or Bold, then choose Cards, Grouped or Minimal panels. Cards separate each answer visually, Grouped panels share edges, and Minimal panels use a lighter divider-based design. These styles change presentation; the exported component uses the same button-and-panel architecture.

Choose a preset, enter your questions and answers, adjust the appearance, and select whether one or several panels may stay open. Test the preview with both a pointer and the keyboard before copying the code or downloading a standalone page or ZIP. The visual editor accepts plain text; richer answer markup can be added carefully after export.

Native HTML or a Custom Button Accordion?

There are two useful starting points: the browser's native <details>/<summary> disclosure, or a custom header button paired with a controlled panel. Native HTML is a strong choice when you want simple opening and closing without JavaScript. A custom implementation gives you direct control over grouping, animations, state and application integration, but you must implement its behavior correctly.

Toolslight currently exports the custom button approach with vanilla JavaScript. “CSS accordion” describes the component and its styling; it does not mean this export operates without a script.

Decision Native <details> / <summary> Custom <button> + panel
Semantic markup <summary> labels its parent disclosure; open represents its state A real button inside an appropriate heading controls an identified content panel
State and accessibility The browser supplies disclosure behavior and state exposure; test actual browser/screen-reader combinations Synchronize aria-expanded, panel availability and focus; use unique aria-controls references
JavaScript Not required for ordinary toggle behavior Required for the custom implementation shown here
One open at a time A shared name groups native disclosures in supporting browsers; older browsers may allow several open Explicit group logic controls exclusivity and whether the final panel can close
Keyboard interaction Browser-provided summary activation and normal Tab navigation Native button activation plus deliberate focus management; additional shortcuts are optional
Animation Basic toggling works without animation; advanced intrinsic-size and ::details-content techniques need feature-specific support checks Grid-row transitions handle variable-height answers without hard-coded pixel caps
Browser compatibility Basic disclosures work across current major browsers; group naming and advanced animation have separate support histories Buttons and ARIA are established; Grid interpolation and inert require their own compatibility strategy
Styling flexibility Preserve an identifiable disclosure control when restyling the marker Full control over button, icon and panel structure; more implementation responsibility
Failure behavior Still opens and closes without a script Requires progressive enhancement so content stays readable when JavaScript fails
Best fit Simple FAQs and documentation disclosures Application UI, custom mode policies and tightly controlled interactions

For a native accordion, put <summary> first and give each independent group its own name. Remove name when multiple answers may remain open. The name is a group identifier, not a label announced to users.

<details name="delivery-help" open>
  <summary>When will my order arrive?</summary>
  <p>Your estimated delivery date appears at checkout.</p>
</details>
<details name="delivery-help">
  <summary>Can I change my delivery address?</summary>
  <p>Contact support before dispatch to request a change.</p>
</details>

Do not add redundant role="button" or manually maintained aria-expanded to this native example. Avoid hiding the entire summary control or replacing it with a non-focusable label. A native group can normally have all its items closed; a policy requiring one panel to stay open needs additional logic. See the HTML disclosure specification and MDN's details reference for the native behavior.

Checkbox and radio-input tricks can produce CSS-driven visuals, but they do not automatically provide disclosure semantics, synchronized expanded state or a reliable content-focus model. Prefer native disclosures or a deliberately implemented button pattern.

Smooth Height Transitions Without a Fixed Content Cap

Traditional CSS transitions interpolate compatible numeric values. A transition from height: 0 to height: auto has no ordinary numeric end value, so a conventional height transition does not smoothly interpolate it. Modern CSS can opt into some intrinsic-size transitions with interpolate-size: allow-keywords, but that feature is still listed as limited availability; it should be a progressive enhancement rather than the only mechanism supporting your content. See MDN's interpolate-size reference.

For a custom accordion, a one-row grid can transition from 0fr to 1fr. The direct grid child needs min-height: 0 so its intrinsic minimum does not hold the panel open, and overflow: hidden clips the answer during the transition. Put answer padding inside that child; padding on the outer panel can leave visible space when the track is collapsed.

.accordion-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 240ms ease;
}

.accordion-panel[data-open="true"] {
  grid-template-rows: 1fr;
}

.accordion-panel__clip {
  min-height: 0;
  overflow: hidden;
}

.accordion-panel__content {
  padding: 0 1rem 1rem;
}

@media (prefers-reduced-motion: reduce) {
  .accordion-panel {
    transition: none;
  }
}

Use the structure panel > clip > content. This CSS only controls appearance: clipping does not remove descendants from keyboard navigation or assistive technology. The complete example below synchronizes the visual state with inert, and falls back to hidden when necessary. Grid track animation is supported in current major browsers; older implementations can switch states without a smooth transition. web.dev explains Grid interpolation.

The grid method allows an open answer to grow when its text, images or translated content change. It does not promise to animate every content mutation, and it is not inherently a compositor-only animation: layout still needs to be calculated. Keep motion short and avoid animating many large panels together.

This technique should not be pasted onto a closed native <details> element with the assumption that its closing content will remain renderable. Native closing behavior and advanced selectors such as ::details-content require their own transition strategy and compatibility checks.

Why max-height is an Imperfect Fallback

Transitioning from max-height: 0 to a large fixed cap works only while the content fits below that cap. A long answer can be clipped, particularly after translation, larger text settings or an image loading. A generous cap also distorts timing: with a linear one-second transition, 300px of content and a 1,000px cap, visible opening finishes after about 0.3 seconds; visible closing does not begin until about 0.7 seconds into the reverse transition. Easing changes those timings further.

An instant, fully usable open/close state is a better fallback than a cap that hides part of an answer. If a legacy implementation must animate a measured pixel height, update its measurement when content changes and separate DOM reads from writes to limit unnecessary layout work.

WCAG 2.2 and the ARIA Accordion Pattern

WCAG success criteria describe accessibility outcomes. The WAI-ARIA Authoring Practices Guide provides an informative implementation pattern; following a sample does not certify an entire page against WCAG.

Use a heading that fits the document outline, with the button as its only direct content. An accordion under an H2 section commonly uses H3 headers:

<h3>
  <button type="button" id="help-trigger-1"
    aria-expanded="true" aria-controls="help-panel-1">
    Can I edit my order?
  </button>
</h3>
<div id="help-panel-1" role="region" aria-labelledby="help-trigger-1">
  <p>Contact support before dispatch to request an edit.</p>
</div>

aria-expanded belongs on the trigger and tracks its actual state. aria-controls identifies its panel. If the panel is a named region, aria-labelledby points back to the trigger. Every ID must be unique across the document. Regions are optional for short answers; too many simultaneously exposed regions make landmark navigation noisy. If an open panel cannot be closed, communicate that with aria-disabled="true" while preserving the header's focusability. These relationships follow the APG accordion pattern.

Keyboard check Expected behavior Status in this implementation
Enter / Space Activate the focused header and apply the selected opening policy Native button behavior; no duplicate key handler
Tab / Shift+Tab Follow normal document order through headers and controls in open answers Preserve the standard page sequence; exclude closed-answer controls
Down / Up arrows Move to the next / previous header, wrapping at the ends Optional convenience implemented here; does not toggle a panel
Home / End Move to the first / last header Optional convenience implemented here
Focus during closure Never leave focus inside content that has become unavailable Return focus to that answer's header when necessary

Arrow and Home/End shortcuts are enhancements, not a separate WCAG requirement for every accordion. Keep the standard Tab sequence and native activation working even when a screen reader intercepts navigation keys.

aria-hidden="true" alone is not a focus-management solution. A clipped link can still receive focus, creating a mismatch between what a keyboard user reaches and what is exposed to assistive technology. inert removes the closed subtree from both focus navigation and the accessibility tree; the aria-hidden reference explains why focusable descendants need special care.

Use a clearly visible focus indicator, descriptive questions and a logical heading hierarchy. Keep decorative icons out of the accessible name with aria-hidden="true"; the text should explain the action without relying on a plus sign or color alone.

Complete HTML, CSS and Vanilla JavaScript Example

This three-item accordion supports exclusive and multi-expand modes, an optional requirement to keep one panel open, keyboard focus shortcuts and reduced-motion preferences. It uses no framework, measures no content heights, and leaves all answers readable before JavaScript runs. In that unenhanced state, header buttons are disabled to avoid presenting controls that do nothing; initialization enables them and applies the requested state.

Put the HTML in your page, load the CSS, and load the JavaScript once with defer or after the markup. Keep a responsive viewport declaration in the document head:

<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="accordion.css">
<script src="accordion.js" defer></script>

Replace the demonstration text and /delivery/ link with your own content and URL. If you duplicate this instance, change every shipping-faq ID prefix and its associated references to a new prefix.

<section class="tl-faq" id="shipping-faq" aria-labelledby="shipping-faq-title"
  data-tl-faq data-exclusive="true" data-allow-all-closed="true"
  data-initial-open="1">
  <h2 id="shipping-faq-title">Delivery and returns</h2>

  <article class="tl-faq__item">
    <h3 class="tl-faq__heading">
      <button class="tl-faq__trigger" type="button" disabled
        id="shipping-faq-trigger-1" aria-expanded="true"
        aria-controls="shipping-faq-panel-1">
        <span>When will my order arrive?</span>
        <span class="tl-faq__icon" aria-hidden="true">+</span>
      </button>
    </h3>
    <div class="tl-faq__panel" id="shipping-faq-panel-1"
      role="region" aria-labelledby="shipping-faq-trigger-1">
      <div class="tl-faq__clip">
        <div class="tl-faq__content">
          <p>Delivery dates appear at checkout. Read our
            <a href="/delivery/">delivery information</a> for details.</p>
        </div>
      </div>
    </div>
  </article>

  <article class="tl-faq__item">
    <h3 class="tl-faq__heading">
      <button class="tl-faq__trigger" type="button" disabled
        id="shipping-faq-trigger-2" aria-expanded="true"
        aria-controls="shipping-faq-panel-2">
        <span>Can I change the delivery address?</span>
        <span class="tl-faq__icon" aria-hidden="true">+</span>
      </button>
    </h3>
    <div class="tl-faq__panel" id="shipping-faq-panel-2"
      role="region" aria-labelledby="shipping-faq-trigger-2">
      <div class="tl-faq__clip">
        <div class="tl-faq__content">
          <p>Contact support before dispatch to request an address change.</p>
        </div>
      </div>
    </div>
  </article>

  <article class="tl-faq__item">
    <h3 class="tl-faq__heading">
      <button class="tl-faq__trigger" type="button" disabled
        id="shipping-faq-trigger-3" aria-expanded="true"
        aria-controls="shipping-faq-panel-3">
        <span>How do returns work?</span>
        <span class="tl-faq__icon" aria-hidden="true">+</span>
      </button>
    </h3>
    <div class="tl-faq__panel" id="shipping-faq-panel-3"
      role="region" aria-labelledby="shipping-faq-trigger-3">
      <div class="tl-faq__clip">
        <div class="tl-faq__content">
          <p>Keep your order reference and contact support to arrange a return.</p>
        </div>
      </div>
    </div>
  </article>
</section>

Choose the Opening Policy

Set these attributes on the root before initialization. They are configuration values read at startup, not a reactive settings API.

data-exclusive data-allow-all-closed Result
true true At most one panel open; all may be closed
true false Exactly one panel stays open
false true Any number may be open, including none
false false Several may be open, but the last open panel cannot close

data-initial-open="1" selects the first answer; 2 and 3 select the corresponding items. Use none to start closed when the policy permits it. If all-closed is disallowed, an invalid or empty initial selection falls back to the first answer. This example deliberately initializes one panel; extend initial-state parsing if you need several specific answers expanded on load.

The initializer ignores an instance already marked ready, preventing duplicate listeners. If your application inserts entirely new accordion markup later, run an equivalent initializer after insertion or use the framework's own component lifecycle. Adding items to an existing initialized instance requires explicit reinitialization logic; this static example does not claim a live editor API.

Mobile Responsiveness and Integration

Touch Targets, Reflow and Content

Use 44 × 44 CSS pixels as the minimum design target for header controls. This is the enhanced target-size value associated with WCAG 2.5.5, a Level AAA criterion. WCAG 2.2's Level AA criterion 2.5.8 uses 24 × 24 CSS pixels, with specified exceptions; do not describe 44 × 44 as its universal AA requirement. See Target Size Minimum and Target Size Enhanced.

Let the accordion fill its container up to a readable maximum width. Allow long questions and URLs to wrap, keep images within the answer width, and avoid fixed answer heights. Check the finished page at 320 CSS pixels, larger text settings, browser zoom and both pointer and keyboard input. A device preview is useful, but the actual theme, content and font determine whether the integrated component fits.

Give normal-sized text at least 4.5:1 contrast, or 3:1 for qualifying large text, and verify essential UI cues against adjacent colors. Test hover, focus and expanded states as well as the resting design. The WCAG contrast guidance explains the text thresholds. Never use a hover-only control on a touch interface.

WordPress Themes and Blocks

Put the HTML in a suitable Custom HTML block, theme template or custom block, and enqueue the stylesheet and behavior file through WordPress. Account permissions, sanitization and security plugins may remove scripts pasted into content, so a separate asset is more reliable than embedding a script inside an answer or post body. Load the script once after its markup or with a suitable deferred strategy, and conditionally enqueue it only where the component is used. See WordPress's asset-loading guide.

Keep styles beneath an accordion class rather than targeting every button, h3 or div on the site. Use unique instance prefixes when a template renders more than one group. Check the integrated page for theme styles that override margins, button appearance, outline or [hidden] behavior. Clear relevant caches after changing generated assets.

React and Vue

Convert the markup to a real component and let framework state own the expanded items, button attributes and panel state. Use React onClick or Vue @click to update that state; do not also attach a second independent vanilla script to the same component. Preserve focus before removing or hiding a panel containing the active element.

In React, generate stable accessibility IDs with useId and derive related trigger/panel IDs from that value. Do not use useId as a substitute for stable list keys. In Vue, use a stable per-instance identifier that matches between server and client, and bind the relationships alongside the component's state; the Vue event-handling guide documents its native event binding. Hydrated markup and initial expanded state should agree between server and client.

Tailwind, Bootstrap and Multiple Designs

The export is plain HTML, CSS and vanilla JavaScript. It requires neither Tailwind nor Bootstrap and is not an installable component package. If you convert styles to utility classes, keep the original semantic relationships, reduced-motion rules, target sizes and hidden-focus behavior. Do not initialize Bootstrap's collapse behavior on panels already controlled by this script.

Load shared behavior once for multiple accordions. Distinct styles can be assigned through root-scoped variables, such as #returns-faq { --faq-accent: #253c78; }, rather than loading competing copies of the same unscoped stylesheet. Recheck contrast after any theme change.

Search Visibility and Useful FAQ Content

Include the questions and answers in the server-rendered HTML whenever practical. Collapsing an answer for users is different from omitting it until a click fetches the text. Google can render JavaScript, but server-rendered content reduces dependencies for users and crawlers; see Google's JavaScript SEO guidance.

Write each question around a real task, answer it directly, and add only the detail needed to solve that task. Accordion markup does not guarantee indexing, higher rankings or a featured snippet. Google ended FAQ rich results in May 2026, so FAQ schema should describe the visible content accurately rather than promise an expanded search result. The change is recorded in Google's Search documentation updates.

Frequently Asked Questions FAQs

Is this CSS accordion generator free?

Yes, you can use the generator and download your accordion without paying or creating an account. Choose a preset, edit your questions and preview the result before copying the code. Download the standalone HTML or ZIP when you are ready to keep a local copy.

Is the generated accordion CSS-only?

No, the current generator exports HTML, CSS and vanilla JavaScript. Include all three parts because the script handles opening, closing, keyboard shortcuts and expanded-state updates. For a no-JavaScript alternative, use native details and summary elements as shown in the developer guide.

Can more than one accordion panel stay open?

Yes, turn off Exclusive expand to allow several panels to remain open. Leave Allow all closed enabled so visitors can close an open panel again. Use Open by default to select the initially expanded item, or choose None to start with every answer closed.

How many FAQ questions can I add?

The visual editor supports up to eight accordion items. Each title accepts up to 120 characters and each answer accepts up to 1,000 characters. If you add more items in the exported HTML, give every new trigger and panel unique IDs and update their ARIA references.

Does the accordion work on mobile screens?

Yes, the generated accordion adapts to its container and adjusts spacing on smaller screens. Use the mobile preview to check long questions, then test the exported component inside your actual page. Keep header controls at least 44 CSS pixels high, allow text to wrap and verify the layout at narrow widths and larger text settings.

Can I use the accordion in WordPress, React or Vue?

Yes, you can integrate the exported code into WordPress or adapt it into a React or Vue component. In WordPress, add the markup through a suitable block or template and enqueue the CSS and JavaScript once. In React or Vue, let component state control expansion and use stable IDs instead of attaching a second independent script to the same markup.

Can I add links, images or HTML inside an answer?

The visual editor accepts plain text and escapes HTML, so pasted tags appear as text. Add links or images to the exported markup in your code editor and keep images within the panel width. Before publishing richer content, prevent controls inside closed panels from receiving focus by using inert or an appropriate hidden-state fallback.

Can I put two generated accordions on one page?

Yes, provided each accordion has unique IDs and correctly scoped styles and behavior. Separate exports reuse default IDs, so rename each instance and update its aria-controls and aria-labelledby references together. Load the behavior script once and scope theme rules separately if the instances need different designs.

Why is my exported accordion not opening?

Missing JavaScript, early initialization or broken trigger-to-panel references can stop an exported accordion from opening. Load the stylesheet and script, run the script after the markup exists or with defer, and check the browser console for errors. Keep the extracted ZIP files together and compare your integration with the standalone HTML download.

Are my questions and accordion designs saved automatically?

No, the generator does not automatically save your questions or design. Copy or download the output before refreshing, resetting or leaving the page. Keep the exported files in your project so you can continue editing the code locally.

How do I animate accordion height without a fixed maximum?

Use a CSS Grid row transition from 0fr to 1fr to animate answers without a fixed height cap. Give the inner grid child min-height: 0 and overflow: hidden, with answer padding inside that clipped child. Pair the visual transition with correct focus handling and a prefers-reduced-motion rule, as demonstrated in the complete example.

Can search engines index accordion content and show FAQ rich results?

Search engines can process crawlable accordion answers, but Google no longer displays FAQ rich results. Render the full questions and answers in the page HTML instead of fetching them only after a click. Keep any FAQ schema synchronized with those visible answers and use Search Console URL Inspection to check the rendered page.