shadcn.io is not affiliated with official shadcn/ui
Curved Loop runs a line of text along a curve, a gentle wave by default or any path you draw, and keeps it flowing like a marquee: the text is repeated to fill the curve and loops without a seam. It pauses while the pointer is over it, and the text can be grabbed and pulled along, either way.
Use it for a band between sections, a playful hero or a tagline that should move. For straight rows that answer the page's scroll, use Scroll Velocity; for text around a ring, Circular Text. It's SVG: the text is set along the path, so every letter keeps its own width and turns with the curve, and an SVG animation slides it along, with no animation library. A small script measures one copy of the text so the loop meets itself exactly. With reduced motion the text holds still.
Installation
Usage
import { CurvedLoop } from "@/components/ui/curved-loop";<CurvedLoop separator="✦" text="Curved Loop" />Demos
Path
path is the curve, as SVG path data drawn in the viewBox, 0 0 1000 200 by default: the drawing scales to the width, text and all. Start and end the path past the box's sides, so the text comes in and goes out of sight, and leave room around it for the letters, which sit centred on the line. speed is the time the text takes to move by one character, 250 ms by default.
Direction
direction is which way the text flows, left by default. The separator is its own part (data-slot="curved-loop-separator"), so it can take a colour of its own, here the primary.
Accessibility
Screen readers read the text once, from a visually hidden copy; the drawing, its repeats and its separators are hidden from them. The text stops while the pointer is over it, for anyone reading slowly. Pulling it along is an extra for the pointer: it only moves decoration, so the keyboard needs no way to do it. With reduced motion the text holds still.
FAQ
How do I size the text?
With a font size class, text-6xl by default, in the viewBox's units: the whole drawing scales to the width, so the text keeps its size against the curve on every screen. In the default box, 1000 units wide, text-6xl is 60 units, six per cent of the width.
How does the loop never show a seam?
One copy of the text, with its separator, is measured, and the text is repeated to fill the path with a copy to spare. The animation slides it back by exactly one copy and starts again, which looks just as it did. It's measured again if the font changes, such as when a web font arrives.
How does pulling the text work?
Press on it and drag sideways: the text follows the pointer along the curve, either way, and flows on from there when you let go. On a touch screen, a sideways swipe pulls it and an upright one scrolls the page. Pass drag={false} to turn it off.
Can the path be any shape?
Any open path: an arc, a wave, a zigzag, a line that climbs. Start and end it outside the box, where the text appears and disappears. A closed shape shows the point where its ends meet; for text round a circle, use Circular Text.
Why SVG text rather than CSS motion paths?
CSS motion paths would place each letter at a fixed step, so narrow and wide letters space unevenly, and their coordinates don't scale with the box. SVG text on a path keeps each letter's real width and kerning and scales with the drawing, and an SVG animation moves it without a frame loop.
API Reference
CurvedLoop
<span> data-slot="curved-loop"
textstring
The text, repeated along the curve.
direction?"left" | "right"
Which way the text flows along the curve. @default "left".
- Type
"left" | "right"- Default
"left"
drag?boolean
Whether the text can be grabbed and pulled along the curve. @default true.
- Default
true
path?string
The curve the text runs along, as SVG path data in the viewBox's
units. Start and end it past the box's sides, so the text comes in and
goes out of sight. By default a gentle wave.
- Default
WAVE
separator?string
Drawn between one copy of the text and the next, such as ✦ or •.
speed?number
The time the text takes to move by one character, in milliseconds; lower is faster.
- Default
250
viewBox?string
The box the path is drawn in; the drawing scales to the width. @default "0 0 1000 200".
- Default
"0 0 1000 200"
render?ReactElement | ((props: object, state: object) => ReactElement)
Replace the host element or compose it with another component.
- Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string
Merged with the part's classes.
Related
- Pro
Circular Text
MotionA ring that turns, for a badge
- Pro
Scroll Velocity
MotionStraight rows that move with the scroll
- Pro
Spiral Text
MotionText coiled into a spiral
- Pro
Wave Text
MotionLetters riding a swell in place
- Pro
Dot Matrix Text
DecorativeA ticker lit on a grid of dots
- Pro
Waveform Text
MotionA wave running through like sound