Skip to content

feat(theme): add a translucent loading-placeholder token role - #824

Merged
isaquebock merged 6 commits into
mainfrom
feat/theme-placeholder-tokens
Jul 29, 2026
Merged

feat(theme): add a translucent loading-placeholder token role#824
isaquebock merged 6 commits into
mainfrom
feat/theme-placeholder-tokens

Conversation

@gabriel-lisboa-azion

@gabriel-lisboa-azion gabriel-lisboa-azion commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

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-surface card (#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-raised and --bg-surface-overlay alike (ratio 1.191.32 across 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 are transparent.

Tests

  • Full webkit suite green (83 files / 1693 passing, 6 skipped), eslint + vue-tsc clean, catalog:check in sync (the catalog embeds tokens, so it is regenerated here).
  • New OnSurfaces story 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-baseline dispatch). Delta: 24 files, all Skeleton, nothing else moved, no orphans.

  • 18 moved — the existing components-feedback-skeleton--* snapshots (fill and sweep colours change).
  • 6 newcomponents-feedback-skeleton--on-surfaces* across the 6-mode matrix.

The regression this fixes is invisible without real styling, so the new OnSurfaces story 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

  • Touches packages/theme/dist/v4/globals.{css,scss}, regenerated with pnpm --filter @aziontech/theme build:tokens — not hand-edited. feat/theme-container-scale regenerates the same files; whichever merges second needs a rebase plus a rebuild.
  • Includes the .claude/docs/DESIGN.md catalog 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 what animate.js actually ships (shimmer, the four slide-in/out utilities and both indeterminate-progress sweeps were missing, and popup-scale-in was documented at 0.95 where keyframes.js uses 0.9).
  • No new dependency. No breaking change.

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
robsongajunior previously approved these changes Jul 29, 2026
isaquebock
isaquebock previously approved these changes Jul 29, 2026
@robsongajunior
robsongajunior dismissed stale reviews from isaquebock and themself via 3064e2c July 29, 2026 14:10
@isaquebock
isaquebock merged commit e9164d6 into main Jul 29, 2026
23 checks passed
@robsongajunior
robsongajunior deleted the feat/theme-placeholder-tokens branch July 29, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants