A menu button that turns into a close button
CaveatIt is a button with aria-expanded, not a checkbox. A checkbox announces itself as a checkbox, and a menu toggle that reads as "checkbox, not checked" is worse than no label at all.
FallbackAnimate rotate and translate, never top. The transform pair runs on the compositor; moving top relayouts the button on every frame.
<button aria-expanded="false">
<span></span><span></span><span></span>
</button>
[aria-expanded="true"] span:nth-child(1) {
translate: 0 8px; rotate: 45deg;
}
Published
Questions
Button with aria-expanded, or the checkbox hack?
A button. A checkbox announces itself as a checkbox, and a menu toggle that reads as "checkbox, not checked" tells a screen reader user something that is not true. aria-expanded on a real <button> says exactly what the control does and what state it is in.
What has to change when the menu opens?
The attribute, and nothing else that matters — flip aria-expanded to true and let CSS take the icon from bars to a cross off that same attribute. One source of truth for the state, rather than a class and an attribute that can disagree.
Why animate transform rather than top?
rotate and translate run on the compositor; animating top relayouts the button on every frame. At this size you may not see the difference on a desktop, which is exactly how a phone ends up dropping frames on a menu icon.