Material 3 Migration Plan¶
Plan for migrating the SyRF frontend from Material Design 2 (M2) to Material Design 3 (M3).
Current State¶
| Aspect | Current |
|---|---|
| Angular Material | v21 |
| Theme System | M2 via mat.m2-define-light-theme() |
| Custom Palettes | 2 (primary navy, secondary gray) |
| Component Themes | 11 custom mixin files |
| Legacy Bootstrap | 3 files (buttons, panels, progress bars) |
| Dark Theme | Class-based toggle (.global-dark-theme) |
| Design Tokens | Introduced (Feb 2026) |
| Hardcoded Colors | ~80 instances across components |
What Changes in M3¶
Material 3 introduces a fundamentally different color and theming system:
Color System¶
| M2 Concept | M3 Equivalent |
|---|---|
| Primary palette (50-900 hues) | Tonal palettes (0-100 tone) |
| Accent/Secondary palette | Secondary + Tertiary palettes |
mat.m2-define-palette() |
mat.m3-define-theme() with color roles |
mat.m2-get-color-from-palette() |
CSS custom properties (--mat-*) |
| Fixed hue selection (500, A200) | Dynamic tone mapping |
Theme API¶
// M2 (current)
$theme: mat.m2-define-light-theme((
color: (primary: $primary, accent: $accent, warn: $warn)
));
@include mat.all-component-themes($theme);
// M3 (target)
$theme: mat.m3-define-theme((
color: (
primary: mat.$violet-palette, // or custom palette
tertiary: mat.$green-palette,
)
));
@include mat.all-component-themes($theme);
Component Theme Mixins¶
M3 themes expose CSS custom properties instead of requiring Sass mixins:
// M2 (current) - Sass mixin approach
@mixin color($config) {
$primary: map.get($config, primary);
.my-element {
color: mat.m2-get-color-from-palette($primary);
}
}
// M3 (target) - CSS custom properties approach
.my-element {
color: var(--mat-sys-primary);
background-color: var(--mat-sys-surface);
}
Key M3 Color Roles¶
| Role | Usage |
|---|---|
--mat-sys-primary |
Primary brand color |
--mat-sys-on-primary |
Text on primary surfaces |
--mat-sys-primary-container |
Container using primary tone |
--mat-sys-secondary |
Secondary accent |
--mat-sys-tertiary |
Tertiary accent (new in M3) |
--mat-sys-error |
Error states |
--mat-sys-surface |
Default background |
--mat-sys-surface-variant |
Alternative surface |
--mat-sys-on-surface |
Text on surfaces |
--mat-sys-outline |
Borders and dividers |
--mat-sys-outline-variant |
Subtle borders |
Visual Changes¶
- Buttons: Tonal fill, elevated, outlined variants replace flat/raised/stroked
- FABs: Tertiary color by default, new "small" and "large" variants
- Cards: Elevated, filled, outlined variants
- Chips: Updated shape and sizing
- Navigation: Rail and drawer get new visual treatment
- Shape:
shapesystem replaces ad-hoc border-radius values - Typography: Type scale renamed (e.g., "Display Large" replaces "Headline 1")
Migration Strategy¶
Approach: Incremental M2-to-M3 with Compatibility Layer¶
Angular Material provides a compatibility path where M2 and M3 can coexist. The migration should be done incrementally, not as a big-bang rewrite.
Prerequisites (Do First)¶
These must be completed before the M3 migration begins:
- Eliminate hardcoded colors - Replace all ~80 hardcoded color values with theme palette references or design tokens
- Remove legacy Bootstrap - Migrate all
.btn-*,.panel-*,.progress-bar-*usage to Material components - Fix !important declarations - Resolve all ~42
!importanthacks - Remove inline styles - Move all ~61 inline
style=""attributes to component SCSS - Clean up MDC migration TODOs - Update all ~20 selectors targeting deprecated internal classes
- Adopt design tokens - All components should use
_design-tokens.scssfor non-theme values
Phase 1: Foundation (Prerequisite Cleanup)¶
Goal: Clean codebase ready for M3 migration.
Step 1.1: Audit and Replace Hardcoded Colors¶
For each component with hardcoded colors, determine the semantic intent and replace:
// BEFORE (hardcoded)
.auth-alert-banner {
background-color: #a60000;
color: white;
}
// AFTER (design tokens for non-theme values)
@use 'src/global-styles/design-tokens' as tokens;
.auth-alert-banner {
background-color: tokens.$color-auth-alert-bg;
color: tokens.$color-text-on-primary;
}
For theme-dependent colors in theme mixins:
// BEFORE (hardcoded in theme file)
#content { background: #ffffff; }
.disabled { background: #f2f2f2; color: #ff6565; }
// AFTER (palette functions)
#content { background-color: mat.m2-get-color-from-palette($background, card); }
.disabled {
background-color: mat.m2-get-color-from-palette($background, hover);
color: mat.m2-get-color-from-palette($warn, lighter);
}
Step 1.2: Remove Legacy Bootstrap¶
For each Bootstrap class still in use:
| Bootstrap | Material Replacement |
|---|---|
.btn.btn-primary |
<button mat-raised-button color="primary"> |
.btn.btn-success |
<button mat-raised-button color="accent"> or custom |
.btn.btn-danger |
<button mat-raised-button color="warn"> |
.btn.btn-link |
<button mat-button> |
.panel |
<mat-card> |
.panel-heading |
<mat-card-header> |
.progress |
<mat-progress-bar> |
After all usages are migrated, delete:
- src/global-styles/legacy-bootstrap/buttons.scss
- src/global-styles/legacy-bootstrap/panel.scss
- src/global-styles/legacy-bootstrap/progress-bars.scss
Step 1.3: Fix Specificity Issues¶
Replace !important with proper cascade:
- Use :host selector for component-level specificity
- Use ::ng-deep sparingly and only for Material internal overrides
- Structure selectors to match the actual DOM hierarchy
Phase 2: Adopt M3 Theme¶
Goal: Switch from M2 theme API to M3 while maintaining visual consistency.
Step 2.1: Generate M3 Color Palette¶
Use the Material Theme Builder to generate an M3 palette from the SyRF primary color #203457.
The tool will generate tonal palettes that preserve the brand identity while providing the full M3 color role set.
Expected mapping:
| Current M2 | M3 Equivalent | Notes |
|---|---|---|
Primary 500 (#203457) |
--mat-sys-primary |
Source color input |
| Primary 300 | --mat-sys-primary-container |
Lighter usage |
| Primary contrast | --mat-sys-on-primary |
Auto-generated |
| Secondary A200 | --mat-sys-secondary |
May need manual tuning |
| Warn (red) | --mat-sys-error |
Standard Material red |
Step 2.2: Create M3 Theme Configuration¶
// syrf-theme.scss (M3 version)
@use '@angular/material' as mat;
// Option A: Use Material's built-in palette generation
$syrf-theme: mat.m3-define-theme((
color: (
theme-type: light,
primary: mat.$blue-palette, // closest built-in, or custom
tertiary: mat.$neutral-palette,
),
typography: Roboto,
density: 0,
));
// Option B: Custom palette from Theme Builder export
// (preferred - preserves exact SyRF brand colors)
$syrf-theme: mat.m3-define-theme((
color: (
theme-type: light,
primary: $_syrf-primary-palette, // Generated from #203457
tertiary: $_syrf-tertiary-palette, // Generated complementary
),
));
@include mat.all-component-themes($syrf-theme);
Step 2.3: Migrate Component Theme Mixins¶
Convert each component theme from M2 palette access to M3 CSS custom properties:
// BEFORE (M2)
@mixin color($config) {
$primary: map.get($config, primary);
.banner {
background-color: mat.m2-get-color-from-palette($primary);
color: mat.m2-get-color-from-palette($primary, default-contrast);
}
}
// AFTER (M3)
// No mixin needed - use CSS custom properties directly
.banner {
background-color: var(--mat-sys-primary);
color: var(--mat-sys-on-primary);
}
This eliminates the need for:
- The @mixin theme() / @mixin color() pattern
- Registration in syrf-theme.scss
- Separate light/dark includes
Dark theme is handled automatically via CSS custom properties when the theme class changes.
Step 2.4: Update Dark Theme Toggle¶
// M3 dark theme
$syrf-dark-theme: mat.m3-define-theme((
color: (
theme-type: dark,
primary: $_syrf-primary-palette,
tertiary: $_syrf-tertiary-palette,
),
));
.global-dark-theme {
@include mat.all-component-colors($syrf-dark-theme);
}
Phase 3: Adopt M3 Component Styles¶
Goal: Update component usage to leverage M3 visual patterns.
Step 3.1: Button Variants¶
M3 introduces new button types. Map existing usage:
| Current | M3 Equivalent | Directive |
|---|---|---|
mat-raised-button |
Filled button | matButton (default) |
mat-flat-button |
Filled tonal button | matButton with mat-tonal-button |
mat-stroked-button |
Outlined button | matButton with mat-outlined-button |
mat-button |
Text button | matButton with mat-text-button |
mat-fab |
FAB | matFab |
mat-icon-button |
Icon button | matIconButton |
Step 3.2: Card Variants¶
<!-- M2 -->
<mat-card>Content</mat-card>
<!-- M3 - choose appropriate variant -->
<mat-card appearance="elevated">Content</mat-card>
<mat-card appearance="filled">Content</mat-card>
<mat-card appearance="outlined">Content</mat-card>
Step 3.3: Shape System¶
Replace ad-hoc border-radius with M3 shape tokens:
// M3 provides shape tokens via CSS custom properties
.my-card {
border-radius: var(--mat-sys-corner-medium); // 12px
}
| M3 Shape Token | Size | Usage |
|---|---|---|
--mat-sys-corner-none |
0 | Flat edges |
--mat-sys-corner-extra-small |
4px | Small elements |
--mat-sys-corner-small |
8px | Standard components |
--mat-sys-corner-medium |
12px | Cards, dialogs |
--mat-sys-corner-large |
16px | Large containers |
--mat-sys-corner-extra-large |
28px | FABs, sheets |
--mat-sys-corner-full |
50% | Circular elements |
Phase 4: Polish and Cleanup¶
Step 4.1: Remove M2 Compatibility Code¶
After all components are migrated:
- Remove all
mat.m2-*API calls - Remove
_design-tokens.scssvalues that are now covered by M3 CSS custom properties - Remove component theme mixin files that are no longer needed
- Remove
syrf-palette.scssif fully replaced by M3 tonal palettes
Step 4.2: Update Design Tokens¶
Design tokens should be updated to reference M3 CSS custom properties where possible:
// Tokens that map to M3 properties can become aliases
$color-primary: var(--mat-sys-primary);
$color-surface: var(--mat-sys-surface);
$radius-card: var(--mat-sys-corner-medium);
// App-specific tokens remain as SCSS variables
$content-max-width: 1200px;
$z-index-cover: 1000;
Step 4.3: Visual Regression Testing¶
- Take screenshots of all major views before migration
- Compare after each phase to catch unintended changes
- Key views to test: home page, project list, study table, annotation view, data export, admin pages
Effort Estimate¶
| Phase | Scope | Relative Size |
|---|---|---|
| Phase 1 (Prerequisites) | ~80 hardcoded colors, ~42 !important, ~61 inline styles, ~20 MDC TODOs, 3 Bootstrap files | Large |
| Phase 2 (M3 Theme) | Theme config, 11 component themes, dark mode | Medium |
| Phase 3 (M3 Components) | Button variants, cards, shape system across ~100 components | Large |
| Phase 4 (Cleanup) | Remove compat code, update tokens, visual testing | Small |
Recommended order: Phase 1 can be done independently and improves code quality regardless of M3. Phases 2-4 should be done as a focused effort.
Risks and Mitigations¶
| Risk | Impact | Mitigation |
|---|---|---|
| M3 visual changes look wrong with SyRF brand | Brand identity loss | Use Material Theme Builder to preview; tune tonal palettes |
| Bootstrap-dependent components break | Functionality loss | Audit all .btn-*, .panel-* usage before removing |
| Dark theme regression | UX degradation | M3 auto-generates dark from same palette - test thoroughly |
| Internal class selectors break | Styling regression | Phase 1 cleans these up before M3 changes |
| Custom sidenav behavior changes | Navigation broken | Sidenav theme is already well-structured; test explicitly |