Skeleton
Animated placeholder bars for loading shapes, headings, and body text.
npx shadcn@latest add @velaui/skeletonpnpm dlx velaui add @velaui/skeletonyarn dlx shadcn@latest add @velaui/skeletonbunx --bun shadcn@latest add @velaui/skeletonveni add skeletonInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/skeletonpnpm dlx velaui add @velaui/skeletonyarn dlx shadcn@latest add @velaui/skeletonbunx --bun shadcn@latest add @velaui/skeletonExamples
- Default
- Shapes
- Avatar sizes
- Paragraph
- Profile
- Card
Usage
Props
Skeleton
Takes every prop of React.ComponentProps<"div">.
SkeletonShape
| Prop | Type | Default |
|---|---|---|
shape | "square" | "rectangle" | "pill" | "round" | "round" |
size | "sm" | "md" | "lg" | "2xs" | "xs" | "lg" |
Takes these and every prop of SkeletonShapeProps.
SkeletonText
| Prop | Type | Default |
|---|---|---|
lines | number | 1 |
Takes these and every prop of SkeletonTextProps.
SkeletonHeading
| Prop | Type | Default |
|---|---|---|
size | "sm" | "md" | "lg" | "lg" |
Takes these and every prop of SkeletonHeadingProps.
Dependencies
- npm
class-variance-authority@0.7.1- Installs with it
- nothing else
Source
components/ui/skeleton.tsx
/* eslint-disable react-refresh/only-export-components */
"use client";
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/* -------------------------------------------------------------------------- */
/* Skeleton */
/* -------------------------------------------------------------------------- */
/** The atomic placeholder bar every other part is built from. */
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("animate-pulse rounded-xl bg-bg-surface-1", className)}
{...props}
/>
);
}
/* -------------------------------------------------------------------------- */
/* Shape */
/* -------------------------------------------------------------------------- */
const skeletonShapeVariants = cva("shrink-0", {
variants: {
shape: {
rectangle: "rounded-xl",
pill: "rounded-full",
round: "rounded-full",
square: "rounded-xl",
},
size: {
"2xs": "h-5",
xs: "h-6",
sm: "h-8",
md: "h-10",
lg: "h-12",
},
},
compoundVariants: [
// Round and square are as wide as they are tall.
{ shape: ["round", "square"], size: "2xs", className: "w-5" },
{ shape: ["round", "square"], size: "xs", className: "w-6" },
{ shape: ["round", "square"], size: "sm", className: "w-8" },
{ shape: ["round", "square"], size: "md", className: "w-10" },
{ shape: ["round", "square"], size: "lg", className: "w-12" },
// Rectangle and pill carry the design's wider footprint.
{ shape: ["rectangle", "pill"], size: "2xs", className: "w-9.5" },
{ shape: ["rectangle", "pill"], size: "xs", className: "w-11.5" },
{ shape: ["rectangle", "pill"], size: "sm", className: "w-15" },
{ shape: ["rectangle", "pill"], size: "md", className: "w-19" },
{ shape: ["rectangle", "pill"], size: "lg", className: "w-23" },
],
defaultVariants: {
shape: "round",
size: "lg",
},
});
export type SkeletonShapeProps = React.ComponentProps<"div"> &
VariantProps<typeof skeletonShapeVariants>;
function SkeletonShape({
className,
shape = "round",
size = "lg",
...props
}: SkeletonShapeProps) {
return (
<Skeleton
data-slot="skeleton-shape"
data-shape={shape}
className={cn(skeletonShapeVariants({ shape, size }), className)}
{...props}
/>
);
}
/* -------------------------------------------------------------------------- */
/* Body text */
/* -------------------------------------------------------------------------- */
export type SkeletonTextProps = Omit<React.ComponentProps<"div">, "children"> & {
/** How many placeholder lines to render — the last one runs short. */
lines?: number;
};
function SkeletonText({ className, lines = 1, ...props }: SkeletonTextProps) {
return (
<div
data-slot="skeleton-text"
className={cn("flex w-full flex-col gap-2", className)}
{...props}
>
{Array.from({ length: lines }, (_, line) => (
<Skeleton
key={line}
data-slot="skeleton-text-line"
className={cn(
"h-2 w-full rounded-full",
// A single line runs full width; a stack ends on a short one.
lines > 1 && line === lines - 1 && "w-[calc(100%-2.5rem)]",
)}
/>
))}
</div>
);
}
/* -------------------------------------------------------------------------- */
/* Heading text */
/* -------------------------------------------------------------------------- */
const skeletonHeadingVariants = cva("w-full rounded-full", {
variants: {
size: {
sm: "h-4",
md: "h-6",
lg: "h-8",
},
},
defaultVariants: {
size: "lg",
},
});
export type SkeletonHeadingProps = React.ComponentProps<"div"> &
VariantProps<typeof skeletonHeadingVariants>;
function SkeletonHeading({
className,
size = "lg",
...props
}: SkeletonHeadingProps) {
return (
<Skeleton
data-slot="skeleton-heading"
className={cn(skeletonHeadingVariants({ size }), className)}
{...props}
/>
);
}
export {
Skeleton,
SkeletonShape,
SkeletonText,
SkeletonHeading,
skeletonShapeVariants,
skeletonHeadingVariants,
};