`` for collapsible UI without a framework
Every collapsible widget in the brainstem is a native HTML <details> element. The agent-call log chips. The accordion-style sections inside the binder modal. The expandable settings rows. None of them use a framework’s “Disclosure” or “Accordion” component. They all use <details> plus some CSS.
<details>
<summary>› ✦ <span class="al-name">Save Memory</span> agent called</summary>
<pre class="al-pre">{...args...}</pre>
<pre class="al-pre">{...result...}</pre>
</details>
The browser handles open/close natively. Click the summary, the contents reveal. Click again, they hide. Keyboard accessible (Enter / Space toggle, Tab focuses, screen readers announce as “expandable”). All for free.
The only CSS we add:
/* Hide the default ▶ disclosure triangle. */
.agent-log details summary::-webkit-details-marker { display: none; }
/* Substitute our own arrow that rotates 90° when open. */
.agent-log details summary::before {
content: '›';
color: #58a6ff;
display: inline-block;
transition: transform .15s;
}
.agent-log details[open] summary::before { transform: rotate(90deg); }
details[open] is the magic selector. The browser sets the open attribute on the element when it’s expanded. CSS reads it. Animation comes from a CSS transition on the transform. No JavaScript involved in the open/close itself.
The one piece of JS we add: the toggle event.
div.querySelectorAll('.agent-log details').forEach(d => {
d.addEventListener('toggle', () => { if (d.open) scrollChat(); });
});
When a chip opens, we want the chat to scroll so the new content stays in view. The toggle event fires on every open/close. We check d.open and scroll if it’s the open transition. Three lines.
Why this beats a framework component:
A framework “Disclosure” component would give you:
- A controlled
openstate. - An
onChangecallback. - Optional animation primitives.
- Probably a custom indicator slot.
You’d write maybe ~50 lines of component code. You’d have to import it, configure it, handle re-renders when the open state changes. The browser already does ALL of this — natively, fast, accessible, with no library code shipped to the user.
<details> is in every browser back to IE11 (well, IE11 doesn’t render the indicator, but the open/close works). Mobile browsers handle it correctly. Screen readers announce it correctly. Keyboard shortcuts work. The CSS to skin it is portable.
Why this isn’t more popular:
Two reasons.
One: it’s perceived as “old.” <details> shipped in HTML5 over a decade ago. Many devs learned framework patterns first, never circled back to native equivalents. They reach for the framework primitive because that’s what they know.
Two: it doesn’t compose with framework state. If your app holds open/close state in Redux/Zustand/whatever, you’d want a controlled component so the framework can render the right state. <details> is uncontrolled — the browser owns the state. You CAN sync via the open attribute and the toggle event, but it requires a tiny bit of glue.
For a vanilla app like the brainstem, neither of those matters. The browser is the framework. <details> is exactly the right primitive.
The pattern generalizes:
Every UI primitive a framework gives you, the browser probably has a native version of:
- Modal?
<dialog>(with.showModal()). - Tooltip?
titleattribute (basic), or CSS::beforeoverlay (styled). - Date picker?
<input type="date">. - Color picker?
<input type="color">. - Range slider?
<input type="range">. - Search box with autocomplete?
<input list="...">+<datalist>. - Progress bar?
<progress>. - Meter?
<meter>. - Disclosure?
<details>.
Each of these is browser-native, accessible by default, faster than a JS component, and zero bytes of JS shipped to the user. The styling defaults are usually ugly; that’s CSS work, not framework work.
For a vanilla project: reach for the native element first. Add framework component only when the native one can’t do what you need (often: never).
The brainstem ships about 20 <details> elements across its UI. None of them required any JavaScript beyond optional auto-scroll on open. They all work. They all look correct. They cost almost nothing.
The browser already built the components. Use them.