ProUnlock everything for $49.99 a year, or $79.99 once. See plans ›

Nexus UI

Reading Spine, animated React component

A scroll bar that knows the article. A slim rail carries a tick where each section starts and a capsule for your place. Scroll or scrub it and a glass label rides alongside with the section and the minutes left, like the Photos scrubber. Rest on the rail for a glass contents panel showing how much of each section you have read.

  • Pro
  • Blog Components
  • Scroll
  • Spring

Play with it

Move the controls and watch it change. Copy code gives you a snippet with only the props you changed.

Essay

Motion that explains itself

2 min read

Why motion

Motion is the part of an interface that explains itself. When a sheet slides up from the edge you tapped, you know where it came from and where it will go when you dismiss it. Nobody had to write that down.

Most of the motion we ship does not do this. It decorates. A card fades in because cards fade in, a button bounces because the library makes bouncing cheap. Decoration is not wrong, but it spends attention without paying any back.

Timing

Durations are a guess about how far something travels and how much it matters. Short distances want short times; anything under a hundred milliseconds reads as instant, anything over four hundred starts to feel like waiting.

Springs remove the guess. Give an element a stiffness and a damping and it finds its own duration from the distance it has to cover. Interrupt it halfway and it turns around from where it is, carrying its speed, instead of snapping back to the start.

That last part matters more than it sounds. People change their minds mid-gesture all the time, and an interface that can follow them feels like it is listening.

Continuity

An element that moves should stay the same element. If a photo in a grid becomes the photo in a detail view, it should travel there, keeping its corners and its crop as it grows, so the eye never has to search for it.

Breaks in continuity are where people get lost. A list that re-sorts by vanishing and reappearing asks the reader to compare two pictures. A list that slides each row to its new place lets them watch it happen.

Restraint

The best motion is usually the motion you cut. If a transition does not tell anyone something they would otherwise have to work out, it is a candidate for removal.

Respect the setting that asks for less motion. Replace travel with a crossfade, keep the information, drop the flourish.

A short checklist

Does it start from where the person acted? Can it be interrupted? Does it end where the thing now lives? Would anyone miss it if it were gone?

If the answers are yes, yes, yes and yes, keep it. Otherwise, cut it and see if anyone notices.

Props

PropTypeDefaultWhat it does
autoplayfalseScrolls the article by itself so the label and contents panel can be seen, until the visitor touches it. Never runs under reduced motion and pauses while the card is off screen.
sectionsa sample articleThe sections of the article. Each body string is one paragraph.

Add it to your project

  1. 1

    Set up your project once

    You need a Next.js project with Tailwind CSS and the @/ import alias. In its root folder, run this once. It creates components.json and the cn() helper in src/lib/utils.ts:

    npx shadcn@latest init -d

    Starting from nothing? The Quick Start walks through the whole thing.

  2. 2

    Connect the registry with your API key

    Reading Spine is a Pro item, so the CLI has to send your API key. Do this once per project:

    1. Create a key at nexus-ui.com/dashboard/api-key. Copy it straight away: it starts with nxs_ and is shown only once.
    2. In your project root, next to package.json, create a file named .env.local with this line, using your own key:
      NEXUS_API_KEY=nxs_your_key_here
    3. Open components.json and add this entry inside the outermost braces, next to "aliases". Leave {name} and ${NEXUS_API_KEY} exactly as written; the CLI fills them in:
      "registries": {
        "@nexus-labs": {
          "url": "https://www.nexus-ui.com/api/registry/{name}",
          "headers": {
            "Authorization": "Bearer ${NEXUS_API_KEY}"
          }
        }
      }