---
title: Orbit
description: Icons or images riding a soft diagonal ellipse.
group: Media
order: 22
new: true
---

Drop icons, images, or anything else on a closed path and let them drift. The default path is a stretched ellipse tilted on a diagonal, not a perfect circle.

## Icons

```tsx
import { Orbit } from "@/components/motion/orbit"

export function Example() {
  return (
    <Orbit
      className="size-72"
      duration={22}
      radiusX={42}
      radiusY={26}
      rotate={-32}
      showPath
      center={<span>Orbit</span>}
    >
      <img src="/a.png" alt="" className="size-12 rounded-xl object-cover" />
      <img src="/b.png" alt="" className="size-12 rounded-xl object-cover" />
    </Orbit>
  )
}
```

## Images

```tsx
import { Orbit } from "@/components/motion/orbit"

export function Example() {
  return (
    <Orbit
      className="size-72"
      duration={22}
      radiusX={42}
      radiusY={26}
      rotate={-32}
      showPath
      center={<span>Orbit</span>}
    >
      <img src="/a.png" alt="" className="size-12 rounded-xl object-cover" />
      <img src="/b.png" alt="" className="size-12 rounded-xl object-cover" />
    </Orbit>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add orbit
```

## Usage

```tsx
import { Orbit } from "@/components/motion/orbit"
```

```tsx
<Orbit
  className="size-72"
  duration={22}
  radiusX={42}
  radiusY={26}
  rotate={-32}
  showPath
  center={<span>Orbit</span>}
>
  <img src="/a.png" alt="" className="size-12 rounded-xl object-cover" />
  <img src="/b.png" alt="" className="size-12 rounded-xl object-cover" />
</Orbit>
```

## API Reference

### Orbit

| Prop | Type | Default |
| --- | --- | --- |
| duration | number | 22 |
| radiusX | number | 42 |
| radiusY | number | 26 |
| rotate | number | -32 |
| reverse | boolean | false |
| pauseOnHover | boolean | false |
| showPath | boolean | false |
| center | ReactNode | — |
| className | string | — |
| children | ReactNode | — |

Also accepts the other native `div` attributes. `radiusX` and `radiusY` are percentages of the frame. `rotate` tilts the path in degrees.

## Source

```tsx
"use client";

import "@/lib/motion-styles";

import {
  Children,
  isValidElement,
  type ComponentProps,
  type CSSProperties,
  type ReactNode,
} from "react";
import { cn } from "@/lib/utils";

export type OrbitProps = Omit<ComponentProps<"div">, "children"> & {
  /** Seconds for one full loop. */
  duration?: number;
  /** Horizontal radius as % of the frame. */
  radiusX?: number;
  /** Vertical radius as % of the frame. */
  radiusY?: number;
  /** Tilt the path in degrees (diagonal ellipse). */
  rotate?: number;
  /** Reverse direction. */
  reverse?: boolean;
  /** Pause while hovered. */
  pauseOnHover?: boolean;
  /** Draw the orbital ellipse. */
  showPath?: boolean;
  /** Optional mark in the middle. */
  center?: ReactNode;
  children?: ReactNode;
};

function Orbit({
  className,
  duration = 22,
  radiusX = 42,
  radiusY = 26,
  rotate = -32,
  reverse = false,
  pauseOnHover = false,
  showPath = false,
  center,
  children,
  style,
  ...props
}: OrbitProps) {
  const items = Children.toArray(children).filter(isValidElement);
  const count = Math.max(items.length, 1);
  const seconds = Math.max(duration, 0.1);
  const rx = Math.min(Math.max(radiusX, 4), 48);
  const ry = Math.min(Math.max(radiusY, 4), 48);

  return (
    <div
      data-slot="orbit"
      data-pause-on-hover={pauseOnHover ? "true" : "false"}
      className={cn("arctis-orbit relative", className)}
      style={
        {
          "--orbit-duration": `${seconds}s`,
          "--orbit-rx": `${rx}%`,
          "--orbit-ry": `${ry}%`,
          "--orbit-rotate": `${rotate}deg`,
          "--orbit-direction": reverse ? "reverse" : "normal",
          ...style,
        } as CSSProperties
      }
      {...props}
    >
      <div className="arctis-orbit-plane absolute inset-0">
        {showPath ? (
          <svg
            className="arctis-orbit-path pointer-events-none absolute inset-0 size-full text-border"
            viewBox="0 0 100 100"
            preserveAspectRatio="none"
            aria-hidden="true"
          >
            <ellipse
              cx="50"
              cy="50"
              rx={rx}
              ry={ry}
              fill="none"
              stroke="currentColor"
              strokeWidth="1"
              vectorEffect="non-scaling-stroke"
            />
          </svg>
        ) : null}
        {items.map((child, index) => (
          <div
            key={child.key ?? index}
            className="arctis-orbit-item"
            style={
              {
                "--orbit-delay": `${(-index / count) * seconds}s`,
              } as CSSProperties
            }
          >
            <div className="arctis-orbit-item-face">{child}</div>
          </div>
        ))}
      </div>

      {center ? (
        <div className="arctis-orbit-center pointer-events-none absolute inset-0 flex items-center justify-center">
          <div className="pointer-events-auto">{center}</div>
        </div>
      ) : null}
    </div>
  );
}

export { Orbit };
```
