跳至正文

Skeleton

内容加载时显示的占位形状。

浏览器
Chrome 123+Firefox 121+Safari 17.5+
层级
0
压缩后大小
317 B
强制布局预算
0 ms
axe 预算
0
支持 SSR
是
公开契约
测试环境

交互示例

Skeleton

Text

个人资料

Media

Table

Pulse, shimmer, static

Load and reveal

Loading workspace…
import { Skeleton } from "@gunkin/uiify";

export default function Example() {
  return <Skeleton className="" width={240} height={20} />;
}
@layer components {
  [data-uiify-skeleton] {
    inline-size: var(--uiify-skeleton-w, 100%);
    min-block-size: var(--uiify-skeleton-h, 1rem);
    border-radius: var(--radius-sm);
  }
  [data-uiify-skeleton][data-state="loading"] {
    block-size: var(--uiify-skeleton-h, 1rem);
    background: var(--muted);
    animation: uiify-skeleton-pulse 1.8s ease-in-out infinite;
  }
  [data-uiify-skeleton][data-shape="circle"] {
    border-radius: 50%;
    corner-shape: round;
  }
  [data-uiify-skeleton][data-variant="shimmer"][data-state="loading"] {
    background: linear-gradient(90deg, var(--muted), var(--secondary), var(--muted));
    background-size: 200% 100%;
    animation: uiify-skeleton-shimmer 1.5s linear infinite;
  }
  [data-uiify-skeleton][data-variant="none"] {
    animation: none;
  }
  [data-uiify-skeleton][data-state="loaded"] {
    animation: uiify-skeleton-reveal 200ms ease-out;
  }

  @keyframes uiify-skeleton-pulse {
    50% {
      opacity: 0.45;
    }
  }

  @keyframes uiify-skeleton-shimmer {
    to {
      background-position: -200% 0;
    }
  }

  @keyframes uiify-skeleton-reveal {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    [data-uiify-skeleton] {
      animation: none;
    }
  }
}
import { Skeleton } from "@gunkin/uiify";

export default function Example() {
  return (
    <Skeleton
      className={
        "[inline-size:var(--uiify-skeleton-w,_100%)] [min-block-size:var(--uiify-skeleton-h,_1rem)] rounded-sm data-" +
        "[state=loading]:[block-size:var(--uiify-skeleton-h,_1rem)] data-[state=loading]:bg-muted data-[state=loadin" +
        "g]:[animation:uiify-skeleton-pulse_1.8s_ease-in-out_infinite] data-[shape=circle]:rounded-[50%] data-[shape" +
        "=circle]:[corner-shape:round] data-[variant=shimmer]:data-[state=loading]:[background-image:linear-gradient" +
        "(90deg,_var(--muted),_var(--secondary),_var(--muted))] data-[variant=shimmer]:data-[state=loading]:bg-trans" +
        "parent data-[variant=shimmer]:data-[state=loading]:[background-size:200%_100%] data-[variant=shimmer]:data-" +
        "[state=loading]:[animation:uiify-skeleton-shimmer_1.5s_linear_infinite] data-[variant=none]:[animation:none" +
        "] data-[state=loaded]:[animation:uiify-skeleton-reveal_200ms_ease-out] motion-reduce:[animation:none]"
      }
      width={240}
      height={20}
    />
  );
}
/* app.css */

@layer reset, ui, app, tokens, base, demo, components, utilities, theme;

@import "@gunkin/uiify/styles/reset";

@import "@gunkin/uiify/components/behavior.css";

@import "@gunkin/uiify/styles/tokens";

@import "@gunkin/uiify/styles/tailwind";

@import "tailwindcss/utilities.css";

@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }

@source inline('[inline-size:var(--uiify-skeleton-w,_100%)] [min-block-size:var(--uiify-skeleton-h,_1rem)] rounded-sm data-[state=loading]:[block-size:var(--uiify-skeleton-h,_1rem)] data-[state=loading]:bg-muted data-[state=loading]:[animation:uiify-skeleton-pulse_1.8s_ease-in-out_infinite] data-[shape=circle]:rounded-[50%] data-[shape=circle]:[corner-shape:round] data-[variant=shimmer]:data-[state=loading]:[background-image:linear-gradient(90deg,_var(--muted),_var(--secondary),_var(--muted))] data-[variant=shimmer]:data-[state=loading]:bg-transparent data-[variant=shimmer]:data-[state=loading]:[background-size:200%_100%] data-[variant=shimmer]:data-[state=loading]:[animation:uiify-skeleton-shimmer_1.5s_linear_infinite] data-[variant=none]:[animation:none] data-[state=loaded]:[animation:uiify-skeleton-reveal_200ms_ease-out] motion-reduce:[animation:none]');

@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }

/* Authored animation and registered properties used by the Tailwind skin.
 * Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
  @keyframes uiify-presence-in {
    from {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@layer components {
  @keyframes uiify-presence-out {
    to {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@property --uiify-spinner-tail {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --uiify-spinner-length {
  syntax: "<angle>";
  inherits: false;
  initial-value: 36deg;
}

@layer components {
  @keyframes uiify-spinner-rotate {
    to {
      transform: rotate(360deg);
    }
  }
}

@layer components {
  @keyframes uiify-spinner-orbit {
    0% {
      --uiify-spinner-tail: 0deg;
      --uiify-spinner-length: 36deg;
    }

    50% {
      --uiify-spinner-tail: 90deg;
      --uiify-spinner-length: 324deg;
    }

    100% {
      --uiify-spinner-tail: 360deg;
      --uiify-spinner-length: 36deg;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-pulse {
    50% {
      opacity: 0.45;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-shimmer {
    to {
      background-position: -200% 0;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-reveal {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

@property --candle-body-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-body-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-slot {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 100%;
}

@property --candle-wick-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-wick-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-x {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@property --line-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-prev-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-count {
  syntax: "<integer>";
  inherits: false;
  initial-value: 1;
}

@property --line-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@layer components {
  @keyframes uiify-otp-input-caret-blink {
    50% {
      opacity: 0;
    }
  }
}

API

Skeleton

属性类型默认值说明
variant"pulse" | "shimmer" | "none""pulse"占位内容动画。启用减少动态效果时,会禁用动画和内容显示动态效果。
circlebooleanfalse渲染圆形占位内容,而非矩形。
heightnumber | string—占位内容高度;数字视为像素。
loadingbooleantrue为false时,在同一外层元素内显示children,保留尺寸和ref;所选样式表为显示过程添加动画。
widthnumber | string—占位内容宽度;数字视为像素。
childrenReactNode—loading为false后渲染的真实内容。
renderReactElement | ((props, state) => ReactElement)—替换渲染元素,同时保留行为和无障碍连接关系。

键盘

按键操作

无障碍

Skeleton在加载和显示内容时保留稳定的外层元素。加载占位内容带有aria-hidden,加载完成的子元素保留原生语义。可选择pulse、shimmer或none。启用减少动态效果时会禁用动画。应在外层区域说明加载状态,而非为每个装饰性骨架添加实时状态。

样式钩子

data-uiify-skeletondata-statedata-variantdata-shape--uiify-skeleton-w--uiify-skeleton-h