@cssints/skeleton

中文文档
0.35.1发布于 2026/11/07MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 7253

Skeleton loaders for cssints whose shimmer is in phase across the page: one animation on :root, read by every skeleton

原始使用文档
文档目录5 节
  1. How it stays in sync
  2. Rules
  3. Browsers
  4. Limits
  5. Credits

@cssints/skeleton

Loading placeholders for cssints whose shimmer is in phase across the page, whenever each one was mounted: one chain member of cssints/plugin scope(), after sync-skeleton. No runtime.

import { cn, height, hover, width } from "cssints" with { type: "cssints" };
import { skeleton } from "@cssints/skeleton" with { type: "cssints" };

export const line = cn(skeleton(), height("1em"), width("12rem"));
export const avatar = cn(skeleton().radius("50%"), width("3rem"), height("3rem"));
export const branded = cn(skeleton().color("#e4e4f0").highlight("#f4f4fa"), height("8rem"));
export const card = cn(skeleton(), hover(skeleton().highlight("white")));
@property --skeleton-p {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 100%;
}
@layer _.k {
  @keyframes kf-1a2b3c4d-skeleton {
    from {
      --skeleton-p: 100%;
    }
    to {
      --skeleton-p: 0%;
    }
  }
  @media (prefers-reduced-motion: no-preference) {
    :root:has(._skeleton-clock) {
      animation: kf-1a2b3c4d-skeleton 1.5s ease-in-out infinite;
    }
  }
  ._skeleton-core {
    border-radius: var(--skeleton-r, 0.25rem);
    background-position: var(--skeleton-p) 0;
    background-image: linear-gradient(
      90deg,
      var(--skeleton-c, color-mix(in oklab, currentColor 12%, transparent)) 40%,
      var(--skeleton-h, color-mix(in oklab, currentColor 4%, transparent)) 50%,
      var(--skeleton-c, color-mix(in oklab, currentColor 12%, transparent)) 60%
    );
    background-size: 300% 100%;
    background-attachment: fixed;
  }
  /* only for targets without color-mix() (the defaults have Chrome 109) */
  @supports not (color: color-mix(in oklab, red, red)) {
    ._skeleton-core {
      background-image: linear-gradient(
        90deg,
        var(--skeleton-c, rgb(128 128 128 / 20%)) 40%,
        var(--skeleton-h, rgb(128 128 128 / 8%)) 50%,
        var(--skeleton-c, rgb(128 128 128 / 20%)) 60%
      );
    }
  }
}
@layer _.a {
  ._kkw3xj {
    --skeleton-r: 50%;
  }
  /* ... */
}

skeleton() is "_skeleton-core _skeleton-clock"; each method adds one class. Both kernels are written as styles, no raw CSS: the clock is media("(prefers-reduced-motion: no-preference)")(nest(":root:has(&)")(animation(keyframes({ from: vars({ "--_p": "100%" }), to: vars({ "--_p": "0%" }) }), "1.5s ease-in-out infinite"))), and only its @property registration is a string (atProperty). The keyframes name is a hash of the steps with the scope's name after it.

How it stays in sync

A CSS animation starts when its element gets its style, so a skeleton that mounts 700ms after another runs 700ms behind it, and the page flickers out of step. No skeleton here runs an animation. One animation on :root moves a registered, inherited percentage, --skeleton-p, and every skeleton reads it as its background-position: there is one clock, so there is one phase, in every browser that has @property. The animation is on only while a skeleton is in the document (:root:has(._skeleton-clock)), so a page with none runs nothing; when the last one goes and a new one comes, the clock starts again, with nothing to be out of step with.

The gradient is three viewports wide and fixed to the viewport, so the band sweeps the page once, across every skeleton, as one picture (the same pixel at the same viewport x, as the browser test reads it). Where fixed has no effect (iOS Safari, which BCD marks as a partial implementation) each skeleton has a band of its own size, still on the same clock: in phase in time, not in space.

The other ways were weighed and left: background-attachment: fixed with an animation on each skeleton lines up the space but not the time (each animation still starts at its own mount); a negative animation-delay from document.timeline.currentTime is script; CSS has no document timeline to name in animation-timeline (only scroll and view timelines), and animation-delay: calc() cannot read the time.

Rules

  • Methods, each once, in any order, all optional: radius(<length-percentage [0,∞]>) (0.25rem), color(<color>) (the base, the text colour at 12%), highlight(<color>) (the band, the text colour at 4%). Each is one class that sets a custom property, so a method works under a condition (hover(skeleton().highlight("white"))).
  • Colours. The defaults mix currentColor into transparent, so a skeleton is a darker box on a light page and a lighter one on a dark page, and its band is less of it (a light sweep on light, a dark one on dark). The kernel reads --skeleton-c, --skeleton-h and --skeleton-r with the defaults as fallbacks, so a value set on an ancestor themes every skeleton under it. A background-color of your own shows through a translucent colour.
  • Reduced motion. Under prefers-reduced-motion: reduce the clock does not run: --skeleton-p stays at its initial 100%, which puts the band off the page, and every skeleton is its base colour, still.
  • Conflicts. skeleton() sets the background image, size, position and attachment and the corners: a cn() that holds it and the background shorthand, one of those or a border-radius* property is a build error, derived from the kernel: cssints: skeleton: kernel core sets border-radius, background-image, background-size, background-position, background-attachment (this style also sets background). hover(skeleton()) is a build error too (a kernel cannot be conditional); wrap a method instead.
  • Errors are the grammar's, in the types and at build time: skeleton.radius: unexpected "-4px" in "-4px", expected <length-percentage [0,∞]>, skeleton.color: unexpected "4px" in "4px", expected <color>.

Browsers

bun run test:browsers mounts a 600px skeleton, then 700ms later a 300px one under it and a control that runs the same keyframes on itself, pauses the animations and reads a row of each from a screenshot:

BrowserAnimationsA and B (300 columns)ControlReduced motion
Chrome (installed channel)one, on :root0 columns differ132none, the base colour
Firefox (Playwright build)one, on :root0 columns differ132none, the base colour
WebKit (Playwright trunk build)one, on :root0 columns differ132none, the base colour

@property (Chrome 85, Firefox 128, Safari 16.4) and :has() (Chrome 105, Firefox 121, Safari 15.4) are the kernel's requires: a target that lacks one is a build warning. Without @property the percentage animates as a discrete value and the band never shows (the base colour, still); without :has() nothing animates. For targets without color-mix() the build adds the grey defaults under @supports not, and only then.

Limits

  • The clock animates an inherited custom property on :root, so while a skeleton is mounted the browser updates the style of the whole document on every frame. Fine for a loading page; keep skeletons out of a page that stays busy.
  • One clock for the page: the duration (1.5s), the easing and the direction are the same for every skeleton, and there are no methods for them.
  • Text inside a skeleton shows: write it empty, or hide its children. A transparent text colour on the skeleton makes the defaults transparent too (they mix currentColor); give it skeleton().color(...).highlight(...) then.
  • The band is horizontal, left to right; there is no pulse variant.
cd packages/skeleton && bun run test           # fixtures through the engine (output, conflicts, errors); bun run typecheck for the type test
cd packages/skeleton && bun run test:browsers  # the phase and reduced motion in Chrome, Firefox and WebKit

Credits

The shared clock follows sync-skeleton (MIT, Corbin Crutchley). The CSS is written here; no code is copied.

版本与发布记录

@cssints/skeleton 共发布 42 个版本,首次发布于 2026/10/11;过去 12 个月发布了 42 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.35.1latest2026/11/073 天前
0.35.02026/11/073 天前
0.34.02026/11/073 天前
0.33.02026/11/046 天前
0.32.02026/11/046 天前
0.31.02026/11/047 天前

关于 @cssints/skeleton 的常见问题

@cssints/skeleton 是什么?

Skeleton loaders for cssints whose shimmer is in phase across the page: one animation on :root, read by every skeleton

如何安装 @cssints/skeleton?

在项目目录运行 npm install @cssints/skeleton,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:cssints(^0.35.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cssints/skeleton 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cssints/skeleton 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cssints/skeleton 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cssints/skeleton 可以商用吗?

@cssints/skeleton 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cssints/skeleton 的下载量有多少?

根据 npm 官方下载统计,@cssints/skeleton 在 2026/10/11 – 2026/11/09 这 30 天内被下载 7,253 次,日均约 242 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cssints/skeleton 还在维护吗?

最新版本 0.35.1 发布于 2026/11/07(3 天前);至今共发布 42 个版本,其中过去 12 个月发布了 42 个;首次发布于 2026/10/11。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。