Skeleton
加载中的占位:比输入框深一档的底色、圆角 10、慢慢地呼吸,没有流光渐变。
skeleton-demo.tsx
import { Skeleton } from "@/components/ui/skeleton"
export default function SkeletonDemo() {
return (
<div aria-busy="true" aria-label="正在加载订阅" className="anibt-sheet flex w-full max-w-md flex-col px-4 py-2">
{[0, 1, 2].map((row) => (
<div key={row} className="anibt-stitch-under flex items-center gap-3 py-3 last:bg-none">
<Skeleton className="h-14 w-10 rounded-[6px]" />
<div className="flex min-w-0 flex-1 flex-col gap-2">
<Skeleton shape="line" className="w-11/12" />
<div className="flex gap-1.5">
<Skeleton shape="pill" className="h-5 w-12" />
<Skeleton shape="pill" className="h-5 w-10" />
<Skeleton shape="line" className="h-3 w-20 self-center" />
</div>
</div>
<Skeleton shape="circle" className="size-8" />
</div>
))}
</div>
)
}安装
npx shadcn@latest add @anibt/skeleton第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。
手动安装
- 1. 先装主题组件只读
@anibt/theme的 token 和配方,没有主题就没有样子。npx shadcn@latest add @anibt/theme - 2. 把下面的文件放进项目components/ui/skeleton.tsx
import * as React from "react" import { cn } from "@/lib/utils" /** * Skeleton — the AniBT 「贴纸手账」 loading placeholder. * * One step deeper than a field (`--anibt-deep2`), radius 10, and a slow pulse * as the only motion — no shimmer gradient. Size it like the thing it stands * in for: a 2:3 cover, a 14px title line, a 20px tag. `shape="pill"` is for * tags and counts, `shape="circle"` for avatars. * * Mark the region that is loading with `aria-busy`, not the skeletons: they * are `aria-hidden`, because a list of grey boxes says nothing to a screen * reader. Under reduced motion the pulse stops. */ function Skeleton({ className, shape = "box", ...props }: React.ComponentProps<"div"> & { /** `box` (radius 10), `line` (radius 6, for text), `pill`, `circle`. */ shape?: "box" | "line" | "pill" | "circle" }) { return ( <div data-slot="skeleton" data-shape={shape} aria-hidden="true" className={cn( "animate-pulse bg-anibt-deep2 motion-reduce:animate-none", shape === "box" && "rounded-[10px]", shape === "line" && "h-3.5 rounded-[6px]", shape === "pill" && "rounded-full", shape === "circle" && "aspect-square rounded-full", className )} {...props} /> ) } export { Skeleton }
用法
用法
import { Skeleton } from "@/components/ui/skeleton"
<div aria-busy="true" aria-label="正在加载订阅">
<Skeleton className="aspect-[2/3] w-full" />
<Skeleton shape="line" className="mt-2 w-4/5" />
</div>照着它要替代的东西定尺寸:2:3 的封面、14 号字高的标题行、20 高的标签。骨架本身是 aria-hidden 的——一串灰块对屏幕阅读器没有意义;在正在加载的区域上写 aria-busy 和一句说明。
切换筛选或页签时,旧内容留到新结果到达;骨架只给第一次加载用。海报墙和资源行各有同高的骨架:PosterCardSkeleton、ReleaseRowSkeleton。
减少动态效果(prefers-reduced-motion)时,呼吸停止。
属性
Skeleton
shape"box" | "line" | "pill" | "circle"默认 "box"圆角 10 的块 / 圆角 6、14 高的文字行 / 胶囊 / 圆
其余属性透传给 div,尺寸用 className 给。
