shadcn.io is not affiliated with official shadcn/ui
Circular Text sets a line of text around a ring that turns slowly, like a stamp or a seal, with anything you like still in its middle: an arrow, a logo, a star. The ring is sized from the text, so the characters never crowd or gap, and the pointer over it can speed it up, slow it down or stop it.
Use it for a "scroll to explore" cue, a badge on a hero, or a seal on a pricing card. For text that follows any curve or path instead of a circle, use Curved Loop; for a spiral, Spiral Text. It's CSS only: each character is rotated to its place and the ring turned by a keyframe animation the install adds to your stylesheet, with no animation library. With reduced motion the ring holds still.
Installation
Usage
import { CircularText } from "@/components/ui/circular-text";<CircularText text="Scroll to explore • Scroll to explore • ">
<ArrowDownIcon aria-hidden="true" />
</CircularText>Demos
Hover
hover is what the pointer over the ring does: fast, the default, spins it four times as fast, slow halves its speed, pause stops it and none leaves it be. The speed changes from wherever the ring is, never jumping back to the start.
Seal
duration is one turn, 20 seconds by default. The ring takes its size from the text and the font size; --circular-text-spacing sets the room each character takes, here a little more for bold capitals on a badge.
Accessibility
Screen readers read the text once, in order, from a visually hidden copy, never the rotated characters. What sits in the middle is yours: give an icon aria-hidden="true" when the text already says what it means. With reduced motion the ring holds still.
FAQ
How big is the ring?
As big as the text needs: each character takes --circular-text-spacing, 0.75em by default, around the ring, so a longer text or a larger font makes a larger ring. Set the font size, or the spacing, to size it.
Why should the text end with a space or a separator?
The end of the text meets its start on the ring. A trailing space or • keeps the last word from running into the first; repeating a short phrase twice fills a ring evenly.
Why doesn't the ring jump when the speed changes?
The extra speed is a second spin inside the ring that only runs while the pointer is over it. Its turn adds to the ring's (or takes from it, for slow), so the ring carries on from where it is.
What can go in the middle?
Anything, as children: an icon, a logo, a short label or a number. It stays still while the ring turns around it.
API Reference
CircularText
<span> data-slot="circular-text"
textstring
The text around the ring; end it with a space or a separator (•) so the seam reads.
children?ReactNode
What sits still in the middle of the ring, such as an icon.
duration?number
One turn of the ring, in milliseconds. @default 20000.
- Default
20_000
hover?"fast" | "slow" | "pause" | "none"
What the pointer over the ring does: spins it four times as fast, half as fast, stops it, or nothing.
- Type
"fast" | "slow" | "pause" | "none"- Default
"fast"
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.
--circular-text-countstring--circular-text-durationstring
One turn of the ring (20s).
--circular-text-indexstring--circular-text-spacingstring
The space each character takes around the ring (0.75em).
Related
- Pro
Curved Loop
MotionText that follows any curve
- Pro
Spiral Text
MotionText that coils inward
- Pro
Scroll Velocity
MotionMarquee rows that move with the scroll
- Pro
Stamp Text
EntranceLetters pressed on like a seal
- Pro
Wave Text
MotionLetters that ride a wave
- Pro
Rolling Text
CyclingLetters that turn over in place