Dot Rail

LED Dot Tab Indicator for React

A strip of LED dots that shows where you are. A run of lit dots jumps under the tab or link you point at, counts up under an autoplaying tab like a timer, sits at how far you've read, or loops while something loads. The dots it leaves glow for a moment and fade, like an LED sign, so a quick move draws a short comet tail.

  • Navigation
  • Progress
  • Canvas

Uses motion, clsx, tailwind-merge

Inspired by Citylines on vibrant.design

Open in ChatGPTOpen in Claude
rows3
pitch6px
trail260ms
stiffness900
followStiffness420
<DotRail />

The demo follows as you drag.

Meetings that finish themselves
It joins the call so nobody has to type

Minutes sits in on Zoom, Meet and Teams, and tells everyone it's there.

Recording · Q4 planning24:13
ATMJ
6 people, 2 rooms
The decisions, not the transcript

Thirty minutes of talk comes back as five lines, with links to the moments behind them.

Summary · 3 decisions

  • Ship the export redesign on Nov 18, not Dec 2. 04:12
  • Pricing stays as is until the January review. 11:40
  • Support hires two more people in Lisbon. 19:05
Every promise gets an owner

“I'll send that over” becomes a task with a name and a date on it.

  • Send the export spec to designAnaThu
  • Draft the January pricing memoTheoNov 29
  • Open two Lisbon support rolesMikaMon
The recap is written before you're back at your desk

Review it, change a word, and send it to everyone who was there.

To 6 people from Q4 planning

Subject Recap: export date, pricing, Lisbon

Thanks all. We moved the export redesign up to Nov 18 and kept pricing as is until January. Three owners below.

Drafted 2 min after the callSend recap
Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Dot Rail: A strip of LED dots that shows where you are. A run of lit dots jumps under the tab or link you point at, counts up under an autoplaying tab like a timer, sits at how far you've read, or loops while something loads. The dots it leaves glow for a moment and fade, like an LED sign, so a quick move draws a short comet tail.
This demo, Feature Tabs: An AI note-taker's four features as tabs that play by themselves. The dots under the current tab count up like a timer, the rail jumps to any tab you point at, and picking one stops the autoplay.

1. Install it:
   npx shadcn@latest add @keyframeui/dot-rail-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/dot-rail-demo.json instead.)
   It adds the demo, components/dot-rail-demo.tsx, and the component, components/ui/dot-rail.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <DotRail> on its own.

3. These props set its feel. Change them before you change the file:
   - rows (default 3): Rows of dots in the band.
   - pitch (default 6): Distance between dot centers, in px. The rail is `rows × pitch` tall.
   - trail (default 260): How long, in ms, a dot keeps glowing after the run leaves it. `0` turns the tail off.
   - stiffness (default 900): Spring stiffness of the end that leads the run when it moves, critically damped so it never overshoots. Higher is snappier.
   - followStiffness (default 420): Spring stiffness of the end that catches up behind it. Lower than `stiffness` and the run stretches toward where it's going and gathers up behind itself.

4. Don't change, in components/ui/dot-rail.tsx:
   - One canvas and one rAF loop draw the rail. A frame redraws only the columns whose brightness step changed, and the loop stops once nothing moves or fades, off screen and on a hidden tab.
   - A lit dot's halo stays inside its own cell, so redrawing one column never clips a neighbor.
   - Each end of the run is its own critically damped spring: the end that grows the run uses `stiffness`, the end that shrinks it `followStiffness`, so the run stretches toward its target.
   - A dot is on or off by whether its column is inside the run, never half lit by position; only its fade after the run leaves is gradual, an exponential that's out by `trail`.
   - A first target and any relayout land without travel, already lit. Under reduced motion every target does, with no tail, and the loop parks mid-rail and breathes.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/dot-rail.md

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/dot-rail.tsx and installs motion, clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/dot-rail

Examples

Finished sections built with Dot Rail. Each one installs whole, with the component included.

Site Header

A print studio's header. The rail under it runs to whichever link you point at and parks under the current page when you leave.

Small runs, loud colors

Zines, posters and lookbooks printed a hundred at a time on two riso drums, in a back room off Calder Street.

Riso · 2 drums · 11 inks

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Dot Rail: A strip of LED dots that shows where you are. A run of lit dots jumps under the tab or link you point at, counts up under an autoplaying tab like a timer, sits at how far you've read, or loops while something loads. The dots it leaves glow for a moment and fade, like an LED sign, so a quick move draws a short comet tail.
This demo, Site Header: A print studio's header. The rail under it runs to whichever link you point at and parks under the current page when you leave.

1. Install it:
   npx shadcn@latest add @keyframeui/dot-rail-header-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/dot-rail-header-demo.json instead.)
   It adds the demo, components/dot-rail-header-demo.tsx, and the component, components/ui/dot-rail.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <DotRail> on its own.

3. These props set its feel. Change them before you change the file:
   - rows (default 3): Rows of dots in the band.
   - pitch (default 6): Distance between dot centers, in px. The rail is `rows × pitch` tall.
   - trail (default 260): How long, in ms, a dot keeps glowing after the run leaves it. `0` turns the tail off.
   - stiffness (default 900): Spring stiffness of the end that leads the run when it moves, critically damped so it never overshoots. Higher is snappier.
   - followStiffness (default 420): Spring stiffness of the end that catches up behind it. Lower than `stiffness` and the run stretches toward where it's going and gathers up behind itself.

4. Don't change, in components/ui/dot-rail.tsx:
   - One canvas and one rAF loop draw the rail. A frame redraws only the columns whose brightness step changed, and the loop stops once nothing moves or fades, off screen and on a hidden tab.
   - A lit dot's halo stays inside its own cell, so redrawing one column never clips a neighbor.
   - Each end of the run is its own critically damped spring: the end that grows the run uses `stiffness`, the end that shrinks it `followStiffness`, so the run stretches toward its target.
   - A dot is on or off by whether its column is inside the run, never half lit by position; only its fade after the run leaves is gradual, an exponential that's out by `trail`.
   - A first target and any relayout land without travel, already lit. Under reduced motion every target does, with no tail, and the loop parks mid-rail and breathes.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/dot-rail.md

Reading Progress

A long read whose rail sits at how far you've scrolled. Scroll fast and the tail stretches out behind it.

Tram & Tide · No. 146 min left
The night bus remembers the tram

By Ines Okafor · Photographs by the author

Ride the N4 out of Varn after midnight and, somewhere past the fish market, the bus does something no timetable explains. It slows, swings wide around a corner nobody needs to swing wide around, and runs the next half mile down the middle of the road, as if it were keeping to a line painted there.

It is. The paint has been gone for fifty years, but the line hasn't. The N4 follows the old number 4 tram, rail for rail, because when the trams stopped in 1974 the city gave their routes to the buses and never took them back. The drivers learned the corners from the tram men, and the next drivers learned them from those.

You can read the whole network this way. The night lines bend where the depots were. They stop at shelters that were tram halts, under iron roofs too long for a single bus. The 11 still waits two minutes at Harbour Gate, which made sense when the tram had to change its pole there and makes none now.

“A faster route that nobody can picture is a worse route at two in the morning.”
Eleven minutes faster

Planners have tried to straighten them. In 2009 a consultancy redrew the night map from scratch, with routes chosen by where people actually live now. It was faster on paper by eleven minutes a trip. Ridership fell by a fifth in the first winter, and most of the old lines were back by spring.

The drivers had a theory about that, and it holds up. A night bus isn't only carrying people home; it's a lit room moving through a dark city on a path everyone already knows. People time their walk to the stop by the old tram bell at St. Anne's. Bakers set their ovens by the first 4 going past. A faster route that nobody can picture is a worse route at two in the morning.

So the city stopped fighting it. The new shelters going up along the 4 are built to the old halt length, with room for a tram that isn't coming, and the depot at Calder Street, the last one standing, is being turned into a bus garage that keeps its rails in the floor.

Ride it once with this in mind and you won't unsee it: the long, unhurried bend at the market, the pause at Harbour Gate, the stretch down the middle of the road. The bus isn't lost. It's remembering.

End · Filed from the 4, 01:40

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Dot Rail: A strip of LED dots that shows where you are. A run of lit dots jumps under the tab or link you point at, counts up under an autoplaying tab like a timer, sits at how far you've read, or loops while something loads. The dots it leaves glow for a moment and fade, like an LED sign, so a quick move draws a short comet tail.
This demo, Reading Progress: A long read whose rail sits at how far you've scrolled. Scroll fast and the tail stretches out behind it.

1. Install it:
   npx shadcn@latest add @keyframeui/dot-rail-reading-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/dot-rail-reading-demo.json instead.)
   It adds the demo, components/dot-rail-reading-demo.tsx, and the component, components/ui/dot-rail.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <DotRail> on its own.

3. These props set its feel. Change them before you change the file:
   - rows (default 3): Rows of dots in the band.
   - pitch (default 6): Distance between dot centers, in px. The rail is `rows × pitch` tall.
   - trail (default 260): How long, in ms, a dot keeps glowing after the run leaves it. `0` turns the tail off.
   - stiffness (default 900): Spring stiffness of the end that leads the run when it moves, critically damped so it never overshoots. Higher is snappier.
   - followStiffness (default 420): Spring stiffness of the end that catches up behind it. Lower than `stiffness` and the run stretches toward where it's going and gathers up behind itself.

4. Don't change, in components/ui/dot-rail.tsx:
   - One canvas and one rAF loop draw the rail. A frame redraws only the columns whose brightness step changed, and the loop stops once nothing moves or fades, off screen and on a hidden tab.
   - A lit dot's halo stays inside its own cell, so redrawing one column never clips a neighbor.
   - Each end of the run is its own critically damped spring: the end that grows the run uses `stiffness`, the end that shrinks it `followStiffness`, so the run stretches toward its target.
   - A dot is on or off by whether its column is inside the run, never half lit by position; only its fade after the run leaves is gradual, an exponential that's out by `trail`.
   - A first target and any relayout land without travel, already lit. Under reduced motion every target does, with no tail, and the loop parks mid-rail and breathes.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/dot-rail.md

Props

value

Typenumber | [number, number]

Where the run sits: a number from 0 (the left end) to 1 (the right end), or a [from, to] range to light, such as [0, 1] for the whole rail. follow and a hovered trackRef item take precedence.

follow

Type"scroll" | "loop"

Let the run place itself: "scroll" sits it at the scroll progress of the page (or containerRef), "loop" sends it round the rail on its own, like a loader. With no follow, value or trackRef, it loops.

containerRef

TypeReact.RefObject<HTMLElement | null>

The scrolling element whose progress follow="scroll" reads. Defaults to the page.

trackRef

TypeReact.RefObject<HTMLElement | null>

A nav, tab list or menu to follow: the run jumps under whichever of its links or buttons is hovered or focused, as wide as it is, and goes back to the one marked aria-current (or data-active, or aria-selected) when the pointer leaves.

fill

TypenumberDefault1

With trackRef, how much of the current item is lit, from its left edge, from 0 to 1. Drive it from a timer and the dots under an autoplaying tab count up one by one.

rows

TypenumberDefault3

Rows of dots in the band.

pitch

TypenumberDefault6

Distance between dot centers, in px. The rail is rows × pitch tall.

length

TypenumberDefault6

How many dots wide the run is when it's a position (a number value, scroll or loop).

trail

TypenumberDefault260

How long, in ms, a dot keeps glowing after the run leaves it. 0 turns the tail off.

stiffness

TypenumberDefault900

Spring stiffness of the end that leads the run when it moves, critically damped so it never overshoots. Higher is snappier.

followStiffness

TypenumberDefault420

Spring stiffness of the end that catches up behind it. Lower than stiffness and the run stretches toward where it's going and gathers up behind itself.

className

Typestring

Extra classes for the rail. Its text color draws the dots, at 30%; set the lit run's color with --dot-rail-accent (default #ff5a1f), e.g. [--dot-rail-accent:#2563eb].