Back

Skeleton

Clean and easy-to-use skeleton loaders for specific components.

Tom Cook

Head of engineering

When it comes to loading skeletons, I typically add a dedicated skeleton component for each of my atomic components like Avatar, Button and so on. This makes loading states much more readable and easier to design. Often the only thing a skeleton component accepts as a prop is a class which defines its size.

The skeleton components are typically co-located with the components they represent. This makes it easier to find and import them.

Photo of Fabian

Best Practices

  • Use template literal types to narrow down allowed class names.

    className: `w-${string}`;
  • Access Tailwind CSS' theme with arbitrary values and access an object's value by directly calling the index on the definition.

    const heights = {
    xs: "[--line-height:theme('spacing.4')] ...",
    sm: "[--line-height:theme('spacing.5')] ...",
    base: "[--line-height:theme('spacing.6')] ...",
    }[size];
  • Define CSS custom properties on the parent component to access them on the children.

    <div className={clsx(heights, 'flex h-[--line-height] items-center')}>

Requirements

Code

import { clsx } from 'clsx';
interface AvatarSkeletonProps {
className: `size-${string}`;
}
interface TextSkeletonProps {
className: `w-${string}`;
size?: 'xs' | 'sm' | 'base';
}
function SkeletonExample() {
return (
<div className="flex items-center gap-x-3">
<AvatarSkeleton className="size-9" />
<div>
<TextSkeleton className="w-16" size="sm" />
<TextSkeleton className="w-28" size="xs" />
</div>
</div>
);
}
function AvatarSkeleton({ className }: AvatarSkeletonProps) {
return (
<div
className={
clsx('animate-pulse rounded-full bg-gray-300', className)
}
/>
);
}
function TextSkeleton({ className, size = 'base' }: TextSkeletonProps) {
const heights = {
xs: "[--line-height:theme('spacing.4')] [--text-height:theme('spacing[1.5]')]",
sm: "[--line-height:theme('spacing.5')] [--text-height:theme('spacing.2')]",
base: "[--line-height:theme('spacing.6')] [--text-height:theme('spacing[2.5])]",
}[size];
return (
<div className={clsx(heights, 'flex h-[--line-height] items-center')}>
<div
className={clsx(
'h-[--text-height] animate-pulse rounded-full bg-gray-300',
className,
)}
/>
</div>
);
}