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
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.
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.
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.
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
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.
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ên | Kiểu | Giá trị | Mặc định | Mô tả |
|---|---|---|---|---|
| shape | enum | text | circular | rectangular | control | text | 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. |
| size | enum | sm | md | lg | xl | md | 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. |
| width | string | - | - | Width for a value known only at runtime, such as a column width. Otherwise set a size class with className (w-48). |
| height | string | - | - | Height for a value known only at runtime. Otherwise set a size class with className (h-24). With lines, the height of each line. |
| lines | number | - | 1 | Text only: that many lines in a column, the last one shorter, like the end of a paragraph. className and width go on the column. |
| label | string | - | Loading | SkeletonGroup only: what screen readers hear while the region loads. |
Cấu tạo
| Phần | Mô tả |
|---|---|
| root | The 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. |
| bar | text only: the bar inside the line box, drawn as a ::before. |
| lines | With 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. |
| group | SkeletonGroup: 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.jsonLệ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").