primitives
Three Body Loader
A compact loading spinner: three dots orbit a shared center while each wobbles in and out on its own phase, reading as an organic three-body motion rather than a rigid spin. Pure CSS, no JS (server component). size and speed are props (mapped to --uib-size / --uib-speed); the dot color comes from --uib-color, defaulting to the --primary token. Announced as role=status with an overridable label. Ported from a uiverse.io loader by dovatgabriel.
Ported from dovatgabriel (MIT)
Install
1. Register the namespace (once per project):
json
// components.json — register the @bottega namespace once
{
"registries": {
"@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
}
}2. Add the component:
bash
npx shadcn add @bottega/three-body-loaderUsage
Usagetsx
<ThreeBodyLoader />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| size | number | 35 | Overall size in px (maps to --uib-size). Default 35. |
| speed | number | 0.8 | Animation period in seconds — lower is faster (maps to --uib-speed). Default 0.8. |
| color | string | — | Dot color. Any CSS color; defaults to the --primary token. |
| label | string | Loading | Accessible label announced by screen readers. Default "Loading". |
Source
three-body-loader.tsxtsx
import type React from "react";
import styles from "./three-body-loader.module.css";
export interface ThreeBodyLoaderProps
extends React.HTMLAttributes<HTMLDivElement> {
/** Overall size in px (maps to --uib-size). Default 35. */
size?: number;
/** Animation period in seconds — lower is faster (maps to --uib-speed). Default 0.8. */
speed?: number;
/** Dot color. Any CSS color; defaults to the --primary token. */
color?: string;
/** Accessible label announced by screen readers. Default "Loading". */
label?: string;
}
/**
* Three Body Loader — three dots orbit a shared center while each wobbles in and
* out on its own phase, reading as an organic three-body motion rather than a
* rigid spin. Pure CSS, no JS (server component).
*
* `size` and `speed` are exposed as props (mapped to --uib-size / --uib-speed);
* the dot color comes from --uib-color, defaulting to the --primary token.
* Announced as role="status" with an overridable label. All motion is removed
* under prefers-reduced-motion (static dots — see the module CSS).
*
* Ported from a uiverse.io loader by dovatgabriel.
*/
export function ThreeBodyLoader({
size = 35,
speed = 0.8,
color,
label = "Loading",
className = "",
style,
...props
}: ThreeBodyLoaderProps) {
return (
<div
role="status"
aria-label={label}
className={[styles.root, className].filter(Boolean).join(" ")}
style={
{
"--uib-size": `${size}px`,
"--uib-speed": `${speed}s`,
...(color ? { "--uib-color": color } : {}),
...style,
} as React.CSSProperties
}
{...props}
>
<div className={styles.dot} />
<div className={styles.dot} />
<div className={styles.dot} />
</div>
);
}
Dependencies
- @bottega/tokens