Motion
How movement should feel, and when to skip it.
Motion shows what changed and where to look. It is short, it starts at once and it never gets in the way of reading. Everything here also works with motion turned off.
Patterns
Replay any one to see its real speed.
Enter
Rises a little and settles, 220ms.
Reduced: Fade only
Exit
Fades and drops away, 150ms.
Reduced: Fade only
Move
Glides to its new spot, 380ms.
Reduced: Fade, no travel
Sheet in
Slides up and settles, 380ms.
Reduced: Fade only
Good software shows its work and leaves you room to think.
Answer appearing
Each word fades in once.
Reduced: Instant
Typing dots
Three dots pulse one after another.
Reduced: Static dots
Shimmer
A glow sweeps across the status text.
Reduced: Static tint
Success swap
Icon changes, then goes back after 1.5s.
Reduced: Same, instant
Error
Static. No shake.
Reduced: Unchanged
Easing
Same dot, three kinds of speed-up and slow-down.
Out
Enter
cubic-bezier(0.2, 0, 0, 1)
In
Exit
cubic-bezier(0.4, 0, 1, 1)
Standard
Move
cubic-bezier(0.2, 0, 0.38, 0.9)
Principles
Tattva follows four principles. The right-hand columns show the closest match in three other systems. We made the pairing ourselves.
| Tattva | What it means here | Carbon | Atlassian | Fluent 2 |
|---|---|---|---|---|
| Purposeful | Motion explains a change or confirms an action. If it does neither, remove it. | Purposeful | Clarity | Functional |
| Responsive | Feedback starts at once. Nobody waits for it. | Immediate feedback | Motion starts immediately | Not matched |
| Natural | Things slow down as they arrive and speed up as they leave. Bigger things take longer. | Continuous elements | Human | Natural |
| Calm | Things people see often are short and quiet. Nothing repeats without a reason. | Not matched | Not matched | Not matched |
Carbon also lists no-motion alternatives and testing across screen sizes. Atlassian also lists accessible and fast. Fluent 2 also lists consistent and appealing. None of the three has a match for Calm, so it is our own addition.
Timing and easing tokens
Four lengths of time and three easing curves. Easing is how an animation speeds up and slows down. Nothing a person presses runs longer than 400ms. One exception: a whole page or view arriving takes 520ms, on a curve that lands most of the way fast and then settles softly, so it feels calm without feeling slow.
| Token | Value | Use |
|---|---|---|
--dur-instant | 70ms | Feedback when you press or toggle. |
--dur-fast | 150ms | Hover, small feedback and everything leaving. |
--dur-base | 220ms | Things arriving: popovers, cards. |
--dur-slow | 380ms | Panels and sheets. Tab panels settling in. |
--dur-page | 520ms | A whole page or view arriving. Never for a control. |
--ease-out | cubic-bezier(0.2, 0, 0, 1) | Arriving. Fast start, soft landing. |
--ease-in | cubic-bezier(0.4, 0, 1, 1) | Leaving. Quick exit. |
--ease-standard | cubic-bezier(0.2, 0, 0.38, 0.9) | Moving between two spots on screen. |
Enter
220ms. Starts fast, lands softly.
Exit
150ms. Starts slowly, leaves fast. Quicker than the enter.
Move
220ms. Eases in and out.
How other systems compare
| Source | Times | Curves |
|---|---|---|
| Carbon | fast-01 70ms (buttons, toggles), fast-02 110ms (fades), moderate-01 150ms, moderate-02 240ms, slow-01 400ms, slow-02 700ms (background dimming). | Productive standard (.2, 0, .38, .9), productive exit (.2, 0, 1, .9), expressive standard (.4, .14, .3, 1). Carbon splits motion into productive (quiet) and expressive (showy). |
| Atlassian | Interactions 50 to 150ms. Transitions 150 to 400ms. | Ease-out bold (0, .4, 0, 1) for panels entering, ease-in-out bold (.4, 0, 0, 1), ease-in practical (.6, 0, .8, .6) for exits, ease-out practical (.4, 1, .6, 1). |
| NN/g | Feedback about 100ms. Substantial changes 200 to 300ms. Range 100 to 400ms. Over 500ms is annoying. | Ease-out is most common. Constant speed feels unnatural. Arrivals run longer than exits. |
| Fluent 2 | Bigger things take longer. | Constant speed only for spinning. |
| Tattva | 70, 150, 220 and 380ms, and 520ms for a page arriving. | Enter (0.2, 0, 0, 1), exit (0.4, 0, 1, 1), move (0.2, 0, 0.38, 0.9). |
Atlassian says exits should be faster so they do not get in the way. That is why our exit is 150ms and our enter is 220ms. The more often an animation runs, the shorter and subtler it should be (NN/g).
Motion for AI states
This list is our own summary of the sources below. It is not a published standard. The times are proposals.
| State | What to show | Do not animate |
|---|---|---|
| Waiting | Under 1 second, show no loader. | Any wait under 100ms. |
| Working | From 1 to 10 seconds, show a shimmer, dots or a gray placeholder shape. | Long, decorative shimmer. NN/g warns that pulsing placeholders can distract. |
| Long run | Over 10 seconds, show progress with status text. | A spinner with no words. |
| Reply appearing | Each new word fades in over 150ms. Only fade, nothing else. | Words already on screen. Height. |
| Arrival | A new item rises in over 220ms. | Items that arrive often, such as every row of a long list. |
| Success | A 150ms icon swap, switching back after about 1.5 to 2 seconds (proposal). | Confetti or repeating effects. |
| Error | A color and icon change. No shake. | Errors. They stay still. |
| Handoff | A calm fade when work passes between the assistant and a person. | Big sweeping moves. |
Pair every waiting state with words. Tattva's shimmer and typing dots are and .
Choosing a loading display
| Expected wait | What to show | Source |
|---|---|---|
| Under 1 second | Show nothing. A tenth of a second feels instant. One second is the most people wait without losing their train of thought. | NN/g response times |
| 1 to 10 seconds | A gray placeholder shape for a whole page. A spinner for one part. | NN/g skeleton screens |
| Over 10 seconds | A progress bar showing percent done. | NN/g skeleton screens |
| Unknown | Any sign of activity is better than none. | NN/g response times |
Do not use placeholder shapes for uploads, downloads or conversions (NN/g).
A reply appearing word by word
Show the answer one word at a time. Add words in place and animate only fade, never height. Words already on screen must not animate again. Streamdown fades each word in over 150ms by default. Upstash limits updates to about 30 per second and stops at word ends. Both are from people who built it.
Sales rose 12% in the third quarter. Most of the gain came from returning customers, while new
const { text, streaming } = useStreamingText(answer);
<StreamingReveal text={text} streaming={streaming} />Scrolling while a reply is written
Keep the conversation stuck to the bottom only while the reader is within about 80px of it. Scrolling up lets go. A floating button in the thread brings them back. It shows a count when new messages arrived. Clicking it scrolls smoothly, or at once if motion is reduced. A ResizeObserver sticks it again when the size changes. When a person sends a message, the advice is to place it near the top. This comes from the shadcn message-scroller docs and other builders, not from a standard.
Question 1
Reply 2. Scroll up, then add a message.
Question 3
Reply 4. Scroll up, then add a message.
Question 5
Reply 6. Scroll up, then add a message.
const { ref, atBottom, unseen, markUnseen, scrollToBottom } = useStickToBottom();
<div className="relative">
<div ref={ref} className="h-96 overflow-y-auto"><div>…messages…</div></div>
<JumpToLatest visible={!atBottom} count={unseen} onClick={scrollToBottom} />
</div>Put the messages in one wrapper inside the scroller. The hook watches that wrapper grow.
Reduced motion
Some people set their device to reduce motion. When they do, remove decorative effects. Keep feedback that tells a person what is happening or how far along it is (web.dev). Atlassian asks that the interface stays fully usable with all motion off, and that nothing flashes or sweeps.
| With motion | With reduced motion | Replace or remove |
|---|---|---|
| Rising or sliding | A fade, or an instant change. | Replace |
| Shimmer on status text | A still tint. The words stay. | Replace |
| Typing dots | Still dots, or a slow fade in and out. | Replace |
| Words fading in | Text appears at once. | Replace |
| Jump to latest scroll | Scrolls at once. | Replace |
| Parallax, scroll-jacking, stagger | No equivalent. | Remove |
What the standards say
- WCAG 2.3.3 (AAA): animation caused by a click or tap can be turned off unless it is essential. Its example is parallax, which can make some people dizzy.
- WCAG 2.2.2 (A): motion that starts on its own and lasts over 5 seconds, next to other content, needs a way to pause, stop or hide it. Essential loading is exempt. Flashing three or more times per second is never allowed.
- Fluent 2 suggests offering a no-motion setting and telling screen readers about changes.
- NN/g says to avoid parallax and scroll-jacking (taking over the page's scrolling).
In code, useReducedMotion() returns true or false from matchMedia("(prefers-reduced-motion: reduce)") and updates when the setting changes. Reveal, StreamingReveal, JumpToLatest and useStickToBottom all use it. As a safety net, the token file also cuts every animation to near zero.
Building it
- Animate only transform and opacity. Avoid properties that make the browser redo layout or repaint. Use will-change sparingly, and only while an animation runs (web.dev animations guide).
- Use CSS for the patterns. Use JavaScript only for scroll handling, buffering a reply as it arrives, and watching the reduced-motion setting. This is our opinion. We found no authoritative source comparing CSS, JavaScript and Lottie.
- View Transitions work within one page in Chrome and Edge 111+, Safari 18+ and Firefox 144+. Between pages they work in Chrome and Edge 126+ and Safari 18.2+, and not in Firefox. Treat them as a bonus: check the browser supports them first, and add your own reduced-motion check.
- Use the Reveal component for arrivals. Items start 40ms apart, and the delay stops growing at the fifth item, so a group finishes inside 400ms. Both numbers are proposals.
if (document.startViewTransition && !reduced) {
document.startViewTransition(() => update());
} else {
update();
}Hooks
| Hook | Gives you | Notes |
|---|---|---|
useReducedMotion() | boolean | Follows the device setting live. False where matchMedia does not exist. |
useStickToBottom({ threshold }) | ref, atBottom, unseen, markUnseen(), scrollToBottom() | Threshold defaults to 80px. Scrolls smoothly, or at once under reduced motion. Watches the container and its first child. |
useStreamingText(full) | text, streaming, start, stop | Demo hook that fakes a reply being written. |
Components: Reveal, StreamingReveal and JumpToLatest.
What we could not verify
- Material 3's emphasized easing (0.2, 0, 0, 1), short1 at 50ms and medium2 at 300ms came from search snippets only.
- We got little from Apple HIG, so we cite nothing from it.
- The AI motion list, the success switch-back time, the 40ms offset and the 220ms arrival are proposals.
- Chat scrolling advice comes from community posts and builders, not standards.
- No authoritative source compares CSS, JavaScript and Lottie for interface motion.