Spinning Text

Text laid out in a circle that rotates continuously.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/spinning-text.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

2 stories available:

Code

import * as React from "react"; import { cn } from "../../lib/utils"; /** Props for {@link SpinningText}. */ export type SpinningTextProps = React.ComponentPropsWithoutRef<"div"> & { /** Text laid out around the ring. */ children: string; /** Seconds for one full revolution. Defaults to `20`. */ duration?: number; /** Radius of the text ring in pixels. Defaults to `80`. */ radius?: number; /** Spin counter-clockwise when true. Defaults to `false`. */ reverse?: boolean; }; function splitCharacters(value: string): string[] { return value.match(/[\s\S]/gu) ?? []; } function characterStyle( index: number, total: number, radius: number, ): React.CSSProperties { return { left: "50%", position: "absolute", top: "50%", transform: `rotate(${(360 / total) * index}deg) translateY(${-radius}px)`, transformOrigin: "0 0", }; } /** * Lays a string evenly around a circle and rotates the ring on a loop. * * Respects `prefers-reduced-motion`: the layout holds but the spin stops. * * @example * ```tsx * <SpinningText>vllnt design system</SpinningText> * ``` */ export const SpinningText = ({ children, className, duration = 20, radius = 80, ref, reverse = false, style, ...props }: SpinningTextProps & { ref?: React.Ref<HTMLDivElement> }) => { const characters = splitCharacters(children); return ( <div aria-label={children} className={cn( "relative animate-spin motion-reduce:animate-none", className, )} ref={ref} style={{ animationDirection: reverse ? "reverse" : "normal", animationDuration: `${duration}s`, height: radius * 2, width: radius * 2, ...style, }} {...props} > {characters.map((character, index) => ( <span aria-hidden="true" key={`${character}-${index.toString()}`} style={characterStyle(index, characters.length, radius)} > {character} </span> ))} </div> ); }; SpinningText.displayName = "SpinningText";

Dependencies

  • @vllnt/ui@^0.2.1