feat(theme): add a translucent loading-placeholder token role - #824
Merged
Conversation
A loading placeholder had no token of its own, so it was built from a surface role. Surface tokens are opaque and tuned for one background, so the choice was only ever right on the layer it was picked against: --bg-surface-overlay (#FAFAFA) on a --bg-surface card (#FFFFFF) is a contrast ratio of 1.02 - effectively invisible. --bg-placeholder and --bg-placeholder-highlight are translucent by design. The fill composites over whatever sits behind it, so one value per mode holds its contrast on canvas, surface, raised and overlay alike (1.19-1.32 across all eight combinations). The highlight is a white alpha in both modes so the sweep lightens the fill rather than replacing it, which keeps the animated and static states on the same base tone.
Skeleton took its fill from --bg-surface-overlay and its sweep from three surface tokens, so in light mode on a card it was very nearly invisible. It now uses --bg-placeholder for the fill and puts the sweep in a gradient whose other stops are transparent, so the animated and static states share one base tone. Adds an OnSurfaces story rendering the placeholder on all four layers: that is the regression, and it is one the unit layer cannot see (the unit DOM is unstyled), so the story is what the visual matrix shoots in both themes. The test asserts the fixture renders and stays axe-clean; the pixels are the visual layer's job.
…on presets DESIGN.md is the catalog the AI reads before choosing a token, so a gap there becomes a wrong token in the next component. Documents the placeholder role with the canonical class string, and bans building a placeholder from an opaque surface. The animation table had drifted from the presets animate.js actually ships: shimmer, the four slide-in/out utilities, both indeterminate-progress sweeps and Tailwind's re-registered loops were all missing, and popup-scale-in was documented at 0.95 when keyframes.js uses 0.9. The consumer theming skill gains the same placeholder guidance, since a consuming app hits this first.
… role Skeleton's fill and sweep now come from --bg-placeholder / --bg-placeholder-highlight instead of surface tokens, so its 18 existing snapshots move, and the new OnSurfaces story adds 6 across the theme x viewport matrix. Generated on ubuntu-latest via the app-storybook-generate-baseline dispatch, the only supported source for the committed contract - darwin rasterizes fonts differently, which is why those baselines are gitignored. Nothing outside components-feedback-skeleton moved, and no baseline was orphaned.
robsongajunior
previously approved these changes
Jul 29, 2026
isaquebock
previously approved these changes
Jul 29, 2026
robsongajunior
approved these changes
Jul 29, 2026
isaquebock
approved these changes
Jul 29, 2026
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A loading placeholder had no token of its own, so it was built from a surface role. Surface tokens are opaque and tuned for one background, so the choice was only ever right on the layer it was picked against:
--bg-surface-overlay(#FAFAFA) on a--bg-surfacecard (#FFFFFF) is a contrast ratio of 1.02 — effectively invisible. Skeleton shipped exactly that.--bg-placeholder(fill) and--bg-placeholder-highlight(sweep) are translucent by design. The fill composites over whatever sits behind it, so one value per mode holds its contrast on--bg-canvas,--bg-surface,--bg-surface-raisedand--bg-surface-overlayalike (ratio1.19–1.32across all eight combinations) instead of being tuned for a single card colour. The highlight is a white alpha in both modes, so the sweep lightens the fill rather than replacing it and the animated and static states share one base tone.Skeleton adopts the pair: the fill stays
background-color, and the sweep moves into a gradient whose other stops aretransparent.Tests
vue-tscclean,catalog:checkin sync (the catalog embeds tokens, so it is regenerated here).OnSurfacesstory test: asserts the fixture renders on all four surface layers and stays axe-clean. Whether the fill reads is deliberately not asserted in the unit layer — that DOM is unstyled, so a colour assertion there would be meaningless. Pixels are Layer 2's job.Visual regression
Baselines are regenerated and committed (ubuntu-latest, via the
app-storybook-generate-baselinedispatch). Delta: 24 files, all Skeleton, nothing else moved, no orphans.components-feedback-skeleton--*snapshots (fill and sweep colours change).components-feedback-skeleton--on-surfaces*across the 6-mode matrix.The regression this fixes is invisible without real styling, so the new
OnSurfacesstory exists specifically to put it in front of the matrix: all four surface layers, both themes. That is the check — the unit layer mounts an unstyled DOM, so it cannot see a contrast problem at all.Notes
packages/theme/dist/v4/globals.{css,scss}, regenerated withpnpm --filter @aziontech/theme build:tokens— not hand-edited.feat/theme-container-scaleregenerates the same files; whichever merges second needs a rebase plus a rebuild..claude/docs/DESIGN.mdcatalog entry for the new role and the matching guidance in the shipped consumer theming skill. That doc is what the AI reads before picking a token, so leaving the role undocumented would just reproduce the bug in the next component. The same commit syncs the animation table, which had drifted from whatanimate.jsactually ships (shimmer, the four slide-in/out utilities and both indeterminate-progress sweeps were missing, andpopup-scale-inwas documented at0.95wherekeyframes.jsuses0.9).