Shots in this scene
Animated Icons and SMIL
In one line
An animated icon is a small SVG that changes with a state: a menu becomes a cross, a tick draws. The icon must keep a name for screen readers and stand still for reduced motion. SMIL, SVG's own animation language, still fits where CSS cannot reach, but it is not on the timeline.
Think of it like this
The hands of a clock tell you the time, and the little pictograms on a lift button tell you what it does. An animated icon is a pictogram that shows a change. If the button has no label, a person who cannot see the picture hears only "button".
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- accessible name
- The text a screen reader announces for a control, from
aria-label, text inside it, or a<title>in its SVG. - aria-expanded
- An attribute on a button that says whether the thing it controls is open (
true) or closed (false). - aria-hidden
- An attribute that hides an element from screen readers. Use it on a decorative icon next to or inside a named control.
- SMIL
- Synchronized Multimedia Integration Language: animation elements inside SVG (
animate,animateTransform,animateMotion,set). - live region
- An element with
role="status"whose text changes are announced by screen readers without moving focus.
Step by step
01Press the menu button and slow it down
At 0.1× press the first button. Three transition tracks run: transform on the top and bottom bars (220 ms, green) and opacity on the middle bar (120 ms, linear). The console already said the first button has a name and a hidden icon, and the second has no name.
02Switch on Reduced motion
Turn on the timeline's reduced-motion switch and press the button: the cross appears with no tracks at all. The state still changed (aria-expanded is true).
03Save and watch the tick
In "A tick that draws" press Save at 0.25×. The ring scales in first and the tick draws after it. The label and the status line both say Saved.
04Compare SMIL with the timeline
In "SMIL on the page" the console says SMIL elements: 4 and document.getAnimations(): 0, and the timeline is empty although dots move. Press the example's Pause button: they hold, because it calls pauseAnimations(). The timeline's own Pause and Step buttons do not control them.
Try it yourself
- 1
Read the menu's tracks
At 0.1×, press the first button and pause at 60 ms. The top bar is turned about 36 degrees and the middle bar is at opacity 0.5. At 220 ms the top bar is at 45 degrees and 6 units down.
- 2
Skip the delay for reduced motion
Switch on Reduced motion and press Save. No tracks appear and the finished tick is on screen at once, with the status text
Saved. - 3
Pause the SMIL
Press Pause: the dots stop. Press it again (it says Play) and they continue. In a reduced-motion run the page starts paused (
paused: true).
Code & diagrams
The second button is the failure: an SVG and no name. The first has `aria-label`, an `aria-hidden` icon and the state in `aria-expanded`.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
menu button has a name: true, icon hidden from screen readers: true
second button has a name: false
after one click: aria-expanded is true, label is still MenuPress **Save**, then **Reset**. The finished tick is the resting state; the animations only add the way there. Run it with the timeline's Reduced motion switch on to see the tick appear at once.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
reduced motion asked: falseThree pulsing dots and a dot riding a curve, all SMIL. With reduced motion on, the script pauses them.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
SMIL elements: 4
document.getAnimations(): 0
reduced motion asked: false, paused: falseBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Remove the label
In the first example delete aria-label="Menu" from the first button.
Myth vs fact
Myth
prefers-reduced-motion in CSS stops every animation, including SMIL.
Fact
It does not reach SMIL. Call pauseAnimations() from a script that reads matchMedia.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
SMIL has its own clock per SVG. For tests, use
setCurrentTime()to jump to a moment and wait one frame before you read a value. Prefer CSS or the Web Animations API when you need an exact time. - ▸
Make the button at least 44 CSS pixels to tap (the example is 44 by 44) even if the drawing is 24: the hit area is the button.
Remember this
- 1
The problem this solves: an icon made of three lines should show "menu" and then "close", in 220 ms, without a second image, without JavaScript beyond the click, and without losing its meaning for a screen reader or a reduced-motion setting. The shape moves with CSS transitions on a state attribute.
- 2
A menu that becomes a cross. Three
<line>bars. Witharia-expanded="true"on the button the top bar moves down 6 units and turns 45 degrees, the bottom bar moves up 6 and turns -45, and the middle fades. The bars usetransform-box: fill-boxandcenter(Topic 13.1). Measured after a click: at 60 ms the top bar was 36 degrees round and the middle at opacity 0.5, at 110 ms it was 43 degrees, and at 220 ms it was done (45 degrees, 6 units down). The easing is out-style, so most of the turn comes first. - 3
The state is an attribute, not a class.
aria-expandedis what a screen reader announces ("Menu, expanded"), and the CSS reads it, so the picture and the meaning cannot disagree. The label stays "Menu": changing it to "Close menu" as well makes it say two things at once. - 4
A name for the button. An SVG-only button has no accessible name: the second button of the first example reports none. Three fixes give one:
aria-label="Menu"on the button, a<title>inside the SVG, or visually hidden text next to the icon. Then putaria-hidden="true"on the SVG, so the picture is not announced as well, andfocusable="false"for old browsers that made SVGs tabbable. - 5
A tick that draws. A ring scales in (400 ms) and the tick draws with the dash trick (350 ms after a 250 ms delay). The finished tick is the resting state, and "Saved" goes into a
role="status"region so that people who do not see the tick hear that it worked. Under reduced motion the finished tick shows at once: measured, no tracks and a dash offset of 0. - 6
SMIL.
<animate>,<animateTransform>and<animateMotion>are animation elements written inside the SVG. Chromium here ran all of them. They are not Web Animations: in the example with four SMIL elements,document.getAnimations()returned 0, so the Motion Timeline shows no track, and its Pause, Step and scrub have no code for them: SMIL keeps its own clock. Reduced motion does not reach them either: no media query applies to SMIL, so the script callssvg.pauseAnimations()(measured:paused: truein a reduced-motion run). The SVG object also hasunpauseAnimations(),setCurrentTime()andgetCurrentTime(). In the third example, aftersvg.pauseAnimations(); svg.setCurrentTime(1.5)and one frame, the riding dot was at (161, 70) in the drawing, half way along its 3-second curve, and the first dot's radius was 10, the top of its 1-second pulse (getComputedStylereads the animatedr). The new time shows after the next frame, not at once. - 7
Where SMIL still fits. It can animate attributes CSS cannot reach, such as a polygon's
points(the DOM attribute stays unchanged, the animated value lives apart). A begin time can be an event on another element (begin="go.click", no script), and it runs inside an SVG used as an<img>, where there is no page script. Otherwise prefer CSS or the Web Animations API: they appear on the timeline, takeprefers-reduced-motionand can be cancelled from script.
Explain it without notes
How do you make an icon-only button accessible, and what do you do with the SVG inside?
Why is SMIL not a good default today, and when does it still make sense?
Practice
Write a play and pause button whose icon changes with aria-pressed, with no motion for reduced motion and a name.
Add a <title>-free way to name a link whose only content is an SVG logo.
Done when you can
You can give an icon-only button a name and hide its SVG
You can drive an icon from
aria-expandedand keep reduced motionYou can say what SMIL is and what the timeline cannot show of it
You can pause SMIL from script for reduced motion