I am building a Micro Frontend (MFE) using React + Vite + Tailwind CSS + Shadcn + (dndKit , sonner). This MFE is being integrated into a legacy Laravel host application.
The project is large (500+ files), and we cannot rewrite the class names manually.
The Problem
I am facing a "two-way CSS conflict" that I cannot seem to solve at build time:
- Outgoing Leak (MFE breaking Host)
My MFE's TailwindPreflight(reset) is leaking out. For example, Tailwind resetsbuttonstyles globally, which strips the styling from the Laravel host's buttons. - Incoming Bleed (Host breaking MFE)
The Laravel host has aggressive global styles (e.g.,div { border: none; }or specifich1styles). These are bleeding into my MFE, causing borders to disappear and layouts to break, even when I use Tailwind utility classes.
Constraints
- No Shadow DOM: we cannot use Shadow DOM because we rely on several external libraries (popups/modals) that break when isolated in a shadow root.
- No manual refactor: the codebase is too large (500+ files) to manually change
className="p-4"toclassName="mfe-p-4"in every file. - Build-time solution required: I need a solution (PostCSS, Vite plugin, or Tailwind config) that automatically scopes all classes, tags, and variables.
What I have tried
unplugin-tailwindcss-mangle: It renamed the classes, but didn't stop the Host's global styles from breaking my UI.postcss-prefix-selector: I tried wrapping everything in a.mfe-rootclass.
If anyone knows the solution for this please provide for you reference check code snippet below:
postcss.config.cjs
module.exports = {
plugins: {
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
'postcss-prefix-selector': {
prefix: '.school-module-app',
exclude: [],
transform(prefix, selector, prefixedSelector, filePath, rule) {
// 1. Transform :root to use our wrapper class
if (selector === ':root') {
return prefix;
}
// 2. Don't double-prefix
if (selector.includes('.school-module-app')) {
return selector;
}
// 3. Handle body/html - scope them under our wrapper
if (selector === 'body' || selector === 'html') {
return prefix;
}
// 4. Handle ::backdrop and other pseudo-elements
if (selector.startsWith('::')) {
return ${prefix} ${selector};
}
// 5. Default: return the prefixed selector
return prefixedSelector;
}
}
}
}
tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
darkMode: ["class"],
important:".school-module-app",
content: ["./pages//*.{ts,tsx}", "./components//.{ts,tsx}", "./app/**/.{ts,tsx}", "./src/**/*.{ts,tsx}"],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
// etc
},
plugins: [require("tailwindcss-animate")],
} satisfies Config;
main.tsx
<div className="school-module-app">
<App />
</div>
index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Definition of the design system. All colors, gradients, fonts, etc should be defined here.
All colors MUST be HSL. Soft & Friendly Theme.
*/
@layer base {
:root {
/* Soft & Friendly Color Palette */
--background: 40 33% 98%;
--foreground: 240 10% 20%;
--card: 0 0% 100%;
--card-foreground: 240 10% 20%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 20%;
/* Warm indigo primary */
--primary: 252 56% 57%;
--primary-foreground: 0 0% 100%;
/* Soft neutral secondary */
--secondary: 240 5% 96%;
--secondary-foreground: 240 10% 30%;
/* Warm muted tones */
--muted: 40 20% 96%;
--muted-foreground: 240 5% 46%;
/* Soft coral accent */
--accent: 15 85% 94%;
--accent-foreground: 15 70% 40%;
--destructive: 0 72% 51%;
--destructive-foreground: 0 0% 100%;
/* Soft borders */
--border: 240 6% 90%;
--input: 240 6% 90%;
--ring: 252 56% 57%;
--radius: 0.75rem;
/* Success, Warning, Info colors */
--success: 152 60% 45%;
--success-foreground: 0 0% 100%;
--warning: 38 92% 50%;
--warning-foreground: 0 0% 100%;
--info: 199 89% 48%;
--info-foreground: 0 0% 100%;
/* Sidebar - Warm neutral */
--sidebar-background: 40 25% 97%;
--sidebar-foreground: 240 10% 30%;
--sidebar-primary: 252 56% 57%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 252 40% 95%;
--sidebar-accent-foreground: 252 56% 45%;
--sidebar-border: 240 6% 92%;
--sidebar-ring: 252 56% 57%;
}
.dark {
--background: 240 10% 8%;
--foreground: 40 20% 95%;
--card: 240 10% 10%;
--card-foreground: 40 20% 95%;
--popover: 240 10% 10%;
--popover-foreground: 40 20% 95%;
--primary: 252 56% 65%;
--primary-foreground: 240 10% 8%;
--secondary: 240 8% 18%;
--secondary-foreground: 40 20% 95%;
--muted: 240 8% 18%;
--muted-foreground: 240 5% 60%;
--accent: 15 40% 20%;
--accent-foreground: 15 70% 80%;
--destructive: 0 62% 45%;
--destructive-foreground: 0 0% 100%;
--border: 240 8% 20%;
--input: 240 8% 20%;
--ring: 252 56% 65%;
--success: 152 50% 40%;
--success-foreground: 0 0% 100%;
--warning: 38 80% 45%;
--warning-foreground: 0 0% 100%;
--info: 199 75% 45%;
--info-foreground: 0 0% 100%;
--sidebar-background: 240 10% 6%;
--sidebar-foreground: 40 20% 90%;
--sidebar-primary: 252 56% 65%;
--sidebar-primary-foreground: 240 10% 8%;
--sidebar-accent: 252 30% 18%;
--sidebar-accent-foreground: 252 50% 80%;
--sidebar-border: 240 8% 18%;
--sidebar-ring: 252 56% 65%;
}
}
@layer base {
- {
@apply border-border;
}
body {
@apply bg-background text-foreground antialiased;
}
/*
Defensive CSS Reset (Scoped)
Shields the app from Host Site's global styles
/
.school-module-app {
/ Enforce app-specific font stack and defaults */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Reset Buttons: Override potential host styles like background: red or border: 5px solid black /
/ Reset Buttons: Override potential host styles like background: red or border: 5px solid black */
:where(.school-module-app) :where(button, [type='button'], [type='reset'], [type='submit']) {
-webkit-appearance: explicit;
appearance: none;
background-color: transparent;
background-image: none;
border-width: 0;
border-style: none;
border-color: transparent;
margin: 0;
padding: 0;
}
/* Reset Inputs which might inherit ugly borders */
:where(.school-module-app) :where(input, optgroup, select, textarea) {
font-family: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
color: inherit;
margin: 0;
border-width: 0;
border-style: none;
outline: none;
background-color: transparent;
box-shadow: none;
}
/* Reset Headings if host adds bottom borders etc */
:where(.school-module-app) :where(h1, h2, h3, h4, h5, h6) {
margin: 0;
font-weight: inherit;
border: none;
}
}