Skip to content

Motion & sound

Colour and type say what a thing is. Motion and sound say what it is doing: a cap going down, a panel arriving, a readout ticking over. In Echo both are tokens — named, shared, and swappable — so the whole device moves and sounds like one object.

Hear them in the Sound audition; feel them together on the Tactile bench.

What each token means

The tables further down are the physics — a waveform, a duration, a curve. This is the part you have to decide: which token belongs to what you are building. Pick by meaning, never by how the name sounds.

TokenWhat it meansUsed by
pressA part goes down under your finger, and comes back with a little overshoot. 90ms — an acknowledgement, not an animation.2: Motion tokens, useTactile
popSomething small arrives: a chip, a badge, a count. It overshoots, the way a light part would.16: Motion tokens, useTactile, Collapsible, Toggle…
summonSomething larger arrives or leaves: a panel, a sheet, a dialog. The only family that fades under reduced motion instead of stopping.6: Motion tokens, useGsapPresence, Modal, Popover…
breathA slow loop that says a thing is alive and idle — a status light, a meter at rest. It never demands attention.1: Motion tokens
settleA part finds its final position after being moved, dragged or released. Slower, and it decelerates.1: Motion tokens
glitchA screen changes. `steps(3)`, never an ease: this is a segment display, and glass timing on it looks wrong.2: Motion tokens, Command
floodA surface is taken over — a wash of light, a value sweeping across a track.3: Motion tokens, Receipt, Ticket
drainTime passing, scrubbed by a clock rather than played. Linear, because nothing about elapsed time accelerates.1: Motion tokens
liftA part notices the pointer and rises toward the light before anything is pressed. Quick, with a small overshoot.5: Motion tokens, HardwareButton, Switch, NumberField…
followA part trails the hand: a knob being dragged, a cap leaning toward the cursor. Short, so it feels heavy but never late.2: Motion tokens, Modal
travelA part lands somewhere new — a knob sent across a rail, a switch thrown, a selection moving over. It springs into place.3: Motion tokens, Table, WidgetGrid
squashA part gives under the finger as it is grabbed. Faster than a press, and it holds until released.1: Motion tokens
reboundA part is let go and springs back past rest before it settles. The overshoot is what says it has weight.2: Motion tokens, Switch
lightA highlight tracks the pointer across a glossy surface. Smooth, because light has no mass.3: Motion tokens, HardwareButton, LedKey
bloomA popup grows out of what opened it — a menu from its trigger, a tooltip from its target — overshooting a touch and settling. Solid almost at once, so it never reads as a fade.2: Motion tokens, useGsapPresence
islandA panel swells out of a small pill, like an island opening: wider first, then taller, on a spring that settles with a soft wobble. Closing gathers it back into the pill.2: Motion tokens, Modal
bootA readout powers on: cells light in steps, digits count up. Stepped like glitch, because it is still a screen.1: Motion tokens
TokenWhat it meansUsed by
tapThe lightest mark: a key in a field, a step in a sequence. Quieter and higher than a press.13: Sound Engine, Card, Item, Collapsible…
pressA cap has gone down. It bends downward in pitch, the way a real key loads.5: Sound Engine, useTactile, HardwareButton, LedKey…
releaseThe cap has come back up and the action has happened.5: Sound Engine, useTactile, HardwareButton, LedKey…
selectOne of several has been chosen — a tab, an option, an item in a menu.13: Sound Engine, Combobox, CheckboxGroup, ChoiceCards…
transitionSomething has arrived or left: a panel, a sheet, a drawer. It bends upward.5: Sound Engine, Command, Modal, Sheet…
notifySomething happened that you did not ask for, now. Two tones, so it is not mistaken for a press.2: Sound Engine, PullToRefresh
cautionThe one rough sound. A confirm that deserves a second of hesitation, and nothing else.2: Sound Engine, AlertDialog
errorAn action was refused, or a destructive one went through. Low and falling.2: Sound Engine, AlertDialog
celebrateA finished thing worth marking. Spend it rarely; it stops meaning anything otherwise.1: Sound Engine

A token nothing uses yet is not a gap; it is there for your own controls. But if you find yourself wanting a meaning that is not on this list, add a token rather than an inline duration — that is how a motion system stays one system.

Motion

Motion tokens are GSAP timings. Components name a token — press, summon — instead of writing a duration and a curve.

TokenDurationEaseReduced motion
press90msback.out(2)none — no movement
pop400msback.out(2.5)none — no movement
summon300mspower2.outopacity only
breath4000mssine.inOut · loopsnone — no movement
settle600mspower1.inOutnone — no movement
glitch80mssteps(3)none — no movement
flood700mspower3.inOutopacity only
drainscrubbedlinearnone — no movement
lift280msback.out(3)none — no movement
follow180mspower3.outnone — no movement
travel560msback.out(1.6)none — no movement
squash110mspower2.outnone — no movement
rebound700mselastic.out(1.1, 0.45)none — no movement
light450mspower2.outnone — no movement
boot520mssteps(6)opacity only
bloom460msback.out(1.5)opacity only
island600mselastic.out(1, 0.75)opacity only

The rule behind them: glass moves smooth, pixels move steppy. Panels and caps ease; anything on an LCD screen changes in steps (glitch), the way a real segment display does. A readout that eases smoothly looks wrong on this material.

CSS transitions use a matching scale, emitted by the theme:

StepDurationEase
--echo-duration-glitch80mssteps(3, end)
--echo-duration-press90mscubic-bezier(0.34, 1.56, 0.64, 1)
--echo-duration-tick120msease
--echo-duration-swap160msease-out
--echo-duration-glide190mscubic-bezier(0.2, 0.8, 0.2, 1)
--echo-duration-sweep220mscubic-bezier(0.4, 0, 0.2, 1)
--echo-duration-pop400mscubic-bezier(0.34, 1.56, 0.64, 1)
--echo-duration-flood700mscubic-bezier(0.65, 0, 0.35, 1)

Reduced motion

When the OS asks for reduced motion, every token follows its own rule, shown above: most stop moving entirely, and the ones that bring something on screen (summon, flood) fade instead. Nothing is left moving that the reader asked to stop.

Sound

A sound token is a recipe, not a file: a waveform, a pitch, a length and a volume, played by a tiny WebAudio synthesiser. There are no audio assets to load or license, and a recipe is data you can edit.

TokenWavePitchLengthGainHaptic
tapsquare2200 Hz30ms0.08none
presssquare1800 Hz (bend -400 Hz)40ms0.12none
releasesquare1400 Hz30ms0.08none
selecttriangle1100 Hz50ms0.1tick
transitionsine700 Hz (bend +250 Hz)80ms0.08none
notifysine660 → 990 Hz150ms0.15success
cautionsawtooth330 Hz180ms0.14warn
errorsawtooth220 Hz (bend -60 Hz)200ms0.15error
celebratesquare523 → 659 → 784 Hz250ms0.14success

The aim is dry, short and hardware: clicks and chirps from a device, not notes from an instrument. Every play varies the pitch by up to 3% and the volume by up to 10%, so repeated presses do not sound mechanical.

SoundProvider

Components play sound through a provider, and are silent without one:

tsx
import { SoundProvider } from "echo-ui-core/sound";

<SoundProvider masterGain={0.5} muteOnReducedMotion>
  {children}
</SoundProvider>

It takes care of the parts that are easy to get wrong:

  • No audio before a gesture. The audio context starts on the first pointer press, as browsers require, and closes when the provider unmounts, so one inside a page leaves nothing running.
  • It starts muted. Someone who hasn't chosen yet hears nothing until they turn sound on, so a first visit makes no sound nobody asked for. Give them a switch (setMuted below), or pass defaultMuted={false} where sound is the point from the first press.
  • Mute is remembered, in localStorage (echo-sound-muted), and a stored choice always wins.
  • Silent when you are not looking — nothing plays in a hidden or unfocused tab.
  • Reduced motion mutes too, by default (muteOnReducedMotion).
  • Master volume at half the recipe level by default (masterGain).

Read and change it with useSound():

tsx
const { play, muted, setMuted, masterGain, setMasterGain } = useSound();
play("notify");

Haptics

On a phone that can vibrate, the tokens that mark a moment also buzz: select a short tick when a choice changes, notify and celebrate a success, caution a warning, error an error (the Haptic column above). A tap or a press never buzzes. Haptics don't wait for sound to be turned on, stay still in a hidden tab, and never start before the person has touched the page.

tsx
const { play, buzz } = useSound();
play("select");                 // a tick, and the sound when it's on
play("press", { haptic: "warn" }); // another haptic, or false for none
buzz("success");                // a haptic on its own

<SoundProvider haptics={false}> turns them off. Android browsers vibrate; iPhones can't be made to from a web page, so they stay still.

useTactile

useTactile binds motion and sound to the phases of an interaction, so a custom control feels like the built-in ones:

tsx
import { useTactile } from "echo-ui-core/tactile";

const { ref, handlers } = useTactile<HTMLButtonElement>({
  press: { motion: "press", sound: "press" },
  release: { motion: "pop", sound: "release" },
});

return <button ref={ref} {...handlers}>Record</button>;
  • Phases: press, release, hover, leave, focus, blur, activate.
  • Keyboard parity: Space and Enter give the same press and release as a pointer.
  • Reduced motion is applied per token, as above; sound follows the provider.
  • Haptics come with a phase's sound; haptic names another, or false for none.
  • Escape hatch: pass raw GSAP vars or a recipe object instead of a token name.

For Base UI popups that need a GSAP exit before unmounting, use useGsapPresence.

Don't

  • Don't put a soft ease on anything on a screen. LCD and dot-matrix surfaces take glitch.
  • Don't invent a duration inline. If the tokens do not cover a gesture, add a token.
  • Don't set transform in CSS on an element useTactile animates — GSAP owns it.
  • Don't gate sound on reduced motion yourself; the provider already does.
  • Don't add reverb or long tails to a recipe. A device, not an instrument.