Loaders
Ceiling Fan Loader
A loading spinner styled like a spinning ceiling fan, with three brushed-metal blades rotating around a metallic hub.
Loading preview
Advertisement
Ad space reserved for a future placement
Source code
"use client";
import { useId } from "react";
type CeilingFanLoaderSize = "sm" | "md" | "lg";
interface CeilingFanLoaderProps {
size?: CeilingFanLoaderSize;
label?: string;
className?: string;
}
const SIZE_PX: Record<CeilingFanLoaderSize, number> = {
sm: 32,
md: 48,
lg: 64,
};
export function CeilingFanLoader({ size = "md", label = "Loading", className }: CeilingFanLoaderProps) {
const px = SIZE_PX[size];
const gradientId = useId();
return (
<div
role="status"
aria-label={label}
className={["ceiling-fan-loader", className].filter(Boolean).join(" ")}
style={{ width: px, height: px }}
>
<svg viewBox="0 0 100 100" fill="none">
<defs>
<linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#f1f5f9" />
<stop offset="25%" stopColor="#94a3b8" />
<stop offset="50%" stopColor="#e2e8f0" />
<stop offset="75%" stopColor="#64748b" />
<stop offset="100%" stopColor="#cbd5e1" />
</linearGradient>
</defs>
<g className="ceiling-fan-loader-blades">
<ellipse cx="50" cy="28" rx="7" ry="20" fill={`url(#${gradientId})`} transform="rotate(0 50 50)" />
<ellipse cx="50" cy="28" rx="7" ry="20" fill={`url(#${gradientId})`} transform="rotate(120 50 50)" />
<ellipse cx="50" cy="28" rx="7" ry="20" fill={`url(#${gradientId})`} transform="rotate(240 50 50)" />
<circle cx="50" cy="50" r="8" fill={`url(#${gradientId})`} />
</g>
</svg>
<style jsx>{`
.ceiling-fan-loader {
display: inline-block;
}
.ceiling-fan-loader svg {
width: 100%;
height: 100%;
}
.ceiling-fan-loader-blades {
transform-origin: 50px 50px;
animation: ceiling-fan-spin 1.1s linear infinite;
}
@keyframes ceiling-fan-spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.ceiling-fan-loader-blades {
animation-duration: 3s;
}
}
`}</style>
</div>
);
}
Usage
Import the component and use it like this.
import { CeilingFanLoader } from "./CeilingFanLoader";
export default function Example() {
return <CeilingFanLoader size="md" label="Loading" />;
}
Dependencies
No external dependencies.
Usage notes
Pass size (sm/md/lg) and an optional label for the accessible status announcement.