Skeleton Loader
Placeholder loading animations for various UI elements. Built using Box, Column, and Row components with CSS animations.
Text skeleton
Source
Add this CSS to your stylesheet:
@keyframes skeleton-shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
.skeleton {
background: linear-gradient(
90deg,
var(--gray-300) 0%,
var(--gray-200) 50%,
var(--gray-300) 100%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s ease-in-out infinite;
}
[data-theme="dark"] .skeleton,
.dark .skeleton {
background: linear-gradient(
90deg,
var(--gray-600) 0%,
var(--gray-500) 50%,
var(--gray-600) 100%
);
background-size: 200% 100%;
}'use client';
import { Box, Column, Row } from '@umami/react-zen';
export function Skeleton({ width = '100%', height = '1rem', borderRadius = 'md', ...props }) {
return (
<Box
width={width}
height={height}
borderRadius={borderRadius}
className="skeleton"
{...props}
/>
);
}
export function SkeletonText({ lines = 3, lastLineWidth = '60%' }) {
return (
<Column gap="2">
{Array.from({ length: lines }).map((_, i) => (
<Skeleton
key={i}
height="0.875rem"
width={i === lines - 1 ? lastLineWidth : '100%'}
/>
))}
</Column>
);
}
export function SkeletonAvatar({ size = 'md' }) {
const sizeMap = { sm: '2rem', md: '2.5rem', lg: '3rem' };
return <Skeleton width={sizeMap[size]} height={sizeMap[size]} borderRadius="full" />;
}
export function SkeletonListItem() {
return (
<Row padding="3" alignItems="center" gap="3" borderColor="muted" border="bottom">
<SkeletonAvatar />
<Column gap="2" flexGrow="1">
<Skeleton height="1rem" width="40%" />
<Skeleton height="0.75rem" width="60%" />
</Column>
<Skeleton width="4rem" height="1.5rem" borderRadius="full" />
</Row>
);
}
export function SkeletonTable({ rows = 5, columns = 4 }) {
return (
<Column>
<Row padding="3" gap="4" borderColor="muted" border="bottom">
{Array.from({ length: columns }).map((_, i) => (
<Skeleton key={i} height="0.75rem" width="6rem" />
))}
</Row>
{Array.from({ length: rows }).map((_, rowIndex) => (
<Row key={rowIndex} padding="3" gap="4" borderColor="muted" border="bottom">
{Array.from({ length: columns }).map((_, colIndex) => (
<Skeleton key={colIndex} height="1rem" width={colIndex === 0 ? '8rem' : '6rem'} />
))}
</Row>
))}
</Column>
);
}