A living exhibition of the modern web platform

HTML can do that?!

Popovers, modals, accordions, autocomplete, lazy loading, validation — things you were told need a JavaScript library. They don’t. Below are 13 live exhibits, each one rendered with the actual HTML feature it celebrates. Touch everything. That’s the point.

Enter the exhibition ↓

👋 Hi from the top layer.

This greeting card is a popover. No z-index wars, no event listeners, no document.querySelector. It opened because a button said popovertarget="hero-popover" — and it closes when you click anywhere else or press Esc. That trick is exhibit 01, and there are twelve more where it came from.

13
live exhibits
0 B
JavaScript per demo
100%
real, clickable HTML

Exhibit 01

popover

Tooltips, menus, toasts, teach-bubbles — anything that floats above the page. One attribute puts an element in the top layer, gives you light-dismiss (click outside or Esc) and wires the trigger button. No positioning library, no focus trap code, no z-index: 99999.

Baseline 2025 Chrome 114+Edge 114+Firefox 125+Safari 17+
Full support matrix
DesktopChrome 114 · Edge 114 · Firefox 125 · Safari 17 · Opera 100
MobileChrome Android 114 · Firefox Android 125 · Safari iOS 17 · Samsung Internet 23
FallbackContent stays in the page flow — hidden with a tiny [popover]{display:none} guard until opened.
Live — click things

Then click anywhere outside it, or press Esc. That’s light-dismiss, free of charge.

Admit one

The Top Layer

I render above everything on this page — no z-index was harmed. My opener button has never met a addEventListener.

The whole trick
<button popovertarget="ticket">Summon the popover</button>

<div id="ticket" popover>
  <p>I live in the top layer. Click outside or
     press Esc — light-dismiss is built in.</p>
  <button popovertarget="ticket"
          popovertargetaction="hide">Close</button>
</div>

Exhibit 02

<dialog>

A bona-fide modal: the page behind it goes inert, focus stays inside, Esc closes it, and ::backdrop dims the world. Closed with a plain <form method="dialog"> — the chosen button’s value even becomes the dialog’s returnValue.

Baseline 2022 Chrome 37+Edge 79+Firefox 98+Safari 15.4+
Full support matrix
DesktopChrome 37 · Edge 79 · Firefox 98 · Safari 15.4 · Opera 24
MobileChrome Android 37 · Firefox Android 98 · Safari iOS 15.4 · Samsung Internet 3
Opened here byAn invoker button (command="show-modal") — see exhibit 04. Older browsers can call .showModal() in one line of JS.
Live — a genuine modal

While it’s open, try clicking this text or tabbing away. You can’t. The page is inert.

Delete your node_modules folder?

It’s 412 MB of JavaScript you might not need. Meanwhile this modal needed zero bytes: focus is trapped, the backdrop is styled with ::backdrop, and Esc works out of the box.

Modal, the declarative way
<button command="show-modal" commandfor="modal">
  Open the modal
</button>

<dialog id="modal">
  <form method="dialog">
    <p>Focus trapped. Page inert. Esc closes.</p>
    <button value="yes">Yes</button>
    <button value="no">No</button>
  </form>
</dialog>

Exhibit 03

Grouped <details>

Give several <details> elements the same name and the browser turns them into an exclusive accordion — open one, its siblings snap shut. Keyboard accessible, screen-reader friendly, and the state machine is not your problem.

Baseline 2025 Chrome 120+Edge 120+Firefox 130+Safari 17.2+
Full support matrix
DesktopChrome 120 · Edge 120 · Firefox 130 · Safari 17.2 · Opera 106
MobileChrome Android 120 · Firefox Android 130 · Safari iOS 17.2 · Samsung Internet 25
Graceful degradationWithout name support, each panel still opens and closes — just independently.
Live — open one, watch the others fold
🚪 Room one — the lobby

Welcome in. Notice I started open, because someone put the boolean open attribute on me. No script measured my height or toggled a class.

📦 Room two — the archive

The moment you opened me, Room one closed itself. That’s the shared name="rooms" doing radio-button logic on disclosure widgets.

🎁 Room three — the gift shop

Try me with a keyboard: Tab to a summary, Enter or Space to toggle. Accessible by default because the semantics are real.

An accordion is a name away
<details name="rooms" open>
  <summary>Room one</summary>
  <p>Open the next room and I close on my own.</p>
</details>
<details name="rooms">
  <summary>Room two</summary>
  <p>Exclusive. Automatic. Native.</p>
</details>

Exhibit 04

command / commandfor

Invoker buttons: a button declares what it does and which element it does it to. Three separate buttons remote-control one popover below — show, hide, toggle — and the browser wires the events, the aria-expanded state, everything.

Newly Baseline 2025 Chrome 135+Edge 135+Firefox 144+Safari 26.2+
Full support matrix
DesktopChrome 135 · Edge 135 · Firefox 144 · Safari 26.2 · Opera 120
MobileChrome Android 135 · Firefox Android 144 · Safari iOS 26.2 · Samsung Internet 29
Today’s verbsshow-modal, close, request-close, toggle-popover, show-popover, hide-popover. Media and value commands are on the way.
Live — remote-control one popover

Three buttons, one target, zero listeners. The toast also light-dismisses.

📡 Invoked. No JavaScript touched this toast — three different buttons did.

Buttons with a mission
<button command="show-popover"   commandfor="toast">Show</button>
<button command="hide-popover"   commandfor="toast">Hide</button>
<button command="toggle-popover" commandfor="toast">Toggle</button>

<div id="toast" popover>Invoked. No JavaScript.</div>

Exhibit 05

Native pickers

Color wells, sliders with tick marks, calendar popups, time spinners — the browser ships a whole control room. Each one is keyboard- and touch-ready, localised automatically, and themed with a single accent-color.

Baseline · ancient history Chrome 4–25+Edge 12+Firefox 23–57+Safari 3.1–14.1+
Per-type support matrix
colorChrome 20 · Edge 14 · Firefox 29 · Safari 12.1
rangeChrome 4 · Edge 12 · Firefox 23 · Safari 3.1
date / timeChrome 20 · Edge 12 · Firefox 57 · Safari 14.1
CaveatStyling depth varies by browser — treat the UA chrome as a feature, not a bug.
Live — mission control

Twiddle every control — they’re all real. (A furniture script echoes your picks here.)

A control room, declaratively
<input type="color" value="#ff3d81">

<input type="range" min="0" max="11" list="ticks">
<datalist id="ticks">
  <option value="0"><option value="5"><option value="11">
</datalist>

<input type="date">
<input type="time" value="19:30">

Exhibit 06

<datalist>

Autocomplete without a combobox library. One list attribute joins an input to a set of <option>s, and the browser filters as you type — fuzzy, instant, arrow-key navigable.

Baseline 2019 Chrome 20+Edge 12+Firefox 4+Safari 12.1+
Full support matrix
DesktopChrome 20 · Edge 12 · Firefox 4 · Safari 12.1 · Opera 9.5
MobileChrome Android 33 · Firefox Android 79 · Safari iOS 12.2 · Samsung Internet 2
CaveatsThe dropdown’s look is browser-controlled, and screen-reader behaviour still varies — see Adrian Roselli’s Under-Engineered Comboboxen.
Live — type “di” or “in”

That dropdown you just saw? It’s not a component. It’s an element.

Instant autocomplete
<input list="elements" placeholder="Start typing…">

<datalist id="elements">
  <option value="dialog">
  <option value="details">
  <option value="datalist">
  <!-- … -->
</datalist>

Exhibit 07

Constraint validation

required, type="email", pattern, minlength, min/max — the browser checks everything on submit, shows native error bubbles, and focuses the first offender. CSS hooks like :user-valid / :user-invalid style the aftermath.

Baseline · widely available Chrome 10+Edge 12+Firefox 4+Safari 5.1+
Full support matrix
Attributesrequired · pattern · minlength/maxlength · min/max/step · typed inputs — universal for a decade.
:user-valid / :user-invalidChrome 119 · Edge 119 · Firefox 88 · Safari 16.5
Zero-JS limitThe success message below uses one furniture script to stay on-page; the validation itself is 100% native.
Live — submit it empty, then make it happy
Rules, declared
<input required minlength="3" pattern="[a-z0-9-]+">
<input type="email" required>
<input type="number" min="0" max="99">

/* and the styling hooks */
input:user-valid   { border-color: green; }
input:user-invalid { border-color: red; }

Exhibit 08

<meter> & <progress>

Gauges and bars with semantics. A <meter> knows its low/high/optimum zones and colors itself accordingly; a <progress> without a value becomes an indeterminate spinner-bar.

Baseline · widely available Chrome 6/8+Edge 12+Firefox 6/16+Safari 5.2/6+
Full support matrix
meterChrome 6 · Edge 12 · Firefox 16 · Safari 5.2 · Opera 11
progressChrome 8 · Edge 12 · Firefox 6 · Safari 6 · Opera 11
NoteValues are declarative — update them from real data (or a furniture script) when they change.
Live gauges — inspect the attributes

JavaScript powering this page’s exhibits

0 KB

0 KB — optimum zone, green

Delight detected in visitors

92%

92 / 100 — above optimum, still green

Battery of a demo laptop at a conf talk

14%

14 / 100 — danger zone, red

Reading this sentence

100%

Done — determinate

Waiting for a build to finish

loading…

Forever — indeterminate

Gauges with opinions
<meter min="0" max="100" low="30" high="80"
       optimum="90" value="92">92%</meter>

<progress max="100" value="64">64%</progress>

<!-- no value → indeterminate -->
<progress>loading…</progress>

Exhibit 09

<audio controls>

Play, pause, seek, volume, download — an entire media player materialises from one boolean attribute. Below: an 8-bit chiptune synthesised for this exhibition, playing through the browser’s own deck.

Baseline · since HTML5 Chrome 3+Edge 12+Firefox 3.5+Safari 3.1+
Full support matrix
audioEvery shipping browser since ~2010. <video controls> same story.
Useful attributescontrols · loop · muted · preload · poster (video)
This track“Stacked Elements” — square waves generated in Python, no samples, 210 KB WAV.
Live — press play
“Stacked Elements” — the exhibition theme, 0:10, chiptune
A player in one attribute
<audio controls preload="none" src="chiptune.wav">
  Your browser doesn’t do audio? Impressively retro.
</audio>

Exhibit 10

contenteditable

The original rich-text editor — and it’s an attribute, not a framework. Any element becomes writable: click the card below and type. Spellcheck comes along for free.

Baseline · older than jQuery UniversalSince IE 5.5 era
Full support matrix
Desktop & mobileLiterally every browser you’ll ever meet.
Companionsspellcheck="true" · enterkeyhint · inputmode shape the on-screen keyboard on mobile.
CaveatSerialization is famously quirky — for serious editors, libraries still earn their keep. For an inline “edit me”? Perfect.
Live — click the card and type

Dear exhibition, I always thought I needed JavaScript to

Finish that sentence. Then watch the squiggly spellcheck underline — also native.

An editor in one attribute
<p contenteditable="true" spellcheck="true">
  Dear exhibition, I always thought…
</p>

Exhibit 11

loading="lazy"

Images that fetch only when they approach the viewport. No IntersectionObserver, no placeholder dance you wrote yourself. Scroll the gallery corridor below and watch the badges flip from deferred to loaded as you go.

Baseline 2022 Chrome 77+Edge 79+Firefox 75+Safari 15.4+
Full support matrix
DesktopChrome 77 · Edge 79 · Firefox 75 · Safari 15.4 · Opera 64
MobileChrome Android 77 · Firefox Android 79 · Safari iOS 15.4 · Samsung Internet 12
The badgesThe “deferred / loaded” readout is page furniture (~10 lines of JS) that only reports what the browser did natively. Watch your network tab to verify.
Live — scroll the corridor ⤵

Checking the gallery…

deferredPoster: POP — <dialog>, exhibit one, pure HTML.
deferredPoster: OVER — popover, exhibit two, pure HTML.
deferredPoster: HTML — <details>, exhibit three, pure HTML.
deferredPoster: LAZY — <datalist>, exhibit four, pure HTML.
deferredPoster: TOP — <meter>, exhibit five, pure HTML.
deferredPoster: ZERO — <audio>, exhibit six, pure HTML.
deferredPoster: JS? — <input>, exhibit seven, pure HTML.
deferredPoster: NO — <progress>, exhibit eight, pure HTML.
Deferral in one attribute
<img src="poster-01.svg" loading="lazy" decoding="async"
     width="800" height="1000" alt="…">

<!-- width & height reserve the space: no layout shift -->

Exhibit 12

hidden="until-found"

Content that’s hidden from view but findable. Hit a fragment link or use find-in-page, and the browser reveals the section and scrolls you right to it — the search-friendliest accordion state ever specced.

Newly Baseline 2025 Chrome 102+Edge 102+Firefox 148+Safari 26.2+
Full support matrix
DesktopChrome 102 · Edge 102 · Firefox 148 · Safari 26.2 · Opera 88
MobileChrome Android 102 · Firefox Android 148 · Safari iOS 26.2 · Samsung Internet 19
Two ways inFragment navigation (#treasure) or find-in-page — both reveal the element automatically.
Live — a tiny treasure hunt

Somewhere in this stage hides a treasure. It is invisible, it takes up no space… but the browser knows where it is.

🗺 Follow the map

Sceptic? Press ⌘F / Ctrl+F and search for “yahaha” instead.

Hide now, find later
<a href="#treasure">Follow the map</a>

<div id="treasure" hidden="until-found">
  <p>Yahaha! You found me!</p>
</div>

<!-- ⌘F works too: the browser auto-reveals matches -->

Exhibit 13

scroll-behavior: smooth

You’ve been riding this exhibit the whole time. Every jump from the navigation glided here on one CSS declaration — plus anchor links that need no router, no scroll-spy library to move. (The active-item highlighting in the sidebar is furniture JS; the gliding is not.)

Baseline 2022 Chrome 61+Edge 79+Firefox 36+Safari 15.4+
Full support matrix
DesktopChrome 61 · Edge 79 · Firefox 36 · Safari 15.4 · Opera 48
Respect motionWrap it in @media (prefers-reduced-motion: no-preference) — as this page does.
Live — take the express lift

Each jump is a plain <a href="#…">. The glide is one line of CSS.

The entire “router”
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

<a href="#treasure">Take me there</a>

The platform is the library.

Thirteen exhibits, every one interactive, every one running on markup alone. JavaScript is wonderful — but the best byte is the one you never ship.

Tour it again ↺ The original list that inspired this exhibition ↗

Honest footnote: this page ships ~4 KB of furniture JavaScript — scroll-spy highlighting, copy buttons, the lazy-load badges and two demo readouts. Each exhibit runs on 0 bytes. View source and check.

View more demos Get $10 off Kimi K3