demo · v133

Transition coordination via :open

Before :open, animating <details> meant [open] attribute selectors plus pre-2024 hacks. :open is a real pseudo-class — it composes with transitions, with descendant selectors, and (crucially) with :not() — without poking at the DOM.

old way — no :open, snaps

What is :open?

A pseudo-class that matches widgets currently in their open state — <details>, <dialog>, and pickers on <select> / <input>.

Where did it come from?

Selectors Level 4 added it after years of authors asking for a way to style the open state of pickers without scripting.

:open way — smooth open/close

What is :open?

A pseudo-class that matches widgets currently in their open state — <details>, <dialog>, and pickers on <select> / <input>.

Where did it come from?

Selectors Level 4 added it after years of authors asking for a way to style the open state of pickers without scripting.

Both panels use the same markup. Only the right one has CSS that hooks :open.

the CSS that drives the right panel

details > div.body {
  max-height: 0;
  transition: max-height 0.35s ease, padding 0.35s ease;
}
details:open > div.body {
  max-height: 14rem;
  padding: 0.4rem 0;
}
details summary::before {
  content: "›";
  transition: transform 0.25s ease;
}
details:open summary::before { transform: rotate(90deg); }

The point: :open is a state, not an attribute. It works on <select> while its picker is showing — something the [open] attribute selector cannot do because <select> doesn't have one. The CSSWG specifically motivated :open by the need to coordinate transitions and entry/exit animations on these widgets without firing JS events.

see also