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.