Skip to content

Motion

Motion in SwiftAgency has a job: it sets reading order on load, confirms a hover or a filter, and shows that a page continues. It never decorates. Only transform, opacity and the font width axis animate, so nothing triggers layout, and there are no scroll listeners anywhere.

Pattern How to use it Where you see it
Load-in lines .mask-line around .intro-line Page headlines rise out of a mask
Load-in fade .intro-fade Intro copy and buttons after the headline
Curtain .intro-curtain on a positioned wrapper The home hero image is uncovered
Scroll reveal data-reveal, optionally in data-reveal-group Sections fade up once as they enter view
Width axis .stretch-host and .stretch Links and titles widen on hover
Cursor preview Built into <ProjectIndex> A frame follows the cursor over the list
Cursor-aware cards Built into the work grid Light and image drift follow the pointer
Scroll-driven hero CSS animation-timeline: view() Case study hero images
Marquee Built into <ClientMarquee> Client logos, pauses on hover

Stagger elements with the --i custom property. Each step adds 90ms.

<h1>
<span class="mask-line">
<span class="intro-line" style="--i:0">
Work that
</span>
</span>
<span class="mask-line">
<span class="intro-line" style="--i:1">
ships.
</span>
</span>
</h1>
<p class="intro-fade" style="--i:0">
Intro copy follows the headline.
</p>
<ul data-reveal-group>
<li data-reveal>One</li>
<li data-reveal>Two</li>
<li data-reveal>Three</li>
</ul>

Children of a data-reveal-group are staggered automatically. Without JavaScript nothing is hidden: the hidden starting state only applies once the script has added the js class to <html>.

When a visitor has asked their system for reduced motion (prefers-reduced-motion: reduce):

  • Every animation and transition settles to its final state immediately.
  • Scroll reveals, the curtain and load-in lines don’t play; content is simply there.
  • The marquee becomes a static row, and the cursor effects stop moving.
  • The width axis stays at rest on hover.

This is handled once in global.css, so your own components inherit it. If you write a new animation, wrap it in @media (prefers-reduced-motion: no-preference) the way the built-in ones are.

Use the motion tokens: duration-fast for colour and hover, duration-base for reveals and menus, duration-slow for load-in. Things that arrive use ease-out; things that loop or cover use ease-in-out.

SwiftAgency is made byLessCodexLicence