Motion Principles

We follow three principles for applying motion in the GoCardless product.

01
Purposeful

Every animation should earn its place by serving a function: confirming an action, guiding attention, communicating state, or smoothing a transition.

02
Efficient

Animation should feel fast. Users are here to get things done — nothing should linger. When in doubt, use a shorter duration.

03
Subtle

Motion should enhance the experience without drawing attention to itself. The product content is the hero, not the animation.

Primary Easing Curves

Our three core curves with their intended use-cases.

ease-smooth
cubic-bezier(0.5, 0, 0, 1)
  • Dialogs, modals & drawers
  • Step and page transitions
  • Progress bars & accordions
  • Default when uncertain
ease-slowdown
cubic-bezier(0, 0, 0.2, 1)
  • Hover states
  • Quick interactive feedback
  • Staggered list items & pills
  • Buttons & tooltips appearing
ease-expressive
cubic-bezier(0.4, 1.4, 0.2, 1.0)
  • Success states & confirmations
  • Badges & chips appearing
  • Scale and translate only
  • Positive moments only

Duration Tiers

Consistent timing creates predictable, polished animations.

Token Duration Usage
duration-xs 100ms Stagger delays only. Not for standalone transitions.
duration-sm 200ms Hover states, colour/border changes, simple state feedback.
duration-md 300ms Standard fades, small movements, tooltip appearances, button loading states.
duration-lg 400ms Modal/dialog entrances and exits, drawer slides, progress indicators.
duration-xl 600ms Accordions, content expansions with height, multi-property animations.
duration-xxl 1000ms Full-page and viewport-scale transitions.
Decorative animations (illustrations, Lottie, marquees) intentionally fall outside these tiers — see Decorative Animation below.

Examples

Live animations using the tokens above. Each loops when scrolled into view and pauses when out of view.

Email address
Password
Form focus
ease-slowdown · duration-sm
Tokens
TokenValueUsed for
ease-slowdowncubic-bezier(0, 0, 0.2, 1)Focus ring transition · cursor glide
duration-sm200msBorder colour + box-shadow on focus

Timing

  • 0msCursor appears above form card
  • 200msGlides to "Email address" field (480ms, ease-slowdown)
  • 780msField focused — border darkens, focus ring appears, caret blinks
  • 2600msGlides to "Password" field (420ms, ease-slowdown)
  • 3120msPassword focused; email blurs
  • 4900msCursor exits stage right (380ms)
  • 5050msPassword blurs
  • 6300msLoop
Sidebar entrance & exit
ease-smooth · duration-lg  →  ease-smooth · duration-md
Tokens
TokenValueUsed for
ease-smoothcubic-bezier(0.5, 0, 0, 1)Slide in · slide out (translateX)
duration-lg400msEntrance
duration-md300msExit

Timing

  • 0msSidebar slides in from left (translateX −100%→0, 400ms, ease-smooth)
  • 200msNav content fades in with slight translateX offset (200ms, ease-slowdown)
  • 400msSidebar fully visible; content area has shifted right
  • 3200msSidebar slides out to left (300ms, ease-smooth)
  • 3500msHidden; content area resets
  • 5000msLoop (CSS animation)
Confirm action
Dialog entrance & exit
ease-smooth · duration-lg  →  ease-smooth · duration-md
Tokens
TokenValueUsed for
ease-smoothcubic-bezier(0.5, 0, 0, 1)Slide up + fade in · slide down + fade out
duration-lg400msEntrance
duration-md300msExit

Timing

  • 0msDialog enters from below (translateY 20px→0, opacity 0→1, 400ms, ease-smooth) + scrim fades in
  • 400msDialog fully visible
  • 3200msDialog exits down (translateY 0→8px, opacity 1→0, 300ms, ease-smooth) + scrim fades out
  • 3500msHidden
  • 5000msLoop (CSS animation)
Tags
Stagger reveal
ease-slowdown · duration-md · 100ms delay increments
Tokens
TokenValueUsed for
ease-smoothcubic-bezier(0.5, 0, 0, 1)Card entrance · heading fade in
ease-slowdowncubic-bezier(0, 0, 0.2, 1)Each pill entrance (scale + opacity)
ease-standardcubic-bezier(0.4, 0, 0.2, 1)Card exit
duration-lg400msCard slide in
duration-md300msEach pill · card exit
100ms staggerDelay per pill (index × 100ms)

Timing

  • 0msCard slides up (translateY 16px→0, opacity 0→1, 400ms, ease-smooth)
  • 200ms"Tags" heading fades in (200ms, ease-smooth)
  • 400msPill 1 enters (scale 0.92→1, opacity 0→1, 300ms, ease-slowdown)
  • 500msPill 2 enters
  • Each subsequent pill 100ms after previous
  • 1200msPill 9 (last) starts entering
  • 1500msAll pills visible; hold for 1500ms
  • 3000msCard exits (translateY 0→8px, opacity 0, 300ms, ease-standard)
  • 4200msLoop
Page transition to success
ease-smooth · duration-xxl · Lottie tick
Tokens
TokenValueUsed for
ease-smoothcubic-bezier(0.5, 0, 0, 1)Page 1 & 2 enter/exit (translateX)
ease-slowdowncubic-bezier(0, 0, 0.2, 1)Cursor glide · button hover transition
ease-standardcubic-bezier(0.4, 0, 0.2, 1)Page 2 fade out
duration-xxl1000msPage enter/exit animations
200msButton background on hover

Timing

  • 0msPage 1 enters (translateX 96px→0, 1000ms, ease-smooth)
  • 1300msCursor appears top-right of stage
  • 1500msCursor glides to "Continue →" button (550ms, ease-slowdown)
  • 1800msButton hover: background → #374151 (200ms, ease-slowdown)
  • 2100msCursor presses (scale 0.82, 80ms)
  • 2200msButton enters loading state — label swaps for spinner
  • 2450msCursor fades out (300ms)
  • 3200msPage 1 exits left (translateX 0→−96px, 1000ms, ease-smooth)
  • 3700msPage 2 enters (1000ms, ease-smooth)
  • 3900msLottie tick plays
  • 7400msPage 2 fades out (400ms, ease-standard)
  • 8400msLoop
Mobile bottom drawer
ease-smooth · duration-lg  →  ease-smooth · duration-md
Tokens
TokenValueUsed for
ease-smoothcubic-bezier(0.5, 0, 0, 1)Drawer slide up · slide down (translateY)
duration-lg400msEntrance
duration-md300msExit

Timing

  • 0msNav tab tap — icon briefly scales down (100ms press, ease-in) and springs back
  • 0msScrim fades in (opacity 0→1, 400ms) · Drawer slides up (translateY 100%→0, 400ms, ease-smooth)
  • 400msDrawer fully visible; hold for ~2.8s
  • 3200msDrawer slides down (translateY 0→100%, 300ms, ease-smooth) · scrim fades out
  • 3500msHidden
  • 5000msLoop (CSS animation)
Accordion / expandable section
ease-smooth · duration-xl  →  ease-smooth · duration-lg
Tokens
TokenValueUsed for
ease-smoothcubic-bezier(0.5, 0, 0, 1)Height expand · collapse
ease-smoothcubic-bezier(0.5, 0, 0, 1)Chevron rotation
ease-slowdowncubic-bezier(0, 0, 0.2, 1)Content fade in (opacity)
ease-acceleratecubic-bezier(1, 0, 1, 1)Content fade out (opacity)
duration-xl600msEntrance — height + chevron
duration-lg400msExit — height + chevron
duration-md300msContent fade in · 80ms delay after height starts
duration-sm200msContent fade out · simultaneous with collapse

Timing

  • 0msAll items closed
  • 600msItem 1 opens — height expands (600ms, ease-smooth) · chevron rotates 180° · content fades in (300ms, ease-slowdown, 80ms delay)
  • 2800msItem 1 closes — content fades out (180ms) · height collapses (400ms, ease-smooth) · chevron reverses
  • 3800msItem 2 opens
  • 5800msItem 2 closes
  • 7000msLoop
Toast / notification
ease-smooth · duration-md  →  ease-accelerate · duration-sm
Tokens
TokenValueUsed for
ease-smoothcubic-bezier(0.5, 0, 0, 1)Entrance — slide from right + fade in
ease-acceleratecubic-bezier(1, 0, 1, 1)Exit — slide to right + fade out
duration-md300msEntrance
duration-sm200msExit — faster feels like a dismissal, not a departure

Timing

  • 0msAll toasts off-screen right
  • 600msToast 1 (success) slides in from right (300ms, ease-smooth)
  • 3400msToast 1 exits to right (200ms, ease-accelerate)
  • 4200msToast 2 (info) slides in
  • 7000msToast 2 exits
  • 8000msLoop

Notes

  • Exit uses ease-accelerate — the toast should feel dismissed, not elegantly departing
  • If a new toast arrives while one is visible, push existing toasts down with duration-sm / ease-smooth translate

Playground

Combine any easing curve with any duration and hit Play to see how the pairing feels.

Easing
Duration
Animation