Animation & motionCircular rotating text
Words in orbit
A quiet circular label turns around a stationary center.

Play to turn the circular label
Add to your project
Component source
Standalone React component. Styles are included.
React only. No additional packages.
Render the default export inside a container with a defined height. Its optional active prop stops movement when false. This example works with React 19; Next.js projects can use it as a client component.
View source code
/**
* Adapted from https://github.com/ibelick/motion-primitives/blob/120f64f6ca60348e251f929e9c81f11ccbe45eda/components/core/spinning-text.tsx
* Standalone composition; inline styles and artwork; bounded motion and reduced-motion behavior.
*
* MIT License
*
* Copyright (c) 2024 ibelick
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
"use client"
const text = "TAKE YOUR TIME · MAKE IT MATTER · "
export default function SpinningText({ active = true }: { active?: boolean }) {
return (
<div
style={{
width: "100%",
height: "100%",
minHeight: 280,
display: "grid",
gridTemplateColumns: "minmax(0, 1fr)",
placeItems: "center",
background: "#e9e3d2",
color: "#6a6142",
fontFamily: "inherit",
}}
>
<style>{`@keyframes sf-spinning-text-turn { to { transform: rotate(360deg); } } .sf-spinning-text-ring { animation: sf-spinning-text-turn 28s linear infinite; } @media (prefers-reduced-motion: reduce) { .sf-spinning-text-ring { animation: none; } }`}</style>
<div style={{ position: "relative", width: 194, height: 194 }}>
<div
aria-label={text}
className="sf-spinning-text-ring"
style={{ position: "absolute", inset: 0, animationPlayState: active ? "running" : "paused" }}
>
{Array.from(text, (letter, index) => (
<span
key={index}
aria-hidden="true"
style={{
position: "absolute",
left: "50%",
top: "50%",
display: "inline-block",
fontSize: 10,
fontWeight: 500,
transform: `translate(-50%, -50%) rotate(${(360 / text.length) * index}deg) translateY(-80px)`,
transformOrigin: "center",
}}
>
{letter}
</span>
))}
</div>
<svg
aria-hidden="true"
viewBox="0 0 80 80"
fill="none"
stroke="#9c8f60"
strokeWidth="1"
style={{ position: "absolute", width: 64, height: 64, top: 65, left: 65 }}
>
{[0, 45, 90, 135].map((angle) => (
<ellipse key={angle} cx="40" cy="40" rx="12" ry="29" transform={`rotate(${angle} 40 40)`} />
))}
<circle cx="40" cy="40" r="3" fill="#9c8f60" />
</svg>
</div>
</div>
)
}
Where it works well
Create a decorative seal, event identity or compact editorial accent.
Reduced motion
The full circular label remains in its authored position.
Things to know
Best for a short decorative phrase. Preserve the accessible full-text label and keep important copy in ordinary reading order.