PageSourceSearch

https://mattermost-proto-playground-elias.vercel.app/assets/animation.guideline-DlR26hJa.js

js mattermost-proto-playground-elias.vercel.app collected 2026-10-03 07:17:51 UTC 9,893 bytes, 96 lines download raw bytes

1import{cd as c,ce as e}from"./index-C84RuTgJ.js";function h(s){const n={a:"a",code:"code",em:"em",h2:"h2",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...c(),...s.components},{Divider:t,MotionMatrix:a,PopoverPattern:o,Principle:i,PrincipleList:d}=n;return t||r("Divider"),a||r("MotionMatrix"),o||r("PopoverPattern"),i||r("Principle"),d||r("PrincipleList"),e.jsxs(e.Fragment,{children:[e.jsx(n.p,{children:`Motion gives the interface a sense of cause and effect. Things appear, move,
2and disappear in ways that mirror how objects behave in the physical world —
3and when used carefully, animation reinforces hierarchy, communicates state
4changes, and helps the product feel alive. Used carelessly, it gets in the
5way.`}),`
6`,e.jsx(n.p,{children:`This page covers when to animate, the duration and easing tokens we ship, and
7the patterns that combine them.`}),`
8`,e.jsx(t,{}),`
9`,e.jsx(n.h2,{id:"when-to-animate",children:e.jsx(n.a,{href:"#when-to-animate",children:"When to animate"})}),`
10`,e.jsxs(n.p,{children:["Reach for animation when motion ",e.jsx(n.strong,{children:`answers a question the user is already
11asking`})," — ",e.jsx(n.em,{children:"Where did this come from? Where did it go? What just changed?"}),`
12Static interfaces force the eye to re-orient on every change; a short
13transition stitches the before and after into a single, understandable moment.`]}),`
14`,e.jsxs(d,{children:[e.jsx(i,{title:"Show cause and effect",children:e.jsx(n.p,{children:`Animate the result of an action so the user can connect what they did with
15what just happened. A panel sliding in from the right is recognisably "a
16thing that opened from this side."`})}),e.jsx(i,{title:"Preserve continuity",children:e.jsx(n.p,{children:`When a single object moves between two states (open ↔ closed, collapsed ↔
17expanded), animate the transition. Never let the object teleport.`})}),e.jsx(i,{title:"Stay out of the way",children:e.jsx(n.p,{children:`Animation should never delay the user's next action. If a transition makes
18the UI feel slow, shorten it or remove it.`})}),e.jsx(i,{title:"Skip pure decoration",children:e.jsx(n.p,{children:`If a motion answers no question and serves no state change, don't ship it.
19Subtle is the system's default — flair belongs to marketing surfaces, not
20the product.`})})]}),`
21`,e.jsx(t,{}),`
22`,e.jsx(n.h2,{id:"duration",children:e.jsx(n.a,{href:"#duration",children:"Duration"})}),`
23`,e.jsx(n.p,{children:`Compass exposes two duration tokens. Together they cover almost every motion
24in the product.`}),`
25`,e.jsxs(n.table,{children:[e.jsx(n.thead,{children:e.jsxs(n.tr,{children:[e.jsx(n.th,{children:"Token"}),e.jsx(n.th,{children:"Value"}),e.jsx(n.th,{children:"Use for"})]})}),e.jsxs(n.tbody,{children:[e.jsxs(n.tr,{children:[e.jsx(n.td,{children:e.jsx(n.code,{children:"--duration-quick"})}),e.jsx(n.td,{children:"150ms"}),e.jsx(n.td,{children:"On-screen micro-transitions — hover states, color changes, small moves"})]}),e.jsxs(n.tr,{children:[e.jsx(n.td,{children:e.jsx(n.code,{children:"--duration-moderate"})}),e.jsx(n.td,{children:"300ms"}),e.jsx(n.td,{children:"Entrances, exits, and larger movements — panels, drawers, popovers"})]})]})]}),`
26`,e.jsx(n.p,{children:`Quick is the default for elements already on screen. Reach for moderate
27whenever an element is entering or leaving the viewport, or travelling a
28significant distance — long enough that 150ms would feel rushed.`}),`
29`,e.jsx(t,{}),`
30`,e.jsx(n.h2,{id:"easing",children:e.jsx(n.a,{href:"#easing",children:"Easing"})}),`
31`,e.jsxs(n.p,{children:["Easing controls the ",e.jsx(n.strong,{children:"shape"}),` of a motion — whether it accelerates,
32decelerates, or moves at a constant rate. Compass uses three easing tokens,
33each tuned to a specific kind of moment.`]}),`
34`,e.jsxs(n.table,{children:[e.jsx(n.thead,{children:e.jsxs(n.tr,{children:[e.jsx(n.th,{children:"Token"}),e.jsx(n.th,{children:"Curve"}),e.jsx(n.th,{children:"Use for"})]})}),e.jsxs(n.tbody,{children:[e.jsxs(n.tr,{children:[e.jsx(n.td,{children:e.jsx(n.code,{children:"--ease-transition"})}),e.jsx(n.td,{children:"ease-in-out"}),e.jsx(n.td,{children:"Element already on screen — moving, expanding, changing color"})]}
34),e.jsxs(n.tr,{children:[e.jsx(n.td,{children:e.jsx(n.code,{children:"--ease-entrance"})}),e.jsx(n.td,{children:"ease-out"}),e.jsx(n.td,{children:"Element entering the screen — settles softly into place"})]}),e.jsxs(n.tr,{children:[e.jsx(n.td,{children:e.jsx(n.code,{children:"--ease-exit"})}),e.jsx(n.td,{children:"ease-in"}),e.jsx(n.td,{children:"Element leaving the screen — accelerates away"})]})]})]}),`
35`,e.jsx(n.p,{children:`The asymmetry between entrance and exit matters: things should arrive gently
36and leave decisively. Inverting that — sharp entrances, sluggish exits — makes
37the interface feel jittery, even when the timing is right.`}),`
38`,e.jsx(t,{}),`
39`,e.jsx(n.h2,{id:"choosing-a-duration--easing-pair",children:e.jsx(n.a,{href:"#choosing-a-duration--easing-pair",children:"Choosing a duration + easing pair"})}),`
40`,e.jsxs(n.p,{children:["Pick a token pair by asking two questions. ",e.jsx(n.em,{children:`Is the element already on screen,
41or is it entering or leaving?`})," And ",e.jsx(n.em,{children:"how far is it travelling?"})]}),`
42`,e.jsx(a,{}),`
43`,e.jsxs(n.p,{children:[`These four pairings cover the bulk of UI motion. When in doubt, default to
44`,e.jsx(n.code,{children:"--duration-quick"})," paired with ",e.jsx(n.code,{children:"--ease-transition"}),"."]}),`
45`,e.jsx(t,{}),`
46`,e.jsx(n.h2,{id:"popover-panels",children:e.jsx(n.a,{href:"#popover-panels",children:"Popover panels"})}),`
47`,e.jsxs(n.p,{children:[`Popovers — menus, info popovers, dropdowns — animate on open and close with a
48combined `,e.jsx(n.strong,{children:"scale + fade"}),`. The scale grows the panel out from its anchor; the
49fade prevents the appearance from feeling abrupt.`]}),`
50`,e.jsxs(n.table,{children:[e.jsx(n.thead,{children:e.jsxs(n.tr,{children:[e.jsx(n.th,{children:"Phase"}),e.jsx(n.th,{children:"Scale"}),e.jsx(n.th,{children:"Opacity"}),e.jsx(n.th,{children:"Duration"}),e.jsx(n.th,{children:"Easing"})]})}),e.jsxs(n.tbody,{children:[e.jsxs(n.tr,{children:[e.jsx(n.td,{children:"Opening"}),e.jsx(n.td,{children:"90% → 100%"}),e.jsx(n.td,{children:"0 → 1"}),e.jsx(n.td,{children:e.jsx(n.code,{children:"--duration-quick"})}),e.jsx(n.td,{children:e.jsx(n.code,{children:"--ease-entrance"})})]}),e.jsxs(n.tr,{children:[e.jsx(n.td,{children:"Closing"}),e.jsx(n.td,{children:"100% → 90%"}),e.jsx(n.td,{children:"1 → 0"}),e.jsx(n.td,{children:e.jsx(n.code,{children:"--duration-quick"})}),e.jsx(n.td,{children:e.jsx(n.code,{children:"--ease-exit"})})]})]})]}),`
51`,e.jsxs(n.p,{children:["Popovers are a short-distance entrance/exit, so they use ",e.jsx(n.code,{children:"--duration-quick"}),`
52rather than the moderate timing reserved for full-size panels and drawers.`]}),`
53`,e.jsxs(n.p,{children:["Set ",e.jsx(n.code,{children:"transform-origin"}
53),` so the scale grows from the anchor — for a popover that
54opens below-and-right of its trigger, use `,e.jsx(n.code,{children:"transform-origin: top left"}),`. For
55unmount-on-close panels, the `,e.jsx(n.code,{children:"useExitAnimation"})," hook in ",e.jsx(n.code,{children:"src/hooks/"}),` keeps the
56element rendered long enough for the exit animation to finish.`]}),`
57`,e.jsx(o,{}),`
58`,e.jsx(t,{}),`
59`,e.jsx(n.h2,{id:"always-use-the-tokens",children:e.jsx(n.a,{href:"#always-use-the-tokens",children:"Always use the tokens"})}),`
60`,e.jsx(n.p,{children:`Never hard-code durations or easing keywords directly. Bind to the CSS
61variables so timing stays consistent across components, and so changes to the
62system propagate without a search-and-replace.`}),`
63`,e.jsx(n.pre,{children:e.jsx(n.code,{className:"language-scss",children:`// Small on-screen transition (e.g. hover, short expand)
64transition: opacity var(--duration-quick) var(--ease-transition);
65
66// Large on-screen transition (e.g. panel sliding across the view)
67transition: transform var(--duration-moderate) var(--ease-transition);
68
69// Entrance
70transition: opacity var(--duration-moderate) var(--ease-entrance);
71
72// Exit
73transition: opacity var(--duration-moderate) var(--ease-exit);
74`})}),`
75`,e.jsx(t,{}),`
76`,e.jsx(n.h2,{id:"reduced-motion",children:e.jsx(n.a,{href:"#reduced-motion",children:"Reduced motion"})}),`
77`,e.jsxs(n.p,{children:["Respect the user's system preference. When ",e.jsx(n.code,{children:"prefers-reduced-motion: reduce"}),` is
78set, suppress non-essential animation so the interface stays usable for users
79who get motion-sick or are easily distracted by movement.`]}),`
80`,e.jsx(n.pre,{children:e.jsx(n.code,{className:"language-scss",children:`@media (prefers-reduced-motion: reduce) {
81  *,
82  *::before,
83  *::after {
84    animation-duration: 0.01ms !important;
85    transition-duration: 0.01ms !important;
86  }
87}
88`})}),`
89`,e.jsxs(n.p,{children:["Animation that ",e.jsx(n.strong,{children:"conveys meaning"}),` — a loading spinner, a progress indicator,
90a typing dot — can keep moving, but should be reduced to its minimum legible
91form. Animation that's purely a flourish should disappear.`]}),`
92`,e.jsx(t,{}),`
93`,e.jsx(n.h2,{id:"tokens-and-implementation",children:e.jsx(n.a,{href:"#tokens-and-implementation",children:"Tokens and implementation"})}),`
94`,e.jsxs(n.p,{children:[`Each duration and easing is exposed as a CSS custom property. For the full
95token list, copy-ready values, and interactive playback of each token, see the
96`,e.jsx(n.a,{href:"/foundations/animation/specimen",children:"Animation specimen page"}),"."]})]})}function x(s={}){const{wrapper:n}={...c(),...s.components};return n?e.jsx(n,{...s,children:e.jsx(h,{...s})}):h(s)}function r(s,n){throw new Error("Expected component `"+s+"` to be defined: you likely forgot to import, pass, or provide it.")}export{x as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.