Chuyển tới nội dung
Component

Skeleton

A gray block that holds the place of content while it loads, in the shape and size of what will replace it: a line of text, an avatar, a control, an image. It pulses unless the user asked for reduced motion.

Xem trước

Props

  • shape
  • size
  • width
  • height
  • lines
import { Skeleton } from "@/components/ui/skeleton"

<Skeleton lines={2} className="w-64 text-sm" />

Cách dùng

Khi nào dùng

  • Hold the place of content whose layout you know while it loads: a list of comments, a card, a profile.
  • Keep the page from jumping when the content comes in.

Khi nào không dùng

  • An action that takes a moment, such as saving. Use Spinner.
  • Work whose amount is known, such as an upload. Use Progress.

Biến thể

shape

text stands in for one line of the text around it; circular for an avatar; control for a Button, Input or Select; rectangular for an image or a block of media.

  • textmặc định

  • circular

  • rectangular

  • control

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

<Skeleton lines={2} className="w-64 text-sm" />
<Skeleton shape="circular" />
<Skeleton shape="rectangular" className="w-64" />
<Skeleton shape="control" className="w-64" />

size

circular and control only. circular takes the Avatar sizes (24, 32, 40px; Avatar has no xl, so xl is the 40px lg circle); control the element heights, so it matches a control of the same size.

  • sm

  • mdmặc định

  • lg

  • xl

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

<Skeleton lines={2} className="w-64 text-sm" />
<Skeleton lines={2} className="w-64 text-sm" />
<Skeleton lines={2} className="w-64 text-sm" />
<Skeleton lines={2} className="w-64 text-sm" />

Ví dụ

A comment loading

An avatar and two lines of text, in the layout of the comment that replaces them.

Loading
import { Skeleton, SkeletonGroup } from "@/components/ui/skeleton"

<SkeletonGroup className="flex w-full max-w-sm items-start gap-md">
  <Skeleton shape="circular" />
  <div className="flex flex-1 flex-col text-sm">
    <Skeleton className="w-32" />
    <Skeleton lines={2} />
  </div>
</SkeletonGroup>

A card loading

Loading
import { Skeleton, SkeletonGroup } from "@/components/ui/skeleton"

<SkeletonGroup className="flex w-64 flex-col gap-md">
  <Skeleton shape="rectangular" />
  <Skeleton className="text-md w-40" />
</SkeletonGroup>

A form loading

A label line at the label's size and a control at the field's size, so the form does not move when it arrives.

Loading settings
import { Skeleton, SkeletonGroup } from "@/components/ui/skeleton"

<SkeletonGroup label="Loading settings" className="flex w-64 flex-col gap-sm">
  <Skeleton className="w-24 text-sm" />
  <Skeleton shape="control" />
</SkeletonGroup>

Nên và không nên

Nên

  • Match the layout of the content that replaces it, so nothing jumps when it loads: text lines in the same font size, controls at the same size, the same corners.
  • Use lines for a paragraph: the last line is shorter, like real text.
  • Wrap the region in SkeletonGroup, or set aria-busy and a polite "Loading" status yourself.
  • Show it only when loading takes longer than about 150ms, and keep it at least about 300ms once shown (useSkeletonDelay), so it never flashes.
  • For a tab, a page of a table or a filter, keep the previous content on screen with a pending state instead of a skeleton.

Không nên

  • Don't use it for an action that takes a moment, such as saving. Use Spinner.
  • Don't use it when the amount of work is known, such as an upload. Use Progress.
  • Don't put text, icons or controls inside it.
  • Don't blank content that is already on screen to show a skeleton.

Props

TênKiểuGiá trịMặc địnhMô tả
shapeenumtext | circular | rectangular | controltexttext stands in for one line of the text around it; circular for an avatar; control for a Button, Input or Select; rectangular for an image or a block of media.
sizeenumsm | md | lg | xlmdcircular and control only. circular takes the Avatar sizes (24, 32, 40px; Avatar has no xl, so xl is the 40px lg circle); control the element heights, so it matches a control of the same size.
widthstring--Width for a value known only at runtime, such as a column width. Otherwise set a size class with className (w-48).
heightstring--Height for a value known only at runtime. Otherwise set a size class with className (h-24). With lines, the height of each line.
linesnumber-1Text only: that many lines in a column, the last one shorter, like the end of a paragraph. className and width go on the column.
labelstring-LoadingSkeletonGroup only: what screen readers hear while the region loads.

Cấu tạo

PhầnMô tả
rootThe block in the soft gray fill. text is a box one line tall (the line height of its font size) with a bar one em tall in the middle, inner corners; circular is an Avatar-sized circle; control is a control-sized block (element height, element corners); rectangular is full width at 16 by 9 with element corners.
bartext only: the bar inside the line box, drawn as a ::before.
linesWith lines: the column that holds the lines, with no gap, so it is as tall as the same number of real lines. The last line is two thirds wide.
groupSkeletonGroup: the region that loads, with aria-busy, and a visually hidden polite status next to it that reads label.

Khả năng truy cập

Role: none

  • Hidden from screen readers (aria-hidden). The region that loads sets aria-busy="true" and says "Loading" in a polite status; SkeletonGroup does both. Remove both when the content is in.
  • The pulse is an opacity loop timed by duration-loop, and it does not run at all under prefers-reduced-motion; the block stays.
  • Never focusable. Controls in the loading region stay out of the tab order until they exist.

Cài đặt

npx shadcn add https://werewolf-ui.vercel.app/r/skeleton.json

Lệnh này thêm luôn styles/werewolf.css, bộ token mà mọi component dùng, và các component khác mà nó cần. Import file đó một lần trong CSS gốc, ngay sau @import "tailwindcss", với đường dẫn tính từ file CSS đó (từ app/globals.css là @import "../styles/werewolf.css").