react-ez-skeleton
Lightweight, accessible, and themeable skeleton loading components for React. Built with SSR, reduced motion, and testing DX in mind.
npm install react-ez-skeletonGetting Started
Add skeleton loaders to your React app in seconds.
1. Install
npm install react-ez-skeleton2. Import and use
import { Skeleton, SkeletonText, SkeletonCircle } from "react-ez-skeleton";
function ProfileCard() {
return (
<div>
<SkeletonCircle size={64} dataTestId="avatar" />
<SkeletonText lines={2} width="80%" dataTestId="name" />
</div>
);
}3. Test with data-testid
Each component accepts a dataTestId prop that maps to data-testid for easy testing. SkeletonText automatically appends line indexes (-0, -1, …).
import { render, screen } from "@testing-library/react";
import { Skeleton } from "react-ez-skeleton";
test("renders skeleton with test ID", () => {
render(<Skeleton dataTestId="loading-avatar" />);
expect(screen.getByTestId("loading-avatar")).toBeInTheDocument();
});Live Examples
Interactive demos with dark mode and reduced motion support.
Basic Skeletons
<Skeleton width={200} height={20} />
<SkeletonText lines={3} />
<SkeletonCircle size={48} />Customized SkeletonText with Random Widths
<SkeletonText
lines={4}
lineHeight={20}
gap={12}
randomizeLineWidths
randomizeMin={60}
randomizeMax={95}
dataTestId="random-text"
/>Theming via CSS Variables
<div style={{
"--ez-skeleton-color-start": "#fef3c7",
"--ez-skeleton-color-middle": "#fde68a",
"--ez-skeleton-color-end": "#fef3c7",
}}>
<Skeleton width={180} height={16} />
</div>Props & API Reference
Detailed props for each component.
Skeleton
| Prop | Type | Default | Description |
|---|---|---|---|
| width | string | number | "100%" | Width of the skeleton |
| height | string | number | 16 | Height of the skeleton |
| radius | string | number | 4 | Border radius |
| className | string | — | Custom class |
| style | React.CSSProperties | — | Inline styles |
| animate | boolean | true | Enable shimmer animation |
| respectReducedMotion | boolean | true | Respects OS motion settings |
| ariaHidden | boolean | true | Accessibility: hide from screen readers |
| injectStyles | boolean | true | Auto-inject required styles |
| dataTestId | string | — | Maps to data-testid for testing |
<Skeleton
width={200}
height={20}
radius={8}
disableAnimation={false}
dataTestId="title-skeleton"
/>SkeletonText
| Prop | Type | Default | Description |
|---|---|---|---|
| lines | number | 3 | Number of lines |
| lineHeight | string | number | 16 | Height of each line |
| gap | string | number | 8 | Gap between lines |
| width | string | number | "100%" | Width of the last line (full lines are 100%) |
| radius | string | number | 4 | Border radius per line |
| randomizeLineWidths | boolean | false | Enable randomized line widths |
| randomizeMin | number | 60 | Min percentage for random widths |
| randomizeMax | number | 100 | Max percentage for random widths |
| randomizeSeed | number | — | Seed for deterministic randomness |
| lineWidths | (number | string)[] | — | Explicit widths per line |
| aria-hidden | boolean | true | Accessibility: hide from screen readers |
| animate | boolean | true | Enable shimmer animation |
| respectReducedMotion | boolean | true | Respects OS motion settings |
| injectStyles | boolean | true | Auto-inject required styles |
| dataTestId | string | — | Maps to data-testid; lines are suffixed with -0, -1, … |
<SkeletonText
lines={4}
lineHeight={20}
gap={12}
width="80%"
dataTestId="bio-skeleton"
/>SkeletonCircle
| Prop | Type | Default | Description |
|---|---|---|---|
| size | string | number | 40 | Width and height (circle) |
| className | string | — | Custom class |
| style | React.CSSProperties | — | Inline styles |
| animate | boolean | true | Enable shimmer animation |
| respectReducedMotion | boolean | true | Respects OS motion settings |
| ariaHidden | boolean | true | Accessibility: hide from screen readers |
| injectStyles | boolean | true | Auto-inject required styles |
| dataTestId | string | — | Maps to data-testid for testing |
<SkeletonCircle
size={64}
dataTestId="avatar-skeleton"
/>Theming & Accessibility
Customize colors and respect user preferences.
CSS Variables
Override the default shimmer colors and animation using CSS variables. Wrap skeletons in a container with custom variables to theme per-component.
:root {
--ez-skeleton-color-start: #f2f2f2;
--ez-skeleton-color-middle: #e6e6e6;
--ez-skeleton-color-end: #f2f2f2;
--ez-skeleton-animation: react-ez-skeleton-pulse 1.4s ease-in-out infinite;
}
.dark {
--ez-skeleton-color-start: #374151;
--ez-skeleton-color-middle: #4b5563;
--ez-skeleton-color-end: #374151;
}Reduced Motion
Skeletons automatically respect prefers-reduced-motion. You can also disable animations per-instance with the disableAnimation prop.
@media (prefers-reduced-motion: reduce) {
:root {
--ez-skeleton-animation: none;
}
}Accessibility
- Skeletons are
aria-hidden="true"by default to hide from screen readers. - Use
dataTestIdfor testing; it maps todata-testid. - Reduced motion is respected automatically.
- Components are SSR-safe with no runtime dependencies beyond React.
Comparison
How react-ez-skeleton stacks up against alternatives.
| Feature | react-ez-skeleton | react-loading-skeleton | @mui/material Skeleton |
|---|---|---|---|
| Bundle size | ~1.75 kB (gzip) | ~3.3 kB (gzip) | Part of MUI (~large) |
| Theming via CSS vars | ✅ | ❌ (inline styles) | ✅ (MUI theme) |
| Reduced motion support | ✅ (built-in) | ✅ | ✅ |
| data-testid / testing DX | ✅ (dataTestId prop) | ❌ (manual) | ❌ (manual) |
| SSR safe | ✅ | ✅ | ✅ |
| Zero dependencies | ✅ | ✅ | ❌ (MUI deps) |
| SkeletonText with random widths | ✅ (built-in) | ❌ | ❌ |
| Circle component | ✅ | ❌ (manual borderRadius) | ✅ |
react-ez-skeleton focuses on developer experience, minimal bundle size, and modern React patterns without requiring a design system.