Spatial typography composition where satellite labels orbit a focal phrase with 3D perspective depth, counter-rotation legibility, and static layout mode.
Preview
MotionFoundry Core
React 19TypeScriptMotion 14CSS ModulesCLI RegistryMIT License
Customize
5 props108
18
22
Installation
Copies the component source and its CSS Module into your project.
npx motionfoundry add orbiting-wordsWrites components/ui/orbiting-words.tsx and components/styles/orbiting-words.module.css.
Usage
Updates live as you change the controls — only props that differ from the defaults are included.
example.tsx
1import { OrbitingWords } from "@motionfoundry/ui";2 3export function Example() {4 return (5 <OrbitingWords />6 );7}Props
Accessibility
Renders a semantic list of orbiting terms for assistive technology and switches automatically to a clean radial static badge layout when mode='static' or prefers-reduced-motion is enabled.
Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.
Source
components/ui/orbiting-words.tsx
1"use client";2 3import * as React from "react";4import styles from "../styles/orbiting-words.module.css";5 6export interface OrbitingWordsProps {7 /** Focal phrase in the center */8 centerPhrase?: string;9 /** Satellite labels orbiting the center */10 words?: string[];11 /** Orbit radius in pixels */12 radius?: number;13 /** Seconds per full revolution */14 duration?: number;15 /** Animated or static layout mode */16 mode?: "animated" | "static";17 /** Perspective pitch angle in degrees */18 tilt?: number;19 /** Optional className */20 className?: string;21}22 23const DEFAULT_WORDS = [24 "React 19",25 "TypeScript",26 "Motion 14",27 "CSS Modules",28 "CLI Registry",29 "MIT License",30];31 32export function OrbitingWords({33 centerPhrase = "MotionFoundry Core",34 words = DEFAULT_WORDS,35 radius = 108,36 duration = 18,37 mode = "animated",38 tilt = 22,39 className = "",40}: OrbitingWordsProps) {41 const safeWords = words.length > 0 ? words : DEFAULT_WORDS;42 const isStatic = mode === "static";43 44 return (45 <div46 style={47 {48 "--mf-orbit-radius": `${radius}px`,49 "--mf-orbit-duration": `${duration}s`,50 "--mf-orbit-tilt": `${tilt}deg`,51 } as React.CSSProperties52 }53 className={`${styles.stage} ${className}`.trim()}54 >55 <div className={styles.centerNode}>{centerPhrase}</div>56 <div57 aria-label={`Satellite capabilities: ${safeWords.join(", ")}`}58 className={`${styles.ring} ${isStatic ? styles.ringStatic : styles.ringAnimated}`}59 >60 {safeWords.map((word, idx) => {61 const angle = (360 / safeWords.length) * idx;62 const rad = (angle * Math.PI) / 180;63 const x = Math.cos(rad) * radius;64 const y = Math.sin(rad) * radius * 0.62;65 return (66 <span67 key={`${word}-${idx}`}68 style={{69 transform: `translate3d(${x.toFixed(1)}px, ${y.toFixed(1)}px, 0)`,70 }}71 className={`${styles.wordTag} ${isStatic ? "" : styles.counterRotate}`.trim()}72 >73 {word}74 </span>75 );76 })}77 </div>78 </div>79 );80}