WebCraft

aria-expanded

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.

HTML + CSS
<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

<details name>

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.