Skeleton items prop, SkeletonGroup removed (DES-127)
Version: 0.2.0 · Type: ✨ Feature
DES-127 (sub issue of DES-124 Skeleton)
Continuation of WEB-889 ([Skeleton] corner radius / color bound to tokens + card-level shimmer sweep)
Problem
The card-level continuous shimmer sweep landed yesterday (2026-06-16) was carried by a standalone SkeletonGroup component, which forced callers to import and nest both Skeleton and SkeletonGroup. Since SkeletonGroup is not yet used by any business code, we are taking the chance to fold the "multi-block continuous shimmer" capability into Skeleton's own items prop, reducing both the public API surface and the nesting overhead.
Changed Files
src/components/Skeleton/Skeleton.tsxsrc/components/Skeleton/SkeletonGroup.tsx(deleted)src/components/Skeleton/skeleton-group-context.ts(deleted)src/components/Skeleton/index.tssrc/components/index.tssrc/components/ui/skeleton.tsx(comments)src/components/Skeleton/__tests__/Skeleton.test.tsxscripts/a11y/a11y-audit-config.tspackages/design-site/docs/components/atomic/progress-indicators/skeleton.mdxpackages/design-site/i18n/zh-CN/.../skeleton.mdxpackages/design-site/src/theme/ReactLiveScope/index.tsx
Changes
Skeletongains anitems?: SkeletonItem[]prop (SkeletonItem={ className?: string; key?: Key }):- Without
items→ single-block skeleton, behaving exactly as before. - With
items→ the outerdivacts as the whole "canvas", rendering multiple blocks per item, with one light sweeping continuously across blocks (the gaps between blocks are not swept); block layout is controlled bySkeleton's ownclassName(flex / grid / gap).
- Without
- Removed the
SkeletonGroupcomponent andSkeletonGroupContext, dropped theSkeletonGroup/SkeletonGroupPropsexports; added theSkeletonItemtype export. - Moved the original
SkeletonGroupgeometry-measurement logic (ResizeObserver+MutationObserver+applyShimmerGeometry) intoSkeleton: in items mode the outer canvas measures the child-block offsets, in single-block mode it measures against its own size. - The three doc-site demos (card-level shimmer / text rows / card) are switched to the
Skeleton itemsusage; the Props table drops theSkeletonGroup Propssection and addsitems/SkeletonItemdocumentation. - The offline a11y audit adds a
Skeleton (items)multi-block fixture (previously only the single block was registered). - Unit tests rewritten accordingly: removed the
SkeletonGrouptest block, added items multi-block behaviors (render per item, className pass-through, the canvas carries no skeleton visuals, empty array, ref) and the axe assertion for "decorative skeleton placed inside arole="status"loading container".
Notes
- The shimmer geometry CSS variables and the singleton
<style>injection (skeleton-shimmer.ts) stay unchanged; only the measurement entry point moves fromSkeletonGrouptoSkeleton. - Single-block and multi-block share the same
useEffect: single-block does not attach aMutationObserver(no child node additions/removals), only multi-block listens for child additions/removals and observes new nodes as they appear.
2026-06-17 Skeleton gains direction and item.children nested layout (added same day)
Changes
Skeletongainsdirection?: 'horizontal' | 'vertical'(defaults tovertical, only effective in items mode): it automatically addsflex flex-row/flex-colto the canvas, while details likegap/alignare still left toclassName(merged viamergeClass, soclassNamecan override, e.g. switching togrid).SkeletonItemgainschildren?: SkeletonItem[]anddirection?: a leaf item (no children) renders one skeleton block; an item withchildrendoes not draw a skeleton block and instead acts as a sub-container, recursively laying out its children per its owndirection(defaults tovertical). This is used for mixed layouts like "avatar on the left + two stacked rows on the right".- Rendering changes to a recursive
renderItem; the geometry-measurement logic needs no change — it still usesquerySelectorAll(SKELETON_SELECTOR)to grab all leaf skeleton blocks within the canvas, so the nested structure is naturally compatible and one light sweeps continuously across all leaf blocks. - Added the
SkeletonDirectiontype export. - The doc site adds
directiondocumentation and a "nested layout (avatar + two rows)" demo; the Props table addsdirectionandSkeletonItem.children/direction. - The offline a11y audit's
Skeleton (items)fixture is changed to a nested structure (horizontal canvas + vertical sub-container). - Unit tests add 7 cases covering direction default/horizontal/className override, nested children (leaf-block count, branch acting as sub-container, sub-container direction default value).
2026-06-17 Fix single-block mode swallowing children + rationale for removing SkeletonGroup (added same day, review follow-up)
Problem
- After the refactor,
Skeletondestructuredchildrenout of props, but the single-block modereturn <SkeletonUI ... />did not passchildrenback.SkeletonPropsstill extendsHTMLAttributes<HTMLDivElement>(so passingchildrenis type-allowed), which caused the children of legacy usage like<Skeleton>content</Skeleton>to be silently dropped — a behavior regression.
Changed Files
src/components/Skeleton/Skeleton.tsxsrc/components/Skeleton/__tests__/Skeleton.test.tsx
Changes
- Single-block mode adds
{children}back, rendering it as the underlying div's child node, restoring the pre-refactor behavior. - Unit tests add a "single-block mode children pass-through" assertion to prevent another regression.
Rationale for removing SkeletonGroup without a deprecated path
Review raised that "SkeletonGroup is already exported from the top level of @plaud/design, making it public API; removal should keep a deprecated compatibility wrapper for one version." Based on the facts of this repo, this change confirms direct removal with no compatibility layer, on the following grounds:
SkeletonGrouplanded on 2026-06-16, survived for less than 24 hours, and a repo-wide grep finds zero business references.@plaud/designis an internal source package within the monorepo (consumers go directly throughsrc/index.ts), so there is no published contract with unknown external consumers.- Therefore it is classified as an "internal adjustment to an unreleased capability", not counted as a breaking change, and needs no deprecated wrapper or major bump.