void
the stdlib you aren't using
HTML and CSS ship features. Developers install packages instead.
The browser is a runtime. It ships with a standard library. That library includes disclosure widgets, modals, date pickers, autocomplete, live regions, popovers, color pickers, parent selectors, scroll snapping, cascade layers, and container queries.
None of these require a package. None require a bundler. None require a framework. None require JavaScript — except where noted, and where noted, the browser's JavaScript is already loaded.
The following table shows what you're shipping instead, and what it costs in kilobytes you didn't need to pay.
| HTML/CSS | replaces | JS saved (kb) | notes |
|---|---|---|---|
| <details> | accordion component | 4–12 | Disclosure widget. Native. Keyboard accessible. Ships in every browser since 2011. |
| <dialog> | modal library | 8–40 | Modal. Focus trap included. Backdrop included. Escape key closes it. You get all this for free. |
| <input type='date'> | date picker | 20–80 | Date picker. Localized. Keyboard navigable. Linked to the device calendar. Zero bytes from you. |
| <datalist> | autocomplete/combobox | 6–30 | Autocomplete suggestions. Pair with any input. Screen reader compatible. Nobody uses this. Baffling. |
| <meter> | progress/gauge component | 2–10 | Visual meter with min/max/value semantics. Not a progress bar (that's <progress>). Both exist. |
| <output> | live region component | 1–5 | Announces computed values to screen readers. Designed for form output. Used by almost nobody. |
| popover API | tooltip/dropdown libraries | 10–50 | Native popover with light dismiss, focus management, and top-layer rendering. 2024. Already here. |
| <input type='color'> | color picker | 15–60 | System color picker. Always fits the OS. Your custom picker does not fit the OS. Think about it. |
| CSS :has() | parent selector JS workaround | 2–15 | Select a parent by its child. This ended a decade of JavaScript workarounds. Use it. |
| CSS scroll-snap | carousel/slider JS | 5–30 | Snap scroll to items. No library. No event listeners. No momentum calculation. CSS. |
| CSS @layer | specificity hacks, !important | 0 | Explicit cascade layers. You declare order. Specificity fights end. No preprocessor needed. |
| CSS container queries | ResizeObserver JS breakpoints | 3–10 | Style based on parent size, not viewport. The component knows its context. Finally. |
Conservative total: 76–342kb of JavaScript that HTML already does. Per page. Not per app.
why this happens
developers don't read specs
The HTML specification is long and not fun to read. npm is fast and has a search box. The incentive structure favors the package.
native elements look native
<input type="date"> looks like the OS.
Your Figma mockup does not look like the OS.
Designers file bugs. Developers ship 40kb.
browser inconsistencies (historically)
This was true in 2010. It is less true in 2024. We are still shipping 2010's workarounds. We should check whether the workaround still has a problem to solve.
teams don't audit their dependencies
Once a package is in node_modules it stays.
Nobody asks whether the browser now ships what the package provides.
The package upgrades. The bundle grows. The browser already had it.