Skip to main content
Jet uses Angular Material 3 with a custom-generated color system that supports light mode, dark mode, and automatic theme switching based on system preferences.

Overview

The theming system is built on Material Design 3 (M3) principles, using a custom color palette generated from a primary brand color. It supports:
  • Three color schemes: Automatic, Light, and Dark
  • Dynamic theme switching without page reload
  • CSS custom properties for easy customization
  • RTL-aware layouts and styling

Color Palette

The color palette was generated using Angular Material’s M3 theme generator from the primary color #6e4b3a (a warm brown). See _theme-colors.scss:7:
// Note: Color palettes are generated from primary: #6e4b3a
$_palettes: (
  primary: (
    0: #000000,
    10: #2e1507,
    20: #47291a,
    25: #533424,
    30: #603f2f,
    35: #6d4a39,
    40: #7b5644,
    50: #966e5b,
    60: #b28774,
    70: #cfa18d,
    80: #ecbca6,
    90: #ffdbcb,
    95: #ffede6,
    98: #fff8f6,
    99: #fffbff,
    100: #ffffff,
  ),
  secondary: (...),
  tertiary: (...),
  neutral: (...),
  neutral-variant: (...),
  error: (...),
);
The palette includes primary, secondary, tertiary, neutral, and error colors, each with tonal variants from 0 (black) to 100 (white).

Palette Structure

Two main palette variables are exported:
$primary-palette: map.merge(map.get($_palettes, primary), $_rest);
$tertiary-palette: map.merge(map.get($_palettes, tertiary), $_rest);
These are used to configure Material components throughout the app.

Theme Configuration

The theme is applied in src/styles.scss:13:
@use '@angular/material' as mat;
@use '../_theme-colors' as mat-theme;

html {
  @include mat.theme(
    (
      color: (
        primary: mat-theme.$primary-palette,
        tertiary: mat-theme.$tertiary-palette,
        theme-type: color-scheme,
      ),
      typography: (
        brand-family: $bg-font-family,
        plain-family: $ns-font-family,
        bold-weight: 700,
        medium-weight: 500,
        regular-weight: 400,
      ),
      density: 0,
    )
  );
}

Theme Properties

color
object
Color configuration including primary and tertiary palettes
  • theme-type: color-scheme enables automatic light/dark switching
typography
object
Font configuration
  • brand-family: Bricolage Grotesque (for headings)
  • plain-family: Noto Sans (for body text)
  • Font weights: 400 (regular), 500 (medium), 700 (bold)
density
number
Component density (0 = comfortable, -1 = compact, -2 = very compact)

Color Scheme Options

Three color schemes are available (src/app/constants/color-scheme-options.constant.ts:4):
export const COLOR_SCHEME_OPTIONS: ColorSchemeOption[] = [
  {
    icon: 'contrast',
    nameKey: marker('constants.automatic'),
    themeColor: '#ffffff',
    value: 'automatic',
  },
  { 
    icon: 'light_mode', 
    nameKey: marker('constants.light'), 
    themeColor: '#fff8f6', 
    value: 'light' 
  },
  { 
    icon: 'dark_mode', 
    nameKey: marker('constants.dark'), 
    themeColor: '#161311', 
    value: 'dark' 
  },
];

Color Scheme Behavior

Follows system preference using prefers-color-scheme media query. Automatically switches between light and dark as the system preference changes.

Dynamic Theme Switching

The theme is controlled via a CSS class on the <body> element:
body {
  background-color: var(--mat-sys-surface);
  color: var(--mat-sys-on-surface);
  color-scheme: light dark;

  &.jet-color-scheme-automatic {
    color-scheme: light dark;
  }

  &.jet-color-scheme-dark {
    color-scheme: dark;
  }

  &.jet-color-scheme-light {
    color-scheme: light;
  }
}
Users can change the theme from the Settings page without reloading:
protected updateSettings(partialSettings: Partial<Settings>): void {
  this.#settingsService.updateSettings(partialSettings);
}

Material Component Overrides

Jet customizes Material components for a refined appearance:

Card Overrides

@include mat.card-overrides(
  (
    elevated-container-elevation: var(--mat-sys-level0),
    subtitle-text-color: var(--mat-sys-on-surface-variant),
  )
);

Sidenav Overrides

@include mat.sidenav-overrides(
  (
    container-background-color: var(--mat-sys-surface-container-low),
  )
);

Tabs Overrides

@include mat.tabs-overrides(
  (
    background-color: var(--mat-sys-primary),
    divider-height: 0,
    foreground-color: var(--mat-sys-on-primary),
  )
);

List Overrides

@include mat.list-overrides(
  (
    list-item-container-shape: var(--mat-sys-corner-small),
    list-item-supporting-text-color: var(--mat-sys-on-surface-variant),
    list-item-two-line-container-height: 74px,
  )
);

Expansion Panel Overrides

@include mat.expansion-overrides(
  (
    container-background-color: var(--mat-sys-surface-container-low),
    container-elevation-shadow: var(--mat-sys-level0),
  )
);

Chips Overrides

@include mat.chips-overrides(
  (
    elevated-container-color: var(--mat-sys-surface-container-low),
    elevated-disabled-container-color: var(--mat-sys-surface-container-lowest),
  )
);

Material Design Tokens

Jet uses M3 design tokens (CSS custom properties) for consistency:

Surface Colors

  • --mat-sys-surface - Base surface color
  • --mat-sys-surface-container-low - Slightly elevated surface
  • --mat-sys-surface-container - Elevated surface
  • --mat-sys-surface-container-highest - Highest elevation

On-Surface Colors

  • --mat-sys-on-surface - Text on surfaces
  • --mat-sys-on-surface-variant - Secondary text
  • --mat-sys-on-primary - Text on primary color

Shape

  • --mat-sys-corner-small - Small component border radius
  • --mat-sys-corner-medium - Medium component border radius
  • --mat-sys-corner-large - Large component border radius
  • --mat-sys-corner-none - No border radius

Typography

  • --mat-sys-body-small - Small body text
  • --mat-sys-body-medium - Medium body text
  • --mat-sys-body-large - Large body text

Typography System

Three font families are configured:
$bg-font-family: ('Bricolage Grotesque', serif);
$ns-font-family: ('Noto Sans', sans-serif);
$nsa-font-family: ('Noto Sans Arabic', sans-serif);
  • Bricolage Grotesque: Brand typography for headings and emphasis
  • Noto Sans: Plain text for body content (English)
  • Noto Sans Arabic: Arabic language support with proper RTL rendering

Dynamic Font Switching

When Arabic is selected, the font family changes:
body.jet-font-pair-nsa-nsa {
  @include mat.theme(
    (
      typography: (
        brand-family: $nsa-font-family,
        plain-family: $nsa-font-family,
        bold-weight: 700,
        medium-weight: 500,
        regular-weight: 400,
      ),
    )
  );
}

Custom Styling

Using Design Tokens

Always use design tokens instead of hardcoded colors:
// ✅ Good
.my-component {
  background-color: var(--mat-sys-surface-container);
  color: var(--mat-sys-on-surface);
}

// ❌ Bad
.my-component {
  background-color: #ffffff;
  color: #000000;
}

Component-Specific Overrides

Override Material components within specific contexts:
.jet-sidenav-toolbar {
  @include mat.toolbar-overrides(
    (
      container-background-color: transparent,
    )
  );
}

Responsive Layout

The theme includes responsive considerations:
.jet-sidenav-container {
  min-width: 360px;
  overflow-x: auto;
}

Safe Area Insets

Mobile device notches and home indicators are handled:
html {
  --jet-sidenav-width: 240px;
}

.jet-sidenav-container {
  &[dir='ltr'] {
    --jet-safe-area-start: env(safe-area-inset-left);
    --jet-safe-area-end: env(safe-area-inset-right);
  }

  &[dir='rtl'] {
    --jet-safe-area-start: env(safe-area-inset-right);
    --jet-safe-area-end: env(safe-area-inset-left);
  }
}

Customizing the Theme

Change Primary Color

  1. Run Angular Material’s theme generator:
    ng generate @angular/material:theme-color
    
  2. Enter your desired primary color (e.g., #3f51b5)
  3. The generator updates _theme-colors.scss with new palettes

Add Custom Color Scheme

Add a new option to COLOR_SCHEME_OPTIONS:
{
  icon: 'brightness_medium',
  nameKey: marker('constants.sepia'),
  themeColor: '#f4ecd8',
  value: 'sepia',
}
Add corresponding CSS:
body.jet-color-scheme-sepia {
  color-scheme: light;
  filter: sepia(0.3);
}

Best Practices

Use CSS custom properties like var(--mat-sys-surface) instead of hardcoded color values for automatic theme support.
Verify your UI works in light, dark, and automatic modes.
Ensure text meets WCAG AA standards (4.5:1 for normal text, 3:1 for large text).
Prefer --mat-sys-on-surface over specific color values for better maintainability.
Use @include mat.component-overrides() to customize Material components consistently.

Accessibility

The theming system prioritizes accessibility:
  • Sufficient contrast: M3 palettes ensure proper contrast ratios
  • System preference: Automatic mode respects user’s OS theme
  • Focus indicators: Material components include visible focus states
  • Color independence: Information isn’t conveyed by color alone

Next Steps

Internationalization

Multi-language support with RTL

PWA Features

Progressive Web App capabilities

Build docs developers (and LLMs) love