Docs
Search

Accordion

This page is currently under construction
Lism UI (@lism-css/ui) is still in preparation.

A UI component for creating accordion elements.

It is built with <div> / <button> elements rather than <details> / <summary>, and uses JavaScript to control the open/close animation. The panel uses hidden="until-found", so its content can be discovered by the browser’s in-page search even when the accordion is closed, and the panel will automatically expand when a match is found.

Overview

↓
Preview
<Accordion.Root>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Button>Accordion Example 1</Accordion.Button>
</Accordion.Heading>
<Accordion.Panel>
<DummyText length="l" />
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>

<Accordion.Button> automatically includes <Accordion.Icon> at the end. The icon is rendered using CSS pseudo-elements (::before / ::after), so no SVG specification is needed.

Styles

The Accordion base styles are defined in the following CSS.

_style.css
@layer lism-block {
  .b--accordion {
    --duration: var(--acc-duration, 0.25s);
  }
  .b--accordion_item {
    --_panelH: 0px;
    --icon-transform: rotate(90deg);
  }

  /* 開いている時 */
  .b--accordion_item[data-opened] {
    --icon-transform: rotate(0deg);
    --_panelH: auto; /* Note: アニメーション時、jsでセットされる */
  }

  .b--accordion_button {
    display: flex;
    gap: var(--s15);
    align-items: center;
    justify-content: space-between;
    padding: var(--s15);
    width: 100%;
  }
  .b--accordion_panel {
    position: relative;
    overflow: hidden;
    height: var(--_panelH);
    transition: height var(--duration);
  }
  .b--accordion_content {
    /* _panel に padding を付けると閉じた時に余白が残るので _content に付ける */
    padding: var(--s15);
  }

  /* パネルが完全に閉じている時にabsoluteで飛ばしておくと、Chromeでも検索時に正常にハイライトされるようになる(なぜかは不明) */
  [hidden] > .b--accordion_content {
    position: absolute;
  }

  /* アイコンの描画 */
  .b--accordion_icon {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 1em;
    height: 1em;

    &::before,
    &::after {
      content: '';
      display: block;
      grid-area: 1 / 1;
      background-color: currentColor;
      height: 0.1em;
      width: 87.5%;
      border-radius: 0.1em;
    }
    &::before {
      transition: transform var(--duration);
      transform: var(--icon-transform);
    }
  }

  /* フォーカス時のアウトラインを少し内側に寄せる( overflow:hidden によって途切れてしまうので)  */
  .b--accordion_button:focus-visible {
    outline: solid 2px currentColor;
    outline-color: revert;
    outline-offset: -3px; /* offset 調整だけだとブラウザ間の差が大きい */
  }

  /* --- 「視差効果を減らす」設定を考慮 --- */
  @media (prefers-reduced-motion: reduce) {
    .b--accordion_item {
      --duration: 0s;
    }
  }

  /* --- JSオフ環境の考慮 --- */
  @media (scripting: none) {
    .b--accordion_panel {
      height: auto !important;
      content-visibility: visible !important;
    }
    .b--accordion_content {
      position: static !important;
    }
  }
}

The full source code is available on GitHub.

Usage

Provided as Accordion from the @lism-css/ui package.

Import

import { Accordion } from '@lism-css/ui/react';

The following components are available:

  • <Accordion.Root>: The root element wrapping multiple accordion items (specify layout="stack" or similar if a layout is needed)
  • <Accordion.Item>: An individual accordion element
  • <Accordion.Heading>: Wrapper for the heading area (defaults to <div role="heading">)
  • <Accordion.Button>: The toggle button (automatically includes <Accordion.Icon> at the end)
  • <Accordion.Icon>: The open/close state indicator icon (rendered via CSS pseudo-elements)
  • <Accordion.Panel>: The content panel that opens and closes (uses hidden="until-found")

Props

Prop Description
<Accordion.Root>
allowMultiple
Allows multiple accordions to be expanded simultaneously. By default, only one can be open at a time.
<Accordion.Item>
isOpen
Expands the item by default (adds data-opened). Use together with isOpen on <Accordion.Button> / <Accordion.Panel>.
<Accordion.Heading>
as
Specifies the HTML tag for the heading. Defaults to div (with role="heading" added automatically). If h2–h6 is specified, role is not added.
<Accordion.Button>
isOpen
Sets aria-expanded to match the initial expanded state.
<Accordion.Panel>
as
Specifies the HTML tag for the outer panel element (.b--accordion_panel). Defaults to div.
<Accordion.Panel>
Lism Props
Props such as p, pt, and flow are passed to the inner content area (.b--accordion_content). as, class (Astro) / className (React), accID, and isOpen are applied to the outer panel element.
<Accordion.Panel>
isOpen
Shows the panel by default (removes hidden).

Examples

Styling example

↓
Preview
<Accordion.Root bd>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Button p="20" hov={{bgc:"base-2"}}>Accordion Example 1</Accordion.Button>
</Accordion.Heading>
<Accordion.Panel p="20" pt="15">
<DummyText length="l" />
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item bd-t>
<Accordion.Heading>
<Accordion.Button p="20" hov={{bgc:"base-2"}}>Accordion Example 2</Accordion.Button>
</Accordion.Heading>
<Accordion.Panel p="20" pt="15">
<DummyText length="l" />
</Accordion.Panel>
</Accordion.Item>
</Accordion.Root>

Changing the heading tag

<Accordion.Heading> outputs a <div role="heading"> by default, but you can specify a heading tag like h3 via the as prop to render it as an actual heading element.

↓
Example

<Accordion.Root bd>
<Accordion.Item>
<Accordion.Heading as="h3">
<Accordion.Button>Accordion Label</Accordion.Button>
</Accordion.Heading>
...
</Accordion.Item>
</Accordion.Root>

Changing the animation duration

The speed of the open/close animation is controlled by the --duration variable, which defaults to 0.25s. You can change it by setting the CSS variable on <Accordion.Root> (for all items) or on an individual <Accordion.Item>.

↓
Preview
<Accordion.Root bd style={{ '--duration': '.15s' }}>
<Accordion.Item>...</Accordion.Item>
<Accordion.Item bd-t>...</Accordion.Item>
</Accordion.Root>

Opening the first item by default

To have an item expanded from the start, specify isOpen on each of <Accordion.Item>, <Accordion.Button>, and <Accordion.Panel>.

Because isOpen plays a different role on each component, specify it on all three. On <Accordion.Item> it adds data-opened to set the panel height and icon to the open state, on <Accordion.Button> it controls aria-expanded, and on <Accordion.Panel> it toggles hidden.

↓
Preview

Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut. Labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut. Aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint.

<Accordion.Root bd>
<Accordion.Item isOpen>
<Accordion.Heading>
<Accordion.Button hov={{bgc:"base-2"}} isOpen>Accordion Label 1</Accordion.Button>
</Accordion.Heading>
<Accordion.Panel isOpen>
<DummyText length="l" />
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item bd-t>...</Accordion.Item>
<Accordion.Item bd-t>...</Accordion.Item>
</Accordion.Root>

Allowing multiple panels to be open

By default, only one accordion within the same <Accordion.Root> can be expanded at a time. Specifying allowMultiple allows multiple panels to be open simultaneously.

↓
Preview
<Accordion.Root allowMultiple bd>
<Accordion.Item>...</Accordion.Item>
<Accordion.Item bd-t>...</Accordion.Item>
<Accordion.Item bd-t>...</Accordion.Item>
</Accordion.Root>

© 2026 Lism CSS.