Prefer native HTML and SVG
Without hover or zoom, ship SVG and <details>, not a library.
From nextjs-pagespeed.
An FAQ, two ways
Press Reload, then click a question in both panes.
JS accordionBad
Yes. Every table has an Export button in its header.
At rest and in transit.
Yes, from Settings. No call needed.
Loading
JS: …
Dead clicks: 0
details / summaryGood
Can I export to CSV?
Yes. Every table has an Export button in its header.
Is my data encrypted?
At rest and in transit.
Can I cancel any time?
Yes, from Settings. No call needed.
0 ms
JS: 0 B
Dead clicks: 0
<details name="faq"> <!-- same name: one open at a time -->
<summary>Can I export to CSV?</summary>
<p>Yes. Every table has an Export button in its header.</p>
</details>
summary { list-style: none; cursor: pointer; }
summary::-webkit-details-marker { display: none; }
details[open] .chevron { transform: rotate(180deg); }
A sparkline, two ways
Press Load chart, then type in the box.
Client chart libraryBad
Revenue, 24 months$92k
Not loaded
Download –
Main thread blocked –
Painted at –
Inline SVG in the HTMLGood
Revenue, 24 months$92k
+119% since month 1
Markup …
Main thread blocked 0 ms
Painted at …
Worst input delay: –
// Server Component: the client gets markup only
export function Sparkline({ values }: { values: number[] }) {
const min = Math.min(...values), max = Math.max(...values);
const points = values
.map((v, i) => `${(i / (values.length - 1)) * 240},${60 - ((v - min) / (max - min)) * 56}`)
.join(" ");
return (
<svg viewBox="0 0 240 64" role="img" aria-label="Revenue up 119% over 24 months">
<polyline points={points} fill="none" stroke="currentColor" vectorEffect="non-scaling-stroke" />
</svg>
);
}
Accessibility for free
Click Start here, then press Tab and Enter.
Hand-rolled divBad
Shipping options
Standard, express, or pickup.
Focus: –
Exposed: generic, no state
Native detailsGood
Shipping options
Standard, express, or pickup.
Focus: –
Exposed: button, collapsed
<details>
<summary>Shipping options</summary>
<p>Standard, express, or pickup.</p>
</details>
details.addEventListener("toggle", () => track(details.open));
Why it matters, pros and cons
Why it matters
The browser already does static figures and disclosure; a library adds bytes.
Pros
- Zero JS to download or hydrate.
- Works when the bundle fails or lags.
- Keyboard and screen reader support built in.
Cons
- Tooltips, zoom or drill-down need a client chart.
- Animating
<details>height lacks full browser support. - Tabs and comboboxes have no native element.