Cargadores
Seis indicadores de carga solo con CSS, en el color del texto.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
/**
* Seis cargadores solo con CSS, en el color del texto (currentColor): anillo, puntos, barras, órbita, pulso y cuadros.
* Anuncian "Cargando" a lectores de pantalla y se detienen con movimiento reducido.
*
* <Loader variant="orbit" size={48} />
* <Loader variant="dots" label="Guardando…" />
*/
import type { CSSProperties } from "react";
export type LoaderVariant = "ring" | "dots" | "bars" | "orbit" | "pulse" | "squares";
export type LoaderProps = {
variant?: LoaderVariant;
/** Tamaño en píxeles. */
size?: number;
/** Texto para lectores de pantalla. */
label?: string;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcld-root { position: relative; display: inline-block; width: var(--gcld-size, 40px); height: var(--gcld-size, 40px); color: inherit; }
.gcld-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.gcld-ring { position: absolute; inset: 0; box-sizing: border-box; border: calc(var(--gcld-size) * .1) solid currentColor; border-color: currentColor transparent currentColor transparent; border-radius: 50%; animation: gcld-spin 1s cubic-bezier(.5, .1, .5, .9) infinite; }
.gcld-dots { display: flex; align-items: center; justify-content: space-between; height: 100%; }
.gcld-dots i { width: 22%; height: 22%; border-radius: 50%; background: currentColor; animation: gcld-bounce 1.1s ease-in-out infinite; }
.gcld-dots i:nth-child(2) { animation-delay: .15s; } .gcld-dots i:nth-child(3) { animation-delay: .3s; }
.gcld-bars { display: flex; align-items: center; justify-content: space-between; height: 100%; }
.gcld-bars i { width: 14%; height: 100%; border-radius: 99px; background: currentColor; transform-origin: center; animation: gcld-stretch 1s ease-in-out infinite; }
.gcld-bars i:nth-child(2) { animation-delay: .1s; } .gcld-bars i:nth-child(3) { animation-delay: .2s; } .gcld-bars i:nth-child(4) { animation-delay: .3s; } .gcld-bars i:nth-child(5) { animation-delay: .4s; }
.gcld-orbit { position: absolute; inset: 0; animation: gcld-spin 1.6s linear infinite; }
.gcld-orbit::before, .gcld-orbit::after { content: ""; position: absolute; width: 26%; height: 26%; border-radius: 50%; background: currentColor; }
.gcld-orbit::before { top: 0; left: 37%; } .gcld-orbit::after { bottom: 0; left: 37%; opacity: .4; }
.gcld-orbit-core { position: absolute; inset: 36%; border-radius: 50%; background: currentColor; opacity: .25; }
.gcld-pulse { position: absolute; inset: 0; }
.gcld-pulse i { position: absolute; inset: 0; border: calc(var(--gcld-size) * .06) solid currentColor; border-radius: 50%; opacity: 0; animation: gcld-ping 1.8s cubic-bezier(.2, .6, .3, 1) infinite; }
.gcld-pulse i:nth-child(2) { animation-delay: .6s; } .gcld-pulse i:nth-child(3) { animation-delay: 1.2s; }
.gcld-squares { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8%; height: 100%; }
.gcld-squares i { border-radius: 18%; background: currentColor; animation: gcld-fade 1.2s ease-in-out infinite; }
.gcld-squares i:nth-child(2), .gcld-squares i:nth-child(4) { animation-delay: .15s; } .gcld-squares i:nth-child(3), .gcld-squares i:nth-child(5), .gcld-squares i:nth-child(7) { animation-delay: .3s; } .gcld-squares i:nth-child(6), .gcld-squares i:nth-child(8) { animation-delay: .45s; } .gcld-squares i:nth-child(9) { animation-delay: .6s; }
@keyframes gcld-spin { to { transform: rotate(360deg); } }
@keyframes gcld-bounce { 0%, 80%, 100% { transform: translateY(0) scale(.7); opacity: .5; } 40% { transform: translateY(-70%) scale(1); opacity: 1; } }
@keyframes gcld-stretch { 0%, 100% { transform: scaleY(.25); opacity: .55; } 50% { transform: scaleY(1); opacity: 1; } }
@keyframes gcld-ping { 0% { transform: scale(.2); opacity: .9; } 100% { transform: scale(1); opacity: 0; } }
@keyframes gcld-fade { 0%, 100% { opacity: .15; transform: scale(.85); } 50% { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) { .gcld-root *, .gcld-root *::before, .gcld-root *::after { animation-duration: 4s !important; } }
`;
const inner: Record<LoaderVariant, React.ReactNode> = {
ring: <span className="gcld-ring" />,
dots: <span className="gcld-dots"><i /><i /><i /></span>,
bars: <span className="gcld-bars"><i /><i /><i /><i /><i /></span>,
orbit: <><span className="gcld-orbit-core" /><span className="gcld-orbit" /></>,
pulse: <span className="gcld-pulse"><i /><i /><i /></span>,
squares: <span className="gcld-squares">{Array.from({ length: 9 }, (_, index) => <i key={index} />)}</span>,
};
export function Loader({ variant = "ring", size = 40, label = "Cargando", className, style }: LoaderProps) {
return (
<>
<style>{CSS}</style>
<span role="status" className={["gcld-root", className].filter(Boolean).join(" ")} style={{ ["--gcld-size" as string]: `${size}px`, ...style } as CSSProperties}>
<span className="gcld-sr">{label}</span>
<span aria-hidden="true" style={{ display: "contents" }}>{inner[variant]}</span>
</span>
</>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/cargadoresEjemplo de uso de Cargadores
Después de copiar loaders.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { Loader } from "./loaders";
export default function Ejemplo() {
return <Loader variant="ring" label="Ejemplo" />;
}Propiedades de Loader
Loader acepta 5 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
variant? | LoaderVariant | Opcional. |
size? | number | Tamaño en píxeles. |
label? | string | Texto para lectores de pantalla. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Variantes disponibles
variant:ring,dots,bars,orbit,pulse,squares
Personaliza Cargadores con variables CSS
Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.
| Variable | Valor por defecto |
|---|---|
--gcld-size | 40px |
Accesibilidad y rendimiento
El archivo tiene 71 líneas (5 KB) y define 5 animaciones con @keyframes. Esto es lo que hace Cargadores por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Declara roles ARIA para lectores de pantalla: status.
Acerca del producto
Seis cargadores en un solo componente: anillo, puntos, barras, órbita, pulso y cuadros. Usan currentColor, así que toman el color de su contenedor, y anuncian "Cargando" a lectores de pantalla.
Incluye
Un solo archivo React + TypeScript, con los estilos dentro. No usa Tailwind ni bibliotecas externas.
- Seis variantes con la propiedad variant
- Tamaño en píxeles
- Texto accesible configurable (label)
- Sin JavaScript
Cómo usarlo
<Loader variant="orbit" size={48} />. Cambia el color con color en el contenedor.