Skip to content

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: shape system 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:

  1. Eliminate hardcoded colors - Replace all ~80 hardcoded color values with theme palette references or design tokens
  2. Remove legacy Bootstrap - Migrate all .btn-*, .panel-*, .progress-bar-* usage to Material components
  3. Fix !important declarations - Resolve all ~42 !important hacks
  4. Remove inline styles - Move all ~61 inline style="" attributes to component SCSS
  5. Clean up MDC migration TODOs - Update all ~20 selectors targeting deprecated internal classes
  6. Adopt design tokens - All components should use _design-tokens.scss for 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:

  1. Remove all mat.m2-* API calls
  2. Remove _design-tokens.scss values that are now covered by M3 CSS custom properties
  3. Remove component theme mixin files that are no longer needed
  4. Remove syrf-palette.scss if 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

References