Animation isn’t decoration
Open a menu. Move your cursor down the list. Watch what the highlight does.
In most interfaces, it doesn’t move. The background goes out on the item you left and comes in on the item you landed on, with nothing in between. Two unrelated events. The interface is technically telling you which item is active, but it isn’t telling you anything about how you got there.
Now picture the same menu where the highlight slides. One continuous movement from the old item to the new one. The difference feels small. It isn’t. The slide carries information the cut throws away: it shows you the relationship between where you were and where you are.
That distinction is the whole argument. Motion is information the interface is already obligated to convey. Every state change is a small story with a beginning and an end. The user can see the end state, because it’s the one they’re looking at. What they can’t see is how it relates to the one before it. Motion is the part of the interface that narrates that relationship. Done well, it answers the question the user is always quietly asking: what just happened?
What motion actually says
A row that collapses upward tells you the rows below are about to move into the space it leaves. A button that depresses tells you the tap registered. A sheet that slides up from the edge of the screen tells you it came from there and will go back. None of this is ornament. It’s the interface accounting for its own changes so the user never has to reconstruct them.
When motion is missing, that reconstruction doesn’t go away. It just moves to the user. They re-scan the screen to work out what shifted. They lose their place. They wonder if the click landed. Each of these is a tiny tax, paid on every interaction, and like every tax in interface design, the user never names it. They just come away feeling the software is harder to use than it should be.
What I built
I recently went through Playtester and replaced its instant state changes with motion that explains the change instead of merely showing it.
The sliding highlight
The dropdown menus, the select inputs, the search command palette: all of them cross-faded the highlight between items. I replaced that with a single highlight bar that slides.
The implementation is a small hook. A MutationObserver watches for the attribute the underlying library sets on whichever item is currently highlighted. When that attribute moves, the hook measures the new item’s position and repositions a single absolutely placed bar; a CSS transform transition does the rest. Because it’s one element being moved rather than many backgrounds being toggled, the browser renders it as continuous motion.
The hard part was the gap. Crossing a separator between menu sections leaves a brief moment where no item is highlighted, and naively the bar would vanish and flash back. So the hook waits eighty milliseconds before hiding. A cursor moving between items clears that gap in far less; a cursor that has actually left, or come to rest on empty space, does not. One number, holding the line between passing through and stopping.
Knowing when to stay quiet
The search palette has a “Recently viewed” row. When I rolled the sliding highlight out everywhere, it landed there too, and it was wrong.
Recently viewed isn’t a menu you move through item by item. It’s a small cluster of links. The sliding bar made it feel heavier than it is, as though it were asking for a kind of attention it hadn’t earned. So I suppressed it there. Those items get a quiet underline on hover, and nothing else.
This is the part most people miss. Motion is a language, and language includes knowing when not to speak. More animation is not better animation. The only question worth asking of any movement is whether it communicates something true, and if it doesn’t, it’s noise. Noise erodes trust at the same rate a missing transition does. The sliding bar was right in four places and wrong in one, and shipping it meant being honest about the one.
The tab underline
Profile pages have tabs, and the active tab carries an underline. It used to appear in its new place instantly, unconnected to where it had been.
Now it slides. Same principle as the menu highlight, turned on its side. The indicator tracks the active tab and moves horizontally between them, its width adjusting to each one as it goes.
The behavior is subtly different from the menus, and the difference is the point. The menu highlight follows your cursor, so it answers to hover. The tab underline follows your selection, so it answers only to a real choice: a click, or a keyboard arrow. Hovering a tab does nothing. The underline moves only once you’ve committed. An instant change would tell you a tab is active. The slide tells you which tab you came from, and keeps the thread of where you’ve been intact. Once you see motion as a language, the distinction stops being a detail and starts being grammar. Cursor-following and selection-following aren’t the same statement, and using one where the other belongs is an error even when nothing visibly breaks.
Why this is worth the time
None of this is a changelog line anyone reads twice. “Menu highlight now slides” is not a feature. It doesn’t win an evaluation against a competitor, and it doesn’t close a deal.
But it sits in the same ledger as keeping a form’s input after a failed submission, or preserving scroll position on back, or choosing the exact verb on a button. The user never points at any of it. They simply come away knowing the product was built by someone who was paying attention. Motion that explains itself is one more entry in that running tally, the one that either earns a user’s trust or quietly spends it.
The interface is always answering the question of what just happened. The only thing you decide is whether it answers the question, or leaves the question to the user.