Accordion
The class goes on a <details> with a <summary> first. The browser handles opening, closing, the keyboard and screen readers announcing “expanded” or “collapsed”.
If you build the same thing from buttons and aria-expanded, all of that has to be written again. And the text in a closed panel is hidden with hidden, so the browser’s own find in page no longer finds it.
How to use it
Section titled “How to use it”Who can apply for housing benefit?
Anyone who lives in the home and is registered at the address can apply. You must be over 18, or have children of your own living with you.
How long does processing take?
We process the application within three weeks. If attachments are missing, you will be told in your digital mailbox.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/accordion/accordion.css"><!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/lotek.css --><details class="lt-accordion"><summary>Who can apply for housing benefit?</summary><div class="lt-accordion__content"> <p>Anyone who lives in the home and is registered at the address can apply. You must be over 18, or have children of your own living with you.</p></div></details>
<details class="lt-accordion" open><summary>How long does processing take?</summary><div class="lt-accordion__content"> <p>We process the application within three weeks. If attachments are missing, you will be told in your digital mailbox.</p></div></details>import "@lo-tek/interfaces/tokens.css"import "@lo-tek/interfaces/accordion.css"lt is used in two ways, and which one depends on whether you create the element or find it.
If you create the markup, you spread the attributes in. That is the route in React, Astro and anything else that renders:
import { lt } from "@lo-tek/interfaces/react"
<details {...lt.accordion()}> <summary>What counts as income?</summary> <div className={lt.accordion.content}>Salary, pension and business income.</div></details>The import goes to /react here, since the block is JSX. From there the keys are called className and htmlFor. In Astro and anything else without React you import @lo-tek/interfaces and get class and for.
If the element is already there, you set them on it. That is the route when you enhance HTML a server or a template has sent:
import { lt } from "@lo-tek/interfaces"
const panel = document.querySelector<HTMLDetailsElement>("#housing-benefit")if (!panel) throw new Error("element not found")
lt.setAttributes(panel, lt.accordion({ variant: "plain" }))
panel.addEventListener("toggle", () => { console.log(panel.open ? "opened" : "closed")})lt.accordion.content gives the class for the content. toggle is the browser’s own event, and fires both when the user opens the panel and when you set open from code.
| Attribute | Effect |
|---|---|
open | The panel is open when the page loads |
data-variant="plain" | Without its own border, for panels stacked directly under each other |
name on several panels | Only one can be open at a time |
name is the browser’s own way to make an accordion where one panel closes the previous one:
<details class="lt-accordion" name="questions">…</details><details class="lt-accordion" name="questions">…</details>If the server sends the panel again in an update, an open panel closes: open is the browser’s own attribute, and the server’s version does not have it. If it should stay open, the server sends open back, in Datastar with data-preserve-attr="open" on the panel. And TalkBack in Firefox on Android does not open <details> on activation; there the user has to tap the <summary> itself.
Heading inside
Section titled “Heading inside”If the panels should be part of the page structure, put a heading inside the <summary>:
<details class="lt-accordion"> <summary><h3>Who can apply for housing benefit?</h3></summary> <div class="lt-accordion__content">…</div></details>Screen reader users can then jump between the questions with heading navigation. The level must fit the rest of the page.
Classes you can style
Section titled “Classes you can style”| Class | What it is |
|---|---|
.lt-accordion | The panel. The variant comes from data-variant. |
.lt-accordion__content | The content under the heading |
| Variable | Default |
|---|---|
--lt-accordion-radius | var(--lt-spacing-1) |
--lt-accordion-padding | var(--lt-spacing-3) var(--lt-accordion-padding-inline), the space in the heading |
--lt-accordion-padding-inline | var(--lt-spacing-4), the space at the sides, in the heading and the content |
The arrow is drawn with ::after on the <summary>, because the browser’s own triangle looks different in every browser. To replace it, overriding content and border is not enough: the pseudo-element has a fixed width and height and is rotated with transform, and the angle changes when the panel is open and in right-to-left. Reset them in the same rule. Give a visible symbol an empty alternative text, otherwise it becomes part of the button’s name: the screen reader says “Who can apply? +” when the <summary> gets focus. The heading keeps its name, so someone navigating by headings does not hear the symbol. The rule is outside the layer and therefore wins over all the variants in the stylesheet:
.lt-accordion summary::after { content: "+"; inline-size: auto; block-size: auto; border: 0; transform: none;}
@supports (content: "/" / "") { .lt-accordion summary::after { content: "+" / ""; }}If the symbol should change when the panel is open, set it on .lt-accordion[open] summary::after in the same way.
See Customising for how the variables and the layer work.
Accessibility
Section titled “Accessibility”- Do not hide anything the user needs to move on. A required form field does not belong in a closed panel.
- The heading in the
<summary>should say what comes, not “Read more”. - Let the panel that contains an error open when the form is validated. Otherwise the user sees an error message without finding the field.
- The
<summary>is already a button to the browser. Do not put a button inside it.