Skip to content

fix(styles): isolate module's Tailwind utilities in a lower cascade layer#17

Merged
klinux merged 2 commits into
mainfrom
fix/css-layer-cascade
May 11, 2026
Merged

fix(styles): isolate module's Tailwind utilities in a lower cascade layer#17
klinux merged 2 commits into
mainfrom
fix/css-layer-cascade

Conversation

@klinux
Copy link
Copy Markdown
Contributor

@klinux klinux commented May 11, 2026

Summary

  • Modules cloned from this template were silently breaking the Backoffice Shell sidebar — clicking the toggle flipped data-state to expanded but the sidebar element stayed display: none. Reproduced live in front-scholarship-funds (fix shipped there: corabank/front-scholarship-funds#1).
  • This is a Tailwind 4 cascade-order bug between the shell's stylesheet and any module's stylesheet sharing the same @layer utilities.
  • Fix: route the module's auto-generated utilities into a dedicated lower-priority layer (module-utilities) so the shell's responsive utilities (.md\:block, .md\:flex, etc) win regardless of <style> injection order.

What changed

src/styles/globals.css:

-@layer theme, base, components, utilities;
-@import "tailwindcss/theme.css" layer(theme);
-@import "tailwindcss/utilities.css" layer(utilities);
+@layer module-theme, module-utilities, theme, base, components, utilities;
+@import "tailwindcss/theme.css" layer(module-theme);
+@import "tailwindcss/utilities.css" layer(module-utilities);

The top-of-file comment is expanded to document this gotcha alongside the existing Preflight one — so devs cloning the template don't have to rediscover it.

Why this is the right fix

Tailwind 4 emits only the utilities each project actually uses. The shell uses hidden md:block (shadcn sidebar), so the shell's CSS has both .hidden and .md\:block. A typical module doesn't use md:block, so the module's CSS has only .hidden. vite-plugin-css-injected-by-js appends the module's <style> after the shell's stylesheet, and inside a shared @layer utilities the later source-order rule wins — so the module's .hidden defeats the shell's .md\:block at any viewport width.

Splitting into a lower-priority module-utilities layer makes the shell's utilities win on collision via layer order (not source order), which is exactly the contract we want: the shell owns chrome, the module styles its own content.

Manual utilities a module adds later via @layer utilities { ... } still go to the high-priority global utilities layer — module-only selectors (e.g. custom gradient-* utilities) keep their precedence over anything the shell might one day define with the same name.

Test plan

  • Clone this template into a new module, run npm install, npm run build — should succeed unchanged.
  • Mount the new module in the Backoffice Shell. Open the sidebar with the trigger — it should open with content (not stay collapsed off-screen).
  • Verify data-state on [data-slot="sidebar"] transitions correctly between collapsedexpanded.
  • Confirm shadcn primitives in the module (Button, Card, Input, Select) still render with their intended colors via shell tokens.
  • Apply the same change to front-scholarship-funds (done in linked PR) and verify side-by-side.

Related

  • corabank/front-scholarship-funds#1 — same fix in a real module, where this bug was diagnosed.

🤖 Generated with Claude Code

…ayer

Modules generated from this template were silently breaking the
Backoffice Shell sidebar: when the module was active, clicking the
sidebar trigger flipped the `data-state` to `expanded` but the sidebar
element stayed at `display: none`. Reproduced live in
`front-scholarship-funds` (fix shipped there as well).

Root cause: cascade order on `.hidden`. The shell uses `hidden md:block`
(shadcn sidebar) and emits both `.hidden` and `.md\:block` in its
stylesheet. The module does not use `md:block`, so Tailwind 4 emits
only `.hidden` for the module. `vite-plugin-css-injected-by-js`
appends the module's <style> after the shell's stylesheet, and inside
the shared `@layer utilities` the later-source rule wins — so the
module's `.hidden` defeats the shell's `.md\:block`, even on a wide
viewport.

Fix: declare the module's auto-generated utilities in a dedicated
`module-utilities` layer that sits below the global `utilities` layer
the shell occupies. The shell's `.md\:block` now wins by layer
priority regardless of <style> injection order. Manual utilities a
module adds later via `@layer utilities { ... }` keep the
high-priority layer, so module-specific visuals still apply where
intended. `@theme` gets the same treatment (`module-theme`) for
symmetry — module theme tokens point at shell tokens via `var(...)`,
so shell `theme` rules winning on conflicts is the desired behavior.

This is the same family of bug as the existing Preflight gotcha
already documented at the top of globals.css; the comment is expanded
to cover both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 11, 2026 16:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Este PR ajusta a forma como o template do módulo importa o Tailwind para evitar colisões de utilitários com o Backoffice Shell quando o CSS do módulo é injetado depois do CSS do shell, garantindo que utilitários responsivos do shell (ex.: md:block) prevaleçam sobre utilitários “base” do módulo (ex.: hidden).

Changes:

  • Introduz camadas de cascata dedicadas (module-theme e module-utilities) com menor prioridade.
  • Redireciona os imports tailwindcss/theme.css e tailwindcss/utilities.css para essas novas camadas.
  • Expande a documentação em globals.css para registrar o gotcha de ordem de cascata (além do já existente sobre Preflight).

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

Comment thread src/styles/globals.css Outdated
Comment on lines +9 to +22
* css-injected-by-js, that reset would apply to every element on
* the page — including the shell's chrome. Symptom: sidebar/header
* borders disappear, layout shifts. Fix: import only `theme` and
* `utilities`, never the full `tailwindcss` entry. The shell ships
* its own Preflight once for the whole page.
*
* 2) Cascade order on shared utilities (`.hidden`, `.flex`, `.block`).
* Tailwind 4 emits only the utilities each project actually uses.
* The shell uses `hidden md:block` (shadcn sidebar) but a module
* typically does not — so the shell's stylesheet contains BOTH
* `.hidden` and `.md\:block`, while the module's stylesheet
* contains ONLY `.hidden`. css-injected-by-js appends the module's
* <style> after the shell's stylesheet, and within a shared
* `@layer utilities` the later-source rule wins. Result: the
Comment thread src/styles/globals.css Outdated
Comment on lines +9 to +22
* css-injected-by-js, that reset would apply to every element on
* the page — including the shell's chrome. Symptom: sidebar/header
* borders disappear, layout shifts. Fix: import only `theme` and
* `utilities`, never the full `tailwindcss` entry. The shell ships
* its own Preflight once for the whole page.
*
* 2) Cascade order on shared utilities (`.hidden`, `.flex`, `.block`).
* Tailwind 4 emits only the utilities each project actually uses.
* The shell uses `hidden md:block` (shadcn sidebar) but a module
* typically does not — so the shell's stylesheet contains BOTH
* `.hidden` and `.md\:block`, while the module's stylesheet
* contains ONLY `.hidden`. css-injected-by-js appends the module's
* <style> after the shell's stylesheet, and within a shared
* `@layer utilities` the later-source rule wins. Result: the
Addresses Copilot review on PR #17 — the comment referred to the
plugin as `css-injected-by-js` while the actual package is
`vite-plugin-css-injected-by-js`. Standardizes on the full name so
the comment is greppable against package.json / vite.config.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@klinux
Copy link
Copy Markdown
Contributor Author

klinux commented May 11, 2026

@copilot review

Copilot finished work on behalf of klinux May 11, 2026 17:20
@klinux klinux merged commit 9702ab7 into main May 11, 2026
3 checks passed
@klinux klinux deleted the fix/css-layer-cascade branch May 11, 2026 17:29
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.

2 participants