wui
组件

骨架屏 Skeleton

在内容加载期间维持页面结构,降低布局跳动与等待的不确定感。

基础示例

Loading…
pnpm dlx wui@latest add @wui/skeleton
components/ui/skeleton.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const skeletonVariants = cva("shrink-0 bg-muted", {
  variants: {
    shape: {
      default: "rounded-md",
      circle: "rounded-full",
      text: "h-4 rounded-sm",
    },
    animation: {
      pulse: "animate-pulse motion-reduce:animate-none",
      none: "",
    },
  },
  defaultVariants: {
    shape: "default",
    animation: "pulse",
  },
})

export interface SkeletonProps
  extends React.ComponentProps<"div">,
    VariantProps<typeof skeletonVariants> {
  /** Corner treatment for the placeholder. @default "default" */
  shape?: "default" | "circle" | "text"
  /** Loading animation. @default "pulse" */
  animation?: "pulse" | "none"
}

/** A neutral placeholder that preserves layout while content is loading. */
function Skeleton({
  className,
  shape = "default",
  animation = "pulse",
  ...props
}: SkeletonProps) {
  return (
    <div
      data-slot="skeleton"
      data-shape={shape}
      data-animation={animation}
      aria-hidden="true"
      className={cn(skeletonVariants({ shape, animation }), className)}
      {...props}
    />
  )
}

export interface SkeletonTextProps extends React.ComponentProps<"div"> {
  /** Number of text rows. @default 3 */
  lines?: number
  /** Width of the final row. @default "70%" */
  lastLineWidth?: React.CSSProperties["width"]
  /** Loading animation shared by every row. @default "pulse" */
  animation?: "pulse" | "none"
}

/** Generates a compact stack of text-shaped skeleton rows. */
function SkeletonText({
  className,
  lines = 3,
  lastLineWidth = "70%",
  animation = "pulse",
  ...props
}: SkeletonTextProps) {
  return (
    <div
      data-slot="skeleton-text"
      aria-hidden="true"
      className={cn("grid w-full gap-2", className)}
      {...props}
    >
      {Array.from({ length: lines }, (_, index) => (
        <Skeleton
          key={index}
          shape="text"
          animation={animation}
          className="w-full"
          style={index === lines - 1 ? { width: lastLineWidth } : undefined}
        />
      ))}
    </div>
  )
}

export { Skeleton, SkeletonText, skeletonVariants }

组件作用

Skeleton 在内容尚未到达时保留其大致形状与空间,适合首屏资料、列表、图片和文字区域。骨架布局应尽量接近真实内容,但不需要复刻每个细节;操作完成后的反馈应使用 Progress 或 Message,而不是 Skeleton。

组件属性

PropTypeDefaultDescription
shape"default" | "circle" | "text"defaultCorner treatment for the placeholder.
animation"pulse" | "none"pulseLoading animation.

shape="default | circle | text" 分别适合图片与容器、头像以及文字行。SkeletonText 通过 lines 快速生成多行占位,并使用 lastLineWidth 调整最后一行长度。

事件

骨架屏是非交互元素,不触发事件,并默认对辅助技术隐藏。加载区域应在父容器设置 aria-busy="true",并通过 aria-label 或邻近状态文本说明正在加载的内容。

拓展使用

内容布局

Loading…

用真实页面使用的 Grid 或 Flex 布局组合多个 Skeleton,可以在数据到达前稳定保留版面。设置 animation="none" 可关闭脉冲效果;系统启用“减少动态效果”时,动画也会自动停用。