Skip to content

feat(ui): @layer + :where() override contract for grid CSS#160

Merged
blove merged 9 commits into
mainfrom
theming-cascade-override-contract
Jun 6, 2026
Merged

feat(ui): @layer + :where() override contract for grid CSS#160
blove merged 9 commits into
mainfrom
theming-cascade-override-contract

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented Jun 6, 2026

Summary

Makes @pretable/ui's grid CSS reliably overridable by consumers. grid.css now lives in a single @layer pretable cascade layer with every selector :where()-flattened to specificity (0,0,0), so consumer styles win — by layer order, by specificity, or both — without !important or specificity wars. Closes the highest-leverage gap in the consumer theming story (the rest of theming was already built).

Spec: docs/superpowers/specs/2026-06-05-css-cascade-override-contract-design.md
Plan: docs/superpowers/plans/2026-06-05-css-cascade-override-contract.md

What changed

  • grid.css@layer pretable + :where() (packages/ui/src/grid.css). Token files stay unlayered (jsdom can't resolve layered custom props, and token overrides already win by source order). Source order already yields correct state precedence — the one intentional behavior delta is selected/focused now winning over zebra/hover (previously a specificity artifact where they lost).
  • App layer order@layer theme, base, pretable, components, utilities; in the website + bench global CSS. Puts the grid after Tailwind Preflight (reset can't clobber it) but before utilities (utility classes still win). Doubles as the consumer reference.
  • Docs — new theming/cascade-and-overrides.mdx (registered in nav) + updates to the theming index, Tailwind page, override-tokens page, and @pretable/ui README.

Tests

  • Structural guard (packages/ui/src/tests/css-cascade.test.ts) — asserts @layer pretable + every selector wrapped in :where(), so a future raw selector fails CI.
  • Real-browser Playwright cascade test (apps/bench/tests/cascade-override.spec.ts) — injects the unlayered consumer rule before the layered grid.css (so it would fail without the migration) and proves the consumer wins; plus selected-beats-zebra.

Verification

  • @pretable/ui vitest: 16/16 (incl. existing contract/density token-resolution tests — confirms token files stayed unlayered)
  • Playwright cascade: 2/2
  • Website docs validation: 21/21
  • prettier clean

Process

Brainstorm → spec → plan → subagent-driven execution (implementer + spec + code-quality review per task). The final whole-branch review caught the missing nav registration, now fixed.

Out of scope (tracked separately)

Semantic targeting attributes (data-column-id/data-cell-type), brand/semantic token-alias layer, dark-mode for Excel, unstyled/headless variant.

🤖 Generated with Claude Code

blove and others added 9 commits June 5, 2026 21:56
Design for making @pretable/ui's shipped CSS reliably overridable by external
consumers via @layer pretable + :where()-flattened selectors. Both token and
deep-CSS overrides win without specificity tricks; layer order survives Tailwind
Preflight. Behavior-preserving one-time migration of grid.css + theme files,
plus a consumer-facing cascade-and-overrides docs page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4-task TDD plan: migrate grid.css to @layer pretable + :where() (structural test
guard), declare app layer order, Playwright cascade proof, docs. Amends spec:
token files stay unlayered (jsdom can't resolve layered custom props; token
overrides already win by source order).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lectors

Makes every grid default specificity (0,0,0) inside the pretable cascade layer,
so consumer overrides (tokens, deep CSS, or unlayered/later-layer rules) win
without !important or specificity tricks. Token files stay unlayered. Source
order already yields correct state precedence; the only behavior delta is
selected/focused now winning over zebra/hover (previously a specificity
artifact). Guarded by a structural test.
The selector loop skipped only \`@layer pretable\`; a future @media/@supports
inside the layer would have failed the test confusingly. Skip any at-rule
opener instead.
Adds '@layer theme, base, pretable, components, utilities;' to the website and
bench global CSS. Doubles as the reference example for external Tailwind
consumers.
…layer

Loads excel.css + the migrated grid.css in Chromium and asserts an unlayered
consumer rule beats the layered :where() default, and that the selected
background survives the zebra/hover rules.
Test 1 now injects the unlayered consumer rule BEFORE the layered grid.css, so
it fails without the @layer migration (grid would win by source order at equal
specificity) and passes with it. Replaces the vacuous non-empty color check
with a real display:flex sanity assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New 'Cascade & overrides' page plus the layer-order line in the architecture
overview, Tailwind page, and @pretable/ui README.
The new page was reachable only via in-content links; _nav.ts is the nav
source of truth (no MDX auto-discovery), so add it to the Theming section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pretable Ready Ready Preview, Comment Jun 6, 2026 3:27pm

@blove blove enabled auto-merge (squash) June 6, 2026 15:28
@blove blove merged commit 35cfdce into main Jun 6, 2026
13 checks passed
@blove blove deleted the theming-cascade-override-contract branch June 6, 2026 15:29
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 6, 2026

Vercel preview ready

Preview: https://pretable-mdup1rhmk-cacheplane.vercel.app
Commit: d8d81ac8488e32d0a717a25cc68336285e3b69d4

Updated automatically by the deploy-preview job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant