State Icons: Menu to Close, Play to Pause, on a Spring
motion-state-icon has since been renamed tomotion-icon-state . The examples below use the new name. The old name still works until version 2.0.
Many icons show a state. A menu button shows three lines when the menu is closed and a cross when it is open. A play button turns into a pause button. A copy button shows a check after it copies.
This article explains how the element works and gives five complete recipes that you can paste into a page.
Why morph instead of swap
The usual way to show a state is to swap one icon for another. The menu icon disappears and a close icon appears in its place. This works, but the change is sudden, and the user does not see how the two states connect.
A morph moves the parts of one icon into the other. The three lines of the menu icon turn and meet in the middle to form a cross. The user sees one control that changes, not two different controls.
The morph also handles fast clicks. Every change runs on a spring from the current position. If a user clicks again before the change finishes, the icon turns around from where it is. It does not jump to the end first, and it does not wait.
Try it in one file
Save this as
Click each icon to switch its state. You can also press Tab to move to an icon and press Space or Enter.
Eight icons
Set
| Without | With | Typical use | |
|---|---|---|---|
| menu | close | Navigation drawer | |
| play | pause | Audio and video players | |
| copy | check | Copy to clipboard | |
| plus | minus | Expand and collapse | |
| chevron down | chevron up | Accordions and drop-down menus | |
| empty heart | filled heart | Like and favorite | |
| spinner | check | Save and submit | |
| eye | eye with a line through it | Show and hide a password |
All eight icons use a 24 pixel grid and 2 pixel round lines. This is the same style as Lucide, Tabler and Heroicons, so a state icon looks right next to the icons you already use.
Two ways to switch
1. The icon is the button
Add
Use this way for small controls that have no text, such as a like icon on a card.
2. Your button controls the icon
Leave out
Use this way when the button already exists, or when it has text next to the icon. All five recipes below work this way.
Recipe 1: Menu button
The button tells screen readers whether the menu is open with
Recipe 2: Copy button
The icon shows a check for 1.5 seconds after the copy, then returns to the copy icon.
Recipe 3: Save button with a spinner
The
Recipe 4: Show password
When the password is visible, the icon shows the eye with a line through it. The button label changes too, so a screen reader always says what the next click does.
Recipe 5: Accordion
The native
Color, size and feel
| Property | What it changes | Default |
|---|---|---|
| The color of the lines | The text color | |
| The icon in its second state, including the filled heart and the checks in | ||
| The width and height |
Two attributes change how the morph feels:
| Attribute | What it changes | Default |
|---|---|---|
| How long the morph takes, in seconds | ||
| How much the spring overshoots. |
A higher
React and Vue
React 19 sets
In Vue, bind
Accessibility
| Setup | What a screen reader gets | What you add |
|---|---|---|
| With | A button with | A |
| Inside your own button | Nothing from the icon. It is decorative. | A name on the button: its text or |
| Alone, without | Nothing. It is decorative. | A |
When a user turns on reduced motion in their system settings, the icon changes state at once with no morph. The loading spinner stands still.
Next steps
Read the motion-icon-state docs for live versions of every recipe in this article. For icons that do not show a state, such as a bell that wiggles or a heart that draws itself in, read motion-components 1.1: Animate Any SVG Icon With One HTML Tag.