Accordion
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
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> <Accordion.Item> <Accordion.Heading> <Accordion.Button>Accordion Example 1</Accordion.Button> </Accordion.Heading> <Accordion.Panel> <DummyText length="l" /> </Accordion.Panel> </Accordion.Item></Accordion.Root><div class="b--accordion"> <div class="b--accordion_item"> <div class="b--accordion_heading" role="heading"> <button class="b--accordion_button set--plain" aria-controls="{id}" aria-expanded="false"> Accordion Example 1 <span class="b--accordion_icon a--icon" aria-hidden="true"></span> </button> </div> <div class="b--accordion_panel" id="{id}" hidden="until-found"> <div class="b--accordion_content l--flow"> ...Contents... </div> </div> </div></div><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.
@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'; import { Accordion } from '@lism-css/ui/astro'; <link href="https://cdn.jsdelivr.net/npm/@lism-css/ui@0.30.0/dist/style.css" rel="stylesheet" /> <script type="module" src="https://cdn.jsdelivr.net/npm/@lism-css/ui@0.30.0/dist/scripts/accordion.js"></script> The following components are available:
<Accordion.Root>: The root element wrapping multiple accordion items (specifylayout="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 (useshidden="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
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.
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> <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><div class="b--accordion -bd"> <div class="b--accordion_item"> <div class="b--accordion_heading" role="heading"> <button class="b--accordion_button set--plain -p:20 -hov:-bgc" style="--hov-bgc: var(--base-2)" aria-controls="{id-1}" aria-expanded="false" > Accordion Example 1 <span class="b--accordion_icon a--icon" aria-hidden="true"></span> </button> </div> <div class="b--accordion_panel" id="{id-1}" hidden="until-found"> <div class="b--accordion_content l--flow -p:20 -pt:15"> <p>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.</p> </div> </div> </div> <div class="b--accordion_item -bd-t"> <div class="b--accordion_heading" role="heading"> <button class="b--accordion_button set--plain -p:20 -hov:-bgc" style="--hov-bgc: var(--base-2)" aria-controls="{id-2}" aria-expanded="false" > Accordion Example 2 <span class="b--accordion_icon a--icon" aria-hidden="true"></span> </button> </div> <div class="b--accordion_panel" id="{id-2}" hidden="until-found"> <div class="b--accordion_content l--flow -p:20 -pt:15"> <p>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.</p> </div> </div> </div></div>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.
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> <Accordion.Heading as="h3"> <Accordion.Button>Accordion Label</Accordion.Button> </Accordion.Heading> ... </Accordion.Item></Accordion.Root><div class="b--accordion -bd"> <div class="b--accordion_item"> <h3 class="b--accordion_heading set--plain"> <button class="b--accordion_button set--plain" aria-controls="{id}" aria-expanded="false"> Accordion Label <span class="b--accordion_icon a--icon" aria-hidden="true"></span> </button> </h3> ... </div></div>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>.
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.
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 style={{ '--duration': '.15s' }}> <Accordion.Item>...</Accordion.Item> <Accordion.Item bd-t>...</Accordion.Item></Accordion.Root><div class="b--accordion -bd" style="--duration: 0.15s"> <div class="b--accordion_item">...</div> <div class="b--accordion_item -bd-t">...</div></div>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.
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.
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.
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><div class="b--accordion -bd"> <div class="b--accordion_item" data-opened> <div class="b--accordion_heading" role="heading"> <button class="b--accordion_button set--plain -hov:-bgc" style="--hov-bgc: var(--base-2)" aria-controls="{id-1}" aria-expanded="true" > Accordion Label 1 <span class="b--accordion_icon a--icon" aria-hidden="true"></span> </button> </div> <div class="b--accordion_panel" id="{id-1}"> <div class="b--accordion_content l--flow"> <p>Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor Incididunt ut.</p> </div> </div> </div> <!-- Closed items keep aria-expanded="false" / hidden="until-found" --> <div class="b--accordion_item -bd-t">...</div> <div class="b--accordion_item -bd-t">...</div></div>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.
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.
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. Occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis undeomnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam.
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 allowMultiple bd> <Accordion.Item>...</Accordion.Item> <Accordion.Item bd-t>...</Accordion.Item> <Accordion.Item bd-t>...</Accordion.Item></Accordion.Root><div class="b--accordion -bd" data-allow-multiple> <div class="b--accordion_item">...</div> <div class="b--accordion_item -bd-t">...</div> <div class="b--accordion_item -bd-t">...</div></div>