Zum Inhalt springen
UI Kit
Accent

Umlaufbahn

Icons, Logos oder Avatare, die auf Ringen um eine Mitte kreisen und dabei aufrecht bleiben.

Eine von 6 Components in Beams, Partikel und Bewegung

3 Variants

Variants

Integrationen

Zwei Ringe drehen gegeneinander um dein Zeichen, neben dem Text. Scrollen gibt ihnen einen zusätzlichen Schwung.

Halo hinter dem Text

Weite gestrichelte Ringe um die Überschrift, die Elemente ziehen an den Rändern vorbei. Auf dem Telefon bleiben nur die Ringe.

Menschen

Avatare auf zwei Ringen über dem Text, für eine Gemeinschaft oder ein Team.

Details

Was Umlaufbahn mitbringt, aus Registry und Quellcode gelesen.

Bereich
Elements
Variants
3
Version
1.0.0 · seit 1.0.0
Datei
components/ui/orbit.tsx
Bringt mit
utilskit
npm-Pakete
Keine
Sprachen
Texte kommen über Props
Studio
Im Code anpassen

Props

Die Schnittstelle OrbitProps aus components/ui/orbit.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.

  • ringsPflicht
    OrbitRing[]
  • centeroptional
    React.ReactNode

    What sits in the middle, e.g. your app mark

  • sizeoptional
    number

    Largest size in px, the orbit shrinks with its container below that. Default 520

  • showRingsoptional
    boolean

    Draw the rings as hairlines, default true

  • dashedoptional
    boolean

    Dashed rings instead of solid

  • scrollTurnoptional
    number

    Degrees the rings turn on top of their run over a full scroll pass, items stay upright. Default 0 (off), 60 reads well.

  • labeloptional
    string

    Read out to screen readers, e.g. "Connects with 12 tools". Without it the orbit is decorative.

Barrierefreiheit

Aus dem Quellcode der Component abgeleitet.

  • Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.

Bewegung

  • Bewegt sich mit dem Scrollen, der Fortschritt folgt der Position auf der Seite.
  • Bringt eigene Animationen mit, ruhig getaktet und ohne Wackeln.
  • Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig.

Quellcode

Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.

Datei anzeigen, 125 Zeilen
components/ui/orbit.tsx
'use client';

import { useEffect, useRef } from 'react';
import { cn, prefersReducedMotion, round } from '@/lib/kit';

export interface OrbitRing {
  /** Icons, logos or avatars, spread evenly around the ring */
  items: React.ReactNode[];
  /** Ring radius in % of the orbit size, 0 to 50 */
  radius: number;
  /** Seconds for one full turn, default 40 */
  duration?: number;
  /** Turn counterclockwise */
  reverse?: boolean;
  /** Start angle of the first item in degrees, default -90 (top) */
  offset?: number;
}

export interface OrbitProps {
  rings: OrbitRing[];
  /** What sits in the middle, e.g. your app mark */
  center?: React.ReactNode;
  /** Largest size in px, the orbit shrinks with its container below that. Default 520 */
  size?: number;
  /** Draw the rings as hairlines, default true */
  showRings?: boolean;
  /** Dashed rings instead of solid */
  dashed?: boolean;
  /** Degrees the rings turn on top of their run over a full scroll pass, items stay upright. Default 0 (off), 60 reads well. */
  scrollTurn?: number;
  /** Read out to screen readers, e.g. "Connects with 12 tools". Without it the orbit is decorative. */
  label?: string;
  className?: string;
}

const CSS = `
@keyframes kitfx-orbit-spin { to { transform: rotate(360deg); } }
.kitfx-orbit * { animation-play-state: var(--kitfx-play, running); }
.kitfx-orbit-turn { rotate: calc((var(--kitfx-p, 0.5) - 0.5) * var(--kitfx-turn)); transition: rotate .3s linear; }
.kitfx-orbit-upright { rotate: calc((var(--kitfx-p, 0.5) - 0.5) * var(--kitfx-turn) * -1); transition: rotate .3s linear; }
@media (prefers-reduced-motion: reduce) { .kitfx-orbit * { animation: none !important; } .kitfx-orbit-turn, .kitfx-orbit-upright { rotate: none; } }
`;

/**
 * Items circling a center on one or more rings. Items stay upright while
 * their ring turns. Each item wrapper carries data-orbit-item, e.g. to hide
 * the items on phones with max-sm:[&_[data-orbit-item]]:hidden. Square, responsive up to `size`. Pauses out of view and
 * stands still with reduced motion.
 */
export function Orbit({ rings, center, size = 520, showRings = true, dashed, scrollTurn = 0, label, className }: OrbitProps) {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const el = ref.current;
    if (!el || typeof IntersectionObserver === 'undefined') return;
    const io = new IntersectionObserver(([e]) => el.style.setProperty('--kitfx-play', e.isIntersecting ? 'running' : 'paused'));
    io.observe(el);
    return () => io.disconnect();
  }, []);
  // Scroll pass: 0 when the orbit enters at the bottom, 1 when it leaves at the top
  useEffect(() => {
    const el = ref.current;
    if (!el || !scrollTurn || prefersReducedMotion()) return;
    let frame = 0;
    const update = () => {
      frame = 0;
      const r = el.getBoundingClientRect();
      el.style.setProperty('--kitfx-p', Math.min(1, Math.max(0, (window.innerHeight - r.top) / (r.height + window.innerHeight))).toFixed(4));
    };
    const onScroll = () => {
      if (!frame) frame = requestAnimationFrame(update);
    };
    update();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => {
      cancelAnimationFrame(frame);
      window.removeEventListener('scroll', onScroll);
    };
  }, [scrollTurn]);

  return (
    <div
      ref={ref}
      {...(label ? { role: 'img', 'aria-label': label } : { 'aria-hidden': true })}
      className={cn('kitfx-orbit relative mx-auto aspect-square w-full', className)}
      style={{ maxWidth: size, ...(scrollTurn ? { ['--kitfx-turn' as string]: `${scrollTurn}deg` } : {}) }}
    >
      <style dangerouslySetInnerHTML={{ __html: CSS }} />
      {showRings &&
        rings.map((r, i) => (
          <div
            key={`r${i}`}
            className={cn('absolute rounded-full border', dashed ? 'border-dashed border-[color-mix(in_oklch,var(--foreground)_16%,transparent)]' : 'border-[color-mix(in_oklch,var(--foreground)_11%,transparent)]')}
            style={{ left: `${50 - r.radius}%`, top: `${50 - r.radius}%`, width: `${r.radius * 2}%`, height: `${r.radius * 2}%` }}
          />
        ))}
      {rings.map((r, i) => {
        const dur = r.duration ?? 40;
        const dir = r.reverse ? 'reverse' : 'normal';
        const counter = r.reverse ? 'normal' : 'reverse';
        return (
          <div key={`o${i}`} className={cn('absolute inset-0', scrollTurn && 'kitfx-orbit-turn')} style={{ animation: `kitfx-orbit-spin ${dur}s linear infinite ${dir}`, ...(r.reverse && scrollTurn ? { ['--kitfx-turn' as string]: `${-scrollTurn}deg` } : {}) }}>
            {r.items.map((item, k) => {
              const a = round((r.offset ?? -90) + 90 + (360 / r.items.length) * k, 2);
              return (
                <div key={k} data-orbit-item="" className="absolute inset-0" style={{ transform: `rotate(${a}deg)` }}>
                  <div className="absolute left-1/2" style={{ top: `${50 - r.radius}%`, transform: `translate(-50%, -50%) rotate(${-a}deg)` }}>
                    <div className={scrollTurn ? 'kitfx-orbit-upright' : undefined} style={{ animation: `kitfx-orbit-spin ${dur}s linear infinite ${counter}` }}>{item}</div>
                  </div>
                </div>
              );
            })}
          </div>
        );
      })}
      {center && <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">{center}</div>}
    </div>
  );
}

/** A round chip for orbit items: an icon or initials on a card surface */
export function OrbitItem({ className, children }: { className?: string; children: React.ReactNode }) {
  return (
    <span className={cn('grid size-11 place-items-center rounded-full border border-border bg-card text-foreground shadow-[var(--highlight),var(--shadow-md)] sm:size-12', className)}>{children}</span>
  );
}

Häufige Fragen

Wie installiere ich Umlaufbahn?

Mit aiw add @ai-website/orbit. Der Befehl legt die Datei components/ui/orbit.tsx in dein Projekt, der Code gehört danach dir. Weitere npm-Pakete braucht sie nicht.

Welche Varianten hat Umlaufbahn?

3 Varianten, Integrationen, Halo hinter dem Text und Menschen.

Kann ein KI-Assistent Umlaufbahn einbauen?

Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Umlaufbahn mit aiw_components und installieren sie mit aiw_add @ai-website/orbit.

Oft gesucht

Direkt zu

In die Zwischenablage kopiert