Skip to content

feat(components): add opt-in layered stylesheet variant (CSS cascade layers)#2643

Open
mfal wants to merge 4 commits into
mainfrom
claude/jovial-engelbart-c15355
Open

feat(components): add opt-in layered stylesheet variant (CSS cascade layers)#2643
mfal wants to merge 4 commits into
mainfrom
claude/jovial-engelbart-c15355

Conversation

@mfal

@mfal mfal commented Jul 14, 2026

Copy link
Copy Markdown
Member

What

Adds an opt-in layered variant of the generated stylesheet, organized into
nested CSS cascade layers:

@layer flow.tokens, flow.reset, flow.base, flow.components;

The default stays unlayered and unchanged — this release is non-breaking.

  • Default (unchanged): @mittwald/flow-react-components/all.css,
    @mittwald/flow-stylesheet/css
  • New opt-in layered: @mittwald/flow-react-components/all-layered.css,
    @mittwald/flow-stylesheet/css-layered

Why

With the unlayered default, overriding Flow requires beating its specificity
(!important, nested selectors). The layered variant lets consumers override any
Flow rule with unlayered CSS regardless of specificity, and gives a
language-guaranteed internal order (tokens → reset → base → components) plus clean
interop with consumers' own cascade layers (e.g. Tailwind). Making it opt-in
avoids changing override resolution for existing consumers (an app-wide
* { all: initial }, for example, would wipe layered Flow — so it must not be
forced).

Changes

  • design-tokens (build-tokens.js): custom Style Dictionary format
    css/variables-layered wraps token CSS in @layer flow.tokens (all destinations,
    selector/outputReferences preserved).
  • components:
    • src/styles/index.scss: reset → @layer flow.reset, fonts → @layer flow.base,
      plus the order declaration.
    • dev/vite/flowComponentsLayerPlugin.ts: PostCSS plugin wrapping every
      *.module.(scss|css) under /src/components/ in @layer flow.components.
    • dev/vite/layerOrderPlugin.ts: Vite writeBundle plugin hoisting the order
      declaration to the top after esbuild minification.
    • dev/vite/stylesheetVariantsPlugin.ts: writes the layered output to
      all-layered.css, then strips @layer and overwrites all.css (unlayered
      default).
  • Exports: @mittwald/flow-react-components adds ./all-layered.css;
    @mittwald/flow-stylesheet adds ./css-layered (its build.js copies both
    variants).
  • Docs: ADRs under docs/adr/ (0001 decision, 0002 current build architecture);
    the stylesheet docs page documents both variants and when to pick which.
  • AGENTS.md: convention to run package scripts via corepack pnpm --filter.

Not a breaking change

Existing consumers on all.css / css see no change. The layered variant is
purely additive. When opting into it, an app-side unlayered reset that conflicts
with Flow (e.g. * { all: initial }) should be moved into a layer declared before
flow.

For reviewers

  • Verified via corepack pnpm --filter @mittwald/flow-react-components build and
    --filter @mittwald/flow-stylesheet build: all.css / styles.css contain
    no @layer; all-layered.css / styles-layered.css start with the order
    declaration and contain the @layer flow.* blocks.
  • nx build components could not fully run in the working environment (icon codegen
    tasks need network / full workspace) — please double-check in CI.
  • Non-blocking follow-ups (see ADR 0001 "Next steps"): sourcemap accuracy after the
    writeBundle rewrite; the many individual @layer flow.components blocks could be
    merged.

🤖 Generated with Claude Code

Wrap the generated stylesheet in nested CSS cascade layers
(@layer flow.tokens, flow.reset, flow.base, flow.components) so consumers can
override Flow styles with unlayered CSS without specificity hacks or !important.

- design-tokens: new Style Dictionary format `css/variables-layered` wraps token
  CSS in @layer flow.tokens (all destinations, selectors preserved)
- components: index.scss assigns the reset to flow.reset and fonts to flow.base
  and declares the layer order; a PostCSS plugin wraps component modules
  (.module.scss/.module.css) in flow.components; a Vite writeBundle plugin hoists
  the layer-order declaration to the top (esbuild drops it during minify)
- additionally export an UNLAYERED variant as an opt-out for apps that cannot
  adopt layers (e.g. a global `* { all: initial }` reset that would otherwise
  wipe layered Flow styles): @mittwald/flow-react-components/all.unlayered.css
  and @mittwald/flow-stylesheet/css-unlayered, derived by stripping @layer from
  the built all.css
- docs: ADRs (docs/adr/0001, 0002), the stylesheet docs page, and MIGRATION.md
- add AGENTS.md: run package scripts via `corepack pnpm --filter <pkg> <script>`

Co-Authored-By: Claude Opus 4.8 <[email protected]>
mfal and others added 2 commits July 14, 2026 16:08
Translate the two cascade-layer ADRs from German to English and rename the files
to English slugs (cross-links updated):

- 0001-css-cascade-layers-im-stylesheet.md -> 0001-css-cascade-layers-in-the-stylesheet.md
- 0002-stylesheet-generierung.md           -> 0002-stylesheet-generation.md

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Flip the two stylesheet variants so adopting this release is non-breaking:

- `all.css` / `@mittwald/flow-stylesheet/css` stays UNLAYERED (unchanged behavior).
- The layered variant moves to `all-layered.css` /
  `@mittwald/flow-stylesheet/css-layered` as an opt-in export.

Both variants come from the same build: the pipeline layers the CSS at the source
(that is the layered output), and the default is that output with the `@layer`
wrappers stripped. Renames the Vite plugin `unlayeredCssPlugin` ->
`stylesheetVariantsPlugin` (writes `all-layered.css`, strips `@layer` into
`all.css`). Updates package exports and the flow-stylesheet copy step accordingly.

Docs updated to the non-breaking, opt-in framing (ADR 0001, stylesheet docs page);
removes the now-obsolete breaking-change entry from MIGRATION.md.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@mfal mfal changed the title feat(components): organize generated stylesheet into CSS cascade layers feat(components): add opt-in layered stylesheet variant (CSS cascade layers) Jul 14, 2026
@mfal

mfal commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved and merged latest origin/main into this branch to clear the conflicts in commit 4ab255a.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in layered stylesheet variant for Flow using CSS cascade layers (tokens → reset → base → components), while keeping the existing default stylesheet path and behavior intended to remain unlayered.

Changes:

  • Generates and exports new layered CSS entrypoints: all-layered.css / css-layered alongside existing all.css / css.
  • Introduces build-time layering: tokens wrapped in @layer flow.tokens, component CSS modules wrapped in @layer flow.components, and a writeBundle hoist to ensure the layer order declaration is at the top.
  • Adds ADR documentation and updates the docs “stylesheet” page to explain the new variant and when to choose it.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/stylesheet/package.json Adds ./css-layered export pointing at dist/styles-layered.css.
packages/stylesheet/build.js Copies both all.css and all-layered.css into dist/styles.css and dist/styles-layered.css.
packages/design-tokens/build-tokens.js Adds a layered Style Dictionary format and switches CSS output to it.
packages/components/vite.build.config.ts Adds PostCSS + Vite plugins to wrap CSS in layers and emit split variants.
packages/components/src/styles/index.scss Declares layer order and moves reset/fonts into flow.reset / flow.base layers.
packages/components/package.json Exports the new ./all-layered.css artifact.
packages/components/dev/vite/stylesheetVariantsPlugin.ts Writes all-layered.css and strips @layer to overwrite all.css (default).
packages/components/dev/vite/layerOrderPlugin.ts Hoists the layer order declaration to the top of emitted CSS.
packages/components/dev/vite/flowComponentsLayerPlugin.ts Wraps component CSS-module output in @layer flow.components.
docs/adr/0001-css-cascade-layers-in-the-stylesheet.md Records the cascade-layers decision, rationale, and implementation details.
docs/adr/0002-stylesheet-generation.md Documents the existing stylesheet generation architecture (retroactively).
apps/docs/src/content/01-get-started/stylesheet/index.mdx Documents the new css-layered usage and guidance for consumers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +14 to +17
for (const asset of Object.values(bundle)) {
if (asset.type !== "asset" || !asset.fileName.endsWith(".css")) {
continue;
}
Comment on lines +49 to +51
const nestedVariables = selector
.reverse()
.reduce(
Comment on lines 144 to 146
{
format: "css/variables",
format: "css/variables-layered",
destination: `css/${destination}.css`,
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.

3 participants