Content / React + CSS
Expandable panel
A compact project summary expands smoothly into useful detail.
"use client";
import styles from "./ExpandablePanel.module.css";
import { useState, useId } from "react";
export default function ExpandablePanel({
title = "Website refresh",
summary = "A little progress, every day.",
children = "Review the direction, align the details, and prepare the next iteration for your team.",
}) {
const [open, setOpen] = useState(false);
const id = useId();
return (
<div className={styles.root}>
<button
type="button"
aria-expanded={open}
aria-controls={id}
onClick={() => setOpen(!open)}
>
<span className={styles.icon} aria-hidden="true">
▤
</span>
<span>
<strong>{title}</strong>
<small>{summary}</small>
</span>
<span className={styles.plus} aria-hidden="true">
{open ? "−" : "+"}
</span>
</button>
<div id={id} className={styles.expand} data-open={open} inert={!open}>
<div>
<div className={styles.body}>
{children}
<span className={styles.status}>Ready for review</span>
</div>
</div>
</div>
</div>
);
}
Make it yours
Use for a compact summary that has optional details. Keep the summary sufficient to identify the item. The panel manages its own open state.
- Use a React project with CSS Modules support, such as Next.js or Vite.
- Save the JSX as
ExpandablePanel.jsxand the CSS asExpandablePanel.module.cssin the same folder. - Import
ExpandablePaneland connect its props to your application. Controlled components need parent state and change handlers.
No third-party component or animation package is required. Use the theme controls to adjust accent, corners, spacing, and type. Copy the Theme CSS into your stylesheet and wrap your components in <div className="tdp-components">. The theme applies to every component inside that wrapper. TDP defaults work without a theme file.
Props
| Prop | Type | Use |
|---|---|---|
title | string | Summary heading. |
summary | string | Short supporting text. |
children | ReactNode | Expanded content. |
Accessibility & behavior
The trigger exposes aria-expanded and aria-controls. The closed content is inert so nested controls cannot receive focus. The reveal uses a reduced-motion-aware grid transition.
Visible focus styles and reduced-motion support are included. Check labels, contrast, keyboard behavior, and screen-reader output again after adapting the component to your product.