UI components built with HTML and CSS only. Zero JavaScript, in 2014 and today.
In 2014, a modal, a carousel or a set of tabs meant a jQuery plugin. This project built them with no JavaScript at all, storing their state in hidden checkboxes, radio buttons and the URL hash. A lot of people shipped them to production.
Twelve years later, the browser does it natively. <details>, <dialog>, invoker commands, the Popover API, anchor positioning and CSS carousels turned those hacks into real, accessible platform features. So the idea is relevant again, and the site now shows both sides.
Each of the six original components, working as it was written in 2014, next to the way HTML and CSS build it natively today.
| Component | 2014 | Today |
|---|---|---|
| Carousel | Radio inputs + labels | scroll-snap, ::scroll-button(), ::scroll-marker |
| Collapse | Hidden checkbox / radios | <details name>, ::details-content |
| Dropdown | Hidden checkbox, :hover |
Popover API, anchor positioning, interestfor |
| Modal | Hidden checkbox | <dialog>, commandfor, @starting-style |
| Tab | Radio inputs | Exclusive <details name> laid out as tabs |
| Tooltip | :hover + ::after |
popover="hint", interestfor, anchor positioning |
Components with no 2014 version. No hidden input could fake them, and until recently each one needed a script.
- Custom select:
appearance: base-select,::picker(select) - Switch and segmented control:
<input switch>,:has() - Smarter forms:
field-sizing: content,:user-invalid - File tree: nested
<details> - Filters drawer:
<dialog closedby>, invoker commands - Sticky header: scroll-state container queries
- Scroll-spy table of contents:
scroll-target-group,:target-current - Reading progress: scroll-driven animations
- Radial menu:
sibling-index(),sibling-count() - Gallery: cross-document view transitions
Some of these features are new. Every exhibit lists where it works today, and browsers without support get a plain version that still works.
- Zero JavaScript. Not in the components, not on the site. It works with JavaScript turned off.
- Honest support. Every native feature shows its real browser support.
- Progressive enhancement. New features sit behind
@supports, so nothing breaks where they are missing. - Accessible by default. The native versions use real elements with focus, keyboard and screen reader support built in.
Each component shows its HTML and CSS on the site, ready to copy. The 2014 originals are kept for history: if you are building something today, use the native versions.
The original Stylus sources and zip downloads are in the v2.0.0 release.
Requires Node.js 22.12+ and pnpm. Built with Astro.
pnpm install
pnpm devpnpm dev: start the dev serverpnpm build: type-check and build todistpnpm lint: lint scripts and styles
Each component lives in its own folder:
src/exhibits/<name>/then.*: the 2014 originalsrc/exhibits/<name>/now.*: the native versionsrc/exhibits/born/<name>/demo.*: born native components
If this project helped you, back then or today, you can sponsor my work on GitHub.
MIT License © Felipe Fialho