Vela UI Docsvenizia.ai

Skeleton

Animated placeholder bars for loading shapes, headings, and body text.

npx shadcn@latest add @velaui/skeleton
npx shadcn@latest add @velaui/skeleton

Examples

  • Default
  • Shapes
  • Avatar sizes
  • Paragraph
  • Profile
  • Card

View code

Skeleton · Default

Installation

pnpm dlx velaui add @velaui/skeleton
pnpm dlx velaui add @velaui/skeleton

Code

skeleton-default.tsx
import { Skeleton } from "@/components/ui/skeleton";

export default function Example() {
  return <Skeleton className="h-4 w-40" />;
}

View code

Skeleton · Shapes

Installation

pnpm dlx velaui add @velaui/skeleton
pnpm dlx velaui add @velaui/skeleton

Code

skeleton-shapes.tsx
import { SkeletonShape } from "@/components/ui/skeleton";

export default function Example() {
  return (
    <div className="flex items-end gap-4">
      <SkeletonShape shape="rectangle" size="md" />
      <SkeletonShape shape="pill" size="md" />
      <SkeletonShape shape="round" size="md" />
      <SkeletonShape shape="square" size="md" />
    </div>
  );
}

View code

Skeleton · Avatar sizes

Installation

pnpm dlx velaui add @velaui/skeleton
pnpm dlx velaui add @velaui/skeleton

Code

skeleton-avatar-sizes.tsx
import { SkeletonShape } from "@/components/ui/skeleton";

export default function Example() {
  return (
    <div className="flex items-end gap-3">
      <SkeletonShape shape="round" size="2xs" />
      <SkeletonShape shape="round" size="xs" />
      <SkeletonShape shape="round" size="sm" />
      <SkeletonShape shape="round" size="md" />
      <SkeletonShape shape="round" size="lg" />
    </div>
  );
}

View code

Skeleton · Paragraph

Installation

pnpm dlx velaui add @velaui/skeleton
pnpm dlx velaui add @velaui/skeleton

Code

skeleton-paragraph.tsx
import { SkeletonText } from "@/components/ui/skeleton";

export default function Example() {
  return <SkeletonText lines={3} className="w-64" />;
}

View code

Skeleton · Profile

Installation

pnpm dlx velaui add @velaui/skeleton
pnpm dlx velaui add @velaui/skeleton

Code

skeleton-profile.tsx
import {
  SkeletonShape,
  SkeletonHeading,
  SkeletonText,
} from "@/components/ui/skeleton";

export default function Example() {
  return (
    <div className="flex items-center gap-4">
      <SkeletonShape shape="round" size="lg" />
      <div className="flex w-48 flex-col gap-2">
        <SkeletonHeading size="sm" />
        <SkeletonText lines={2} />
      </div>
    </div>
  );
}

View code

Skeleton · Card

Installation

pnpm dlx velaui add @velaui/skeleton
pnpm dlx velaui add @velaui/skeleton

Code

skeleton-card.tsx
import {
  SkeletonShape,
  SkeletonHeading,
  SkeletonText,
} from "@/components/ui/skeleton";

export default function Example() {
  return (
    <div className="flex w-56 flex-col gap-3">
      <SkeletonShape shape="rectangle" size="lg" className="h-32 w-full" />
      <SkeletonHeading size="md" />
      <SkeletonText lines={2} />
    </div>
  );
}

Usage

import { Skeleton } from "@/components/ui/skeleton";

export default function Example() {
  return <Skeleton className="h-4 w-40" />;
}

Props

Skeleton

Takes every prop of React.ComponentProps<"div">.

SkeletonShape

PropTypeDefault
shape"square" | "rectangle" | "pill" | "round""round"
size"sm" | "md" | "lg" | "2xs" | "xs""lg"

Takes these and every prop of SkeletonShapeProps.

SkeletonText

PropTypeDefault
linesnumber1

Takes these and every prop of SkeletonTextProps.

SkeletonHeading

PropTypeDefault
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,
};