UI Components Easy
Spinning Text
Text arranged in a circle that rotates continuously using CSS transforms. Characters are positioned radially and the whole assembly spins with a smooth animation.
Open in Lab
MCP
css javascript vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0a0a0a;
color: #f1f5f9;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.spinning-text-wrapper {
position: relative;
width: 250px;
height: 250px;
}
.spinning-text {
position: absolute;
inset: 0;
animation: spin 10s linear infinite;
}
.spinning-text span {
position: absolute;
left: 50%;
top: 0;
font-size: 1.1rem;
font-weight: 600;
color: #e2e8f0;
transform-origin: 0 125px;
display: inline-block;
}
/* Center dot decoration */
.spinning-text-wrapper::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background: #a78bfa;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 20px #a78bfa, 0 0 40px rgba(167, 139, 250, 0.3);
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.spinning-text {
animation: none;
}
}
/* Subtle glow on letters */
.spinning-text span {
text-shadow: 0 0 8px rgba(167, 139, 250, 0.4);
}/**
* Spinning Text
* Splits a string into individual characters and positions each one
* around a circle. The CSS animation handles the continuous rotation.
*/
(function () {
const container = document.querySelector(".spinning-text");
if (!container) return;
const text = container.dataset.text || "SPINNING TEXT EFFECT * ";
const radius = parseInt(container.dataset.radius, 10) || 125;
function render() {
container.innerHTML = "";
const chars = text.split("");
const step = 360 / chars.length;
chars.forEach((char, i) => {
const span = document.createElement("span");
span.textContent = char;
span.style.transform = `rotate(${step * i}deg)`;
span.style.transformOrigin = `0 ${radius}px`;
container.appendChild(span);
});
}
render();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spinning Text</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="spinning-text-wrapper">
<div
class="spinning-text"
data-text="STEAL THIS COMPONENT * "
data-radius="125"
></div>
</div>
<script src="script.js"></script>
</body>
</html>import { useMemo } from "react";
interface SpinningTextProps {
text?: string;
radius?: number;
duration?: number;
}
export default function SpinningText({
text = "STEAL THIS COMPONENT * ",
radius = 125,
duration = 10,
}: SpinningTextProps) {
const chars = useMemo(() => {
const arr = text.split("");
const step = 360 / arr.length;
return arr.map((char, i) => ({ char, angle: step * i }));
}, [text]);
return (
<div
style={{
minHeight: "100vh",
background: "#0a0a0a",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
position: "relative",
width: radius * 2,
height: radius * 2,
}}
>
{/* Center dot */}
<div
style={{
position: "absolute",
top: "50%",
left: "50%",
width: 8,
height: 8,
background: "#a78bfa",
borderRadius: "50%",
transform: "translate(-50%, -50%)",
boxShadow: "0 0 20px #a78bfa, 0 0 40px rgba(167,139,250,0.3)",
}}
/>
{/* Spinning container */}
<div
style={{
position: "absolute",
inset: 0,
animation: `spinText ${duration}s linear infinite`,
}}
>
{chars.map(({ char, angle }, i) => (
<span
key={i}
style={{
position: "absolute",
left: "50%",
top: 0,
fontSize: "1.1rem",
fontWeight: 600,
color: "#e2e8f0",
transformOrigin: `0 ${radius}px`,
transform: `rotate(${angle}deg)`,
textShadow: "0 0 8px rgba(167,139,250,0.4)",
}}
>
{char}
</span>
))}
</div>
</div>
<style>{`
@keyframes spinText {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`}</style>
</div>
);
}<script setup>
import { computed } from "vue";
const props = defineProps({
text: { type: String, default: "STEAL THIS COMPONENT * " },
radius: { type: Number, default: 125 },
duration: { type: Number, default: 10 },
});
const chars = computed(() =>
props.text.split("").map((char, i) => ({
char,
angle: (360 / props.text.length) * i,
}))
);
</script>
<template>
<div class="demo">
<div class="spin-ring" :style="{ width: props.radius * 2 + 'px', height: props.radius * 2 + 'px' }">
<!-- Center dot -->
<div class="center-dot"></div>
<!-- Spinning container -->
<div class="spin-container" :style="{ animation: `spinText ${props.duration}s linear infinite` }">
<span
v-for="(c, i) in chars"
:key="i"
class="char"
:style="{ transformOrigin: `0 ${props.radius}px`, transform: `rotate(${c.angle}deg)` }"
>
{{ c.char }}
</span>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes spinText {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.demo {
min-height: 100vh;
background: #0a0a0a;
display: flex;
align-items: center;
justify-content: center;
}
.spin-ring {
position: relative;
}
.center-dot {
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background: #a78bfa;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 20px #a78bfa, 0 0 40px rgba(167,139,250,0.3);
}
.spin-container {
position: absolute;
inset: 0;
}
.char {
position: absolute;
left: 50%;
top: 0;
font-size: 1.1rem;
font-weight: 600;
color: #e2e8f0;
text-shadow: 0 0 8px rgba(167,139,250,0.4);
}
</style><script>
export let text = "STEAL THIS COMPONENT * ";
export let radius = 125;
export let duration = 10;
$: chars = text.split("").map((char, i) => ({
char,
angle: (360 / text.length) * i,
}));
</script>
<div class="demo">
<div class="spin-ring" style="width: {radius * 2}px; height: {radius * 2}px;">
<!-- Center dot -->
<div class="center-dot"></div>
<!-- Spinning container -->
<div class="spin-container" style="--spin-dur: {duration}s;">
{#each chars as { char, angle }, i}
<span
class="char"
style="transform-origin: 0 {radius}px; transform: rotate({angle}deg);"
>
{char}
</span>
{/each}
</div>
</div>
</div>
<style>
@keyframes spinText {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.demo {
min-height: 100vh;
background: #0a0a0a;
display: flex;
align-items: center;
justify-content: center;
}
.spin-ring {
position: relative;
}
.center-dot {
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
background: #a78bfa;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 20px #a78bfa, 0 0 40px rgba(167,139,250,0.3);
}
.spin-container {
position: absolute;
inset: 0;
animation: spinText var(--spin-dur) linear infinite;
}
.char {
position: absolute;
left: 50%;
top: 0;
font-size: 1.1rem;
font-weight: 600;
color: #e2e8f0;
text-shadow: 0 0 8px rgba(167,139,250,0.4);
}
</style>Spinning Text
Text characters arranged in a circle that rotates continuously. Each character is positioned using transform-origin and individual rotate transforms, while the container spins via a CSS @keyframes animation.
How it works
- The source string is split into individual characters.
- Each character is wrapped in a
<span>positioned absolutely at the center of the circle. - Every span is rotated by
(360 / charCount) * indexdegrees and pushed outward withtranslateY(-radius). - The parent element applies a continuous
rotateanimation.
Features
- Configurable text — pass any string
- Configurable radius — control circle size
- Pure CSS animation — GPU-accelerated rotation
- Respects reduced-motion — animation pauses when preferred