Module 24: Styling & Design System Integration (Senior/Enterprise)
Chapter 24 • Advanced
Styling & Design System Integration (Senior/Enterprise)
The Real Problem
Scenario: Products app uses blue buttons. Checkout app uses green buttons. Admin app uses red buttons. Users see inconsistent UI.
The Question: How do I maintain consistent styling across multiple micro frontends while allowing teams to work independently?
This is design system architecture.
The Production Reality
Styling Challenges
- Consistency - All apps should look the same
- Independence - Teams need to work independently
- Theme Management - Support light/dark themes
- CSS Isolation - Styles shouldn't leak between apps
- Design System - Shared components and tokens
The Question: How do we solve each challenge?
Strategy 1: Shared Design System Package
The Pattern
Create a shared design system package. All apps consume it.
Step 1: Create Design System Package
# Create design system package
mkdir design-system
cd design-system
npm init -y
# Structure
design-system/
├── src/
│ ├── tokens/
│ │ ├── colors.ts
│ │ ├── typography.ts
│ │ └── spacing.ts
│ ├── components/
│ │ ├── button/
│ │ ├── input/
│ │ └── card/
│ └── styles/
│ └── global.css
└── package.json
Step 2: Define Design Tokens
// design-system/src/tokens/colors.ts
export const colors = {
primary: {
main: '#1976d2',
light: '#42a5f5',
dark: '#1565c0'
},
secondary: {
main: '#dc004e',
light: '#e33371',
dark: '#9a0036'
},
background: {
default: '#ffffff',
paper: '#f5f5f5'
},
text: {
primary: '#212121',
secondary: '#757575'
}
} as const
// design-system/src/tokens/typography.ts
export const typography = {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
h1: {
fontSize: '2.5rem',
fontWeight: 500,
lineHeight: 1.2
},
h2: {
fontSize: '2rem',
fontWeight: 500,
lineHeight: 1.3
},
body1: {
fontSize: '1rem',
fontWeight: 400,
lineHeight: 1.5
}
} as const
Step 3: Create Shared Components
// design-system/src/components/button/button.component.ts
@Component({
selector: 'ds-button',
template: `
<button
[class]="'ds-button ds-button--' + variant"
[disabled]="disabled"
(click)="onClick.emit($event)">
<ng-content></ng-content>
</button>
`,
styles: [`
.ds-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-family: var(--ds-font-family);
font-size: var(--ds-font-size-body1);
cursor: pointer;
transition: background-color 0.2s;
}
.ds-button--primary {
background-color: var(--ds-color-primary-main);
color: white;
}
.ds-button--primary:hover:not(:disabled) {
background-color: var(--ds-color-primary-dark);
}
.ds-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`]
})
export class ButtonComponent {
@Input() variant: 'primary' | 'secondary' = 'primary'
@Input() disabled = false
@Output() onClick = new EventEmitter<MouseEvent>()
}
Step 4: Consume in Apps
// products-app/package.json
{
"dependencies": {
"@company/design-system": "^1.0.0"
}
}
// products-app/src/app/app.module.ts
import { DesignSystemModule } from '@company/design-system'
@NgModule({
imports: [DesignSystemModule],
// ...
})
export class AppModule {}
// products-app/src/app/products/products.component.html
<ds-button variant="primary" (onClick)="addToCart()">
Add to Cart
</ds-button>
Strategy 2: CSS Variables for Theming
The Pattern
Use CSS variables for theme values. Change variables to change theme.
Step 1: Define CSS Variables
/* design-system/src/styles/theme.css */
:root {
/* Colors */
--ds-color-primary-main: #1976d2;
--ds-color-primary-light: #42a5f5;
--ds-color-primary-dark: #1565c0;
--ds-color-secondary-main: #dc004e;
--ds-color-secondary-light: #e33371;
--ds-color-secondary-dark: #9a0036;
--ds-color-background-default: #ffffff;
--ds-color-background-paper: #f5f5f5;
--ds-color-text-primary: #212121;
--ds-color-text-secondary: #757575;
/* Typography */
--ds-font-family: "Roboto", "Helvetica", "Arial", sans-serif;
--ds-font-size-h1: 2.5rem;
--ds-font-size-h2: 2rem;
--ds-font-size-body1: 1rem;
/* Spacing */
--ds-spacing-xs: 4px;
--ds-spacing-sm: 8px;
--ds-spacing-md: 16px;
--ds-spacing-lg: 24px;
--ds-spacing-xl: 32px;
}
Step 2: Dark Theme
/* design-system/src/styles/theme-dark.css */
[data-theme="dark"] {
--ds-color-primary-main: #42a5f5;
--ds-color-primary-light: #64b5f6;
--ds-color-primary-dark: #1976d2;
--ds-color-background-default: #121212;
--ds-color-background-paper: #1e1e1e;
--ds-color-text-primary: #ffffff;
--ds-color-text-secondary: #b0b0b0;
}
Step 3: Use in Components
/* products-app/src/app/products/products.component.css */
.product-card {
background-color: var(--ds-color-background-paper);
padding: var(--ds-spacing-md);
border-radius: 8px;
}
.product-title {
color: var(--ds-color-text-primary);
font-size: var(--ds-font-size-h2);
font-family: var(--ds-font-family);
}
.product-price {
color: var(--ds-color-primary-main);
font-size: var(--ds-font-size-body1);
}
Step 4: Toggle Theme
// Host App - Theme service
@Injectable({ providedIn: 'root' })
export class ThemeService {
private theme$ = new BehaviorSubject<'light' | 'dark'>('light')
constructor() {
// Load saved theme
const saved = localStorage.getItem('theme') as 'light' | 'dark'
if (saved) {
this.setTheme(saved)
}
}
setTheme(theme: 'light' | 'dark') {
document.documentElement.setAttribute('data-theme', theme)
this.theme$.next(theme)
localStorage.setItem('theme', theme)
// Notify remotes
this.eventBus.emit({
event: 'theme:changed',
version: '1.0.0',
data: { theme }
})
}
getTheme(): Observable<'light' | 'dark'> {
return this.theme$.asObservable()
}
}
Strategy 3: CSS Isolation
The Problem
Products app styles leak into Checkout app.
Solution 1: ViewEncapsulation (Angular Default)
// Angular automatically scopes styles
@Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css'],
encapsulation: ViewEncapsulation.Emulated // Default
})
export class ProductsComponent {}
Why This Works:
- ✅ Styles scoped to component
- ✅ No style leakage
- ✅ Works automatically
Solution 2: Shadow DOM (True Isolation)
@Component({
selector: 'app-products',
templateUrl: './products.component.html',
styleUrls: ['./products.component.css'],
encapsulation: ViewEncapsulation.ShadowDom
})
export class ProductsComponent {}
Why This Works:
- ✅ True isolation
- ✅ Styles can't leak
- ⚠️ CSS variables don't work (need special handling)
Solution 3: CSS Modules
// products.component.css
.productCard {
background: white;
padding: 16px;
}
// products.component.ts
@Component({
template: `
<div [class]="styles.productCard">
Product
</div>
`
})
export class ProductsComponent {
styles = styles // Import from CSS module
}
Strategy 4: Shared Styles Package
The Pattern
Package shared styles. Apps import what they need.
Step 1: Create Styles Package
// shared-styles/src/reset.css
/* CSS Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
// shared-styles/src/utilities.css
.ds-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--ds-spacing-md);
}
.ds-flex {
display: flex;
}
.ds-flex-center {
display: flex;
align-items: center;
justify-content: center;
}
Step 2: Import in Apps
// products-app/src/styles.css
@import '@company/shared-styles/reset.css';
@import '@company/shared-styles/utilities.css';
@import '@company/design-system/theme.css';
Strategy 5: Design System as Remote
The Pattern
Expose design system as Module Federation remote.
Step 1: Configure Design System as Remote
// design-system/webpack.config.js
new ModuleFederationPlugin({
name: 'design-system',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/button/button.component.ts',
'./Input': './src/components/input/input.component.ts',
'./Card': './src/components/card/card.component.ts',
'./Theme': './src/styles/theme.css'
},
shared: {
'@angular/core': { singleton: true, strictVersion: true }
}
})
Step 2: Consume in Apps
// products-app/webpack.config.js
new ModuleFederationPlugin({
name: 'products',
remotes: {
'design-system': 'design-system@https://design-system.example.com/remoteEntry.js'
}
})
// products-app/src/app/products/products.component.ts
import { ButtonComponent } from 'design-system/Button'
@Component({
imports: [ButtonComponent],
template: `
<ds-button>Add to Cart</ds-button>
`
})
export class ProductsComponent {}
Production Checklist
- [ ] Design system package created
- [ ] Design tokens defined
- [ ] Shared components built
- [ ] CSS variables for theming
- [ ] Theme switching implemented
- [ ] CSS isolation configured
- [ ] Shared styles packaged
- [ ] Documentation for teams
Key Takeaways
- Shared design system - Single source of truth
- CSS variables - Easy theming
- CSS isolation - Prevent style leakage
- Package styles - Reusable across apps
- Documentation - Teams need guidelines
Remember: Consistent styling requires architecture, not just CSS.
The next module: Type-safe Module Federation.
Related Tutorials
Previous: Module 23: Migration from Monolith to Micro Frontends (Senior/Enterprise)
Learn about module 23: migration from monolith to micro frontends (senior/enterprise)
Next: Module 25: Type-Safe Module Federation (Senior/Enterprise)
Continue with module 25: type-safe module federation (senior/enterprise)