Angular Material 主题定制完全指南:基于 Sass 的mat.themeAPI 实现 M3 设计系统
【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components
Angular Material 的主题系统借鉴了 Google Material Design(M3)的设计理念,允许你通过定义自定义主题来全面控制组件外观。本文基于官方 主题指南,系统讲解 Angular Material v19 起引入的全新 Sass 主题 API:如何编写主题文件、配置色彩/排版/密度、使用预构建主题与内置色板、实现亮暗模式切换与多主题共存,并深入仓库源码(tokens/_system.scss、theming/_definition.scss 等)揭示其底层实现原理。读完本文,你将能够从零搭建一套支持 Material 3、可切换亮暗模式、可细粒度覆盖 Design Token 的完整应用主题方案。
快速上手:创建你的第一个主题文件
Angular Material 的主题定制从一份 Sass主题文件开始,该文件必须引入mat.thememixin。这个 mixin 接收一个包含 color、typography、density 三类配置的 map,并输出一组控制组件外观与布局的CSS 变量(Design Tokens)。
颜色类变量使用 CSSlight-dark()颜色函数定义,因此主题可以借助color-schemeCSS 属性在亮色与暗色模式之间自由切换。
下面是一份最简主题文件:应用 violet 色板、Roboto 字体和标准密度。它作用于html选择器,确保 CSS 变量覆盖整个应用;color-scheme显式设置为light dark,让最终亮暗模式由用户系统偏好决定:
@use '@angular/material' as mat; html { color-scheme: light dark; @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0 )); }为让应用默认使用主题的 surface 背景与 on-surface 文本色,可以补充如下全局样式:
body { background: var(--mat-sys-surface); color: var(--mat-sys-on-surface); }也可以借助mat.system-classes()生成一组 CSS 工具类,直接在组件模板中套用主题样式:
html { ... @include mat.system-classes(); }<body class="mat-bg-surface mat-text-on-surface">需要特别注意的是:mat.thememixin 只为输入 map 中包含的类别声明 CSS 变量。例如未提供typography时,输出的 CSS 中不会包含排版相关变量。这一点在源码中得到印证——tokens/_system.scss 中排版变量的发射逻辑被包裹在@if ($typography)分支内,色彩、排版、密度三部分各自独立判断。
主题配置的默认值与数据结构
从 theming/_definition.scss 中define-theme函数的实现可以确认各配置项的默认值:
| 配置项 | 默认值 | 说明 |
|---|---|---|
color.theme-type | light | 颜色取值类型 |
color.primary | $violet-palette | 主色板 |
color.tertiary | 同 primary | 三级色板,缺省时复用主色板 |
typography.plain-family | (Roboto, sans-serif) | 正文(plain)字体 |
typography.brand-family | 同 plain | 品牌(brand)字体 |
typography.bold-weight | 700 | 粗体字重 |
typography.medium-weight | 500 | 中等字重 |
typography.regular-weight | 400 | 常规字重 |
density.scale | 0 | 密度等级 |
主题对象内部由_mat-system(系统级变量)与_mat-theming-internals-do-not-access(内部结构)两部分组成,其中系统变量通过m3-tokens模块按 M3 规范生成,涵盖了颜色、排版、形状、状态、高度(elevation)等全套 Design Token。
配置色彩(Color)
主题的色彩决定了组件的颜色样式,例如复选框的填充色、按钮的涟漪颜色等。色彩依赖一组色调渐变的色板(Color Palette)来构建完整的配色方案。
设置颜色有两种方式:单一色板或颜色 map。
方式一:单一色板
直接传入一个色板,Angular Material 会将其用作主题的 primary、secondary 和 tertiary 颜色。此时颜色值使用light-dark()CSS 函数定义,因此应用样式必须显式声明color-scheme属性,否则无法触发亮暗切换:
@use '@angular/material' as mat; html { color-scheme: light dark; @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0 )); }方式二:颜色 map
传入颜色 map 可以将 tertiary 色板与 primary 色板分开配置。tertiary 色板常用来为部分组件提供独特的强调色。同时可以通过theme-type控制颜色值的定义方式:
color-scheme:使用light-dark()CSS 函数同时包含亮、暗两套颜色值(默认行为);light:仅定义亮色颜色值;dark:仅定义暗色颜色值。
light-dark()已得到所有主流浏览器的广泛支持,但若应用需要兼容较旧浏览器或非主流浏览器,建议显式将theme-type设为light或dark。
下面示例应用 violet 主色与 orange 三级色,theme-type为light,即只为应用定义亮色颜色值:
@use '@angular/material' as mat; html { @include mat.theme(( color: ( primary: mat.$violet-palette, tertiary: mat.$orange-palette, theme-type: light, ), typography: Roboto, density: 0 )); }底层实现:theme-type 如何决定输出
在 tokens/_system.scss 的_generate-sys-colors函数中可以看到三种theme-type的完整分支:light直接返回 M3 亮色系统色;dark返回暗色系统色;color-scheme则对每一对亮/暗颜色值调用light-dark($light-value, $dark-value)生成可切换变量。此外,如果传入的是单一色板(而非 map),theme mixin 会自动把tertiary指向同一色板,并将theme-type默认置为color-scheme——这与文档描述的行为完全一致。
配置排版(Typography)
排版决定组件内的文本样式,例如对话框标题或菜单列表项的字体。同样有两种配置方式。
方式一:单一字体族
直接传入字体族字符串,Angular Material 会将其应用于组件所有文本。组件中使用的字重固定为:粗体 700、中等 500、常规 400。
方式二:排版 map
传入排版 map 可为plain(正文)与brand(品牌)文本设置不同字体族:plain 字体用于应用大部分正文,brand 字体通常用于标题与题名。map 中还可分别指定 bold、medium、regular 字重。
下面示例:正文使用 Roboto、品牌文本使用 Open Sans,粗体 900、中等 500、常规 300,色彩为 violet 色板、标准密度:
@use '@angular/material' as mat; html { @include mat.theme(( color: mat.$violet-palette, typography: ( plain-family: Roboto, brand-family: Open Sans, bold-weight: 900, medium-weight: 500, regular-weight: 300, ), density: 0, )); }从源码看,theme mixin 处理排版时,若值是字符串则 plain 与 brand 共用;若值是 map 则分别读取plain-family、brand-family与三个字重键,最终经由system-level-typography调用m3.md-sys-typescale-values生成--mat-sys-*排版变量(如--mat-sys-body-large等字体快捷变量)。
配置密度(Density)
密度值决定组件内部间距,例如按钮文字周围的内边距、表单字段的高度。
密度值接受0 到 -5的整数:0 为默认间距,-5 为最紧凑布局。每下降一个整数值(-1、-2……),受影响尺寸减少 4px,直到组件能正常渲染所需的最小尺寸为止。
下面示例将密度设为 -2,使大部分组件减少留白、布局更紧凑:
@use '@angular/material' as mat; html { @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: -2, )); }两点重要提醒:
- 密度低于 0 可能降低可访问性,给使用辅助技术的用户带来导航困难;
- 密度自定义不影响出现在任务型或弹出型上下文中的组件(如日期选择器)。Material Design 密度规范明确不建议改变此类交互的密度,因为它们并不与应用布局争抢空间。
源码佐证:在 tokens/_system.scss 中,只有当$scale != 0时才会输出组件级密度 Token,且这些 Token(如 checkbox、button、form-field 等各组件的密度变量)不会回退到系统级值,必须由 mixin 直接定义。
使用预构建主题(Prebuilt Themes)
如果不想通过 Sass 自定义主题,Angular Material 提供了8 个预构建主题 CSS 文件:其中 4 个基于现代 Material 3 设计系统,另外 4 个基于旧的 Material 2 设计系统。若希望应用遵循 M3 设计语言,务必选用 M3 主题;M2 主题仅为向后兼容而保留,将在未来版本中移除。
| 主题 | 设计系统 | 亮/暗 | 色板(primary, tertiary) |
|---|---|---|---|
azure-blue.css | M3 | 亮 | azure, blue |
rose-red.css | M3 | 亮 | rose, red |
cyan-orange.css | M3 | 暗 | cyan, orange |
magenta-violet.css | M3 | 暗 | magenta, violet |
deeppurple-amber.css | M2 | 亮 | deep-purple, amber |
indigo-pink.css | M2 | 亮 | indigo, pink |
pink-bluegrey.css | M2 | 暗 | pink, blue-grey |
purple-green.css | M2 | 暗 | purple, green |
预构建主题文件位于 Angular Material npm 包的prebuilt-themes目录(即@angular/material/prebuilt-themes)。在项目angular.json的styles数组中引入所选 CSS 文件即可:
"styles": [ "@angular/material/prebuilt-themes/azure-blue.css" ]这些预构建主题的源码就存放在本仓库的 src/material/core/theming/prebuilt 目录下,是学习完整主题定义的绝佳范例。例如 azure-blue.scss 的实现正是调用system.theme,传入theme-type: light、azure 主色板、blue 三级色板与 Roboto 字体:
html { @include system.theme(( color: ( theme-type: light, primary: palettes.$azure-palette, tertiary: palettes.$blue-palette, ), typography: Roboto, density: 0, )); }色板(Color Palettes)
色板是一组色调相近、明度由浅到深的颜色集合。Angular Material 主题借助色板构建配色方案,以传达应用的层级(hierarchy)、状态(state)与品牌(brand)信息。
内置色板
Angular Material 提供了12 个预构建色板,可直接用于应用主题:
$red-palette$green-palette$blue-palette$yellow-palette$cyan-palette$magenta-palette$orange-palette$chartreuse-palette$spring-green-palette$azure-palette$violet-palette$rose-palette
这些色板全部定义在 src/material/core/theming/_palettes.scss 中。以$violet-palette为例,它包含 0~100 的色调梯度(如40: #7d00fa、80: #d5baff),并且通过_patch-error-palette为每个色板补充了独立的error色阶(如40: #ba1a1a);同时每个色板都内置secondary(次要)、neutral(中性)、neutral-variant(中性变体)子色板,这些子色板会随主题自动参与 M3 系统色的构建(见 theming/_definition.scss 中 primary/secondary/tertiary/neutral/neutral-variant/error 六类色板的组装逻辑)。
自定义色板
Angular Material 提供了色板生成 schematic:基于单个主色输入构建自定义色板,并可选择性输入更多颜色以进一步定制 secondary、tertiary 与 neutral 色板:
ng generate @angular/material:theme-color该 schematic 的详细说明见 src/material/schematics/ng-generate/theme-color/README.md,其支持primaryColor、tertiaryColor、neutralColor、neutralVariantColor等选项(未指定时由 Material 基于主色自动推导),生成的$primary-palette、$tertiary-palette可直接接入mat.theme使用,并额外提供高对比度(prefers-contrast)覆盖方案。
加载字体(Loading Fonts)
Google Fonts 是加载字体的常用选项之一。例如下面的代码放在应用<head>中,即可加载 Roboto(400/500/700)与 Open Sans(300..800)字体族:
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">注意:默认情况下,使用 Angular CLI 创建的项目被配置为内联来自 Google Fonts 的资源,以减少阻塞渲染的请求。这也解释了为什么上面的加载示例要在<head>中显式使用preconnect——自行引入字体时可以通过预连接减少加载延迟。
支持亮色与暗色模式
默认情况下,mat.thememixin 使用light-dark()CSS 颜色函数定义颜色,使应用能够轻松地在亮暗模式间切换。light-dark()函数依赖全局样式中声明的color-scheme值:若应用未定义color-scheme,则始终应用亮色。
可通过color-scheme: light或color-scheme: dark显式指定模式;要跟随用户系统偏好,则使用color-scheme: light dark:
@use '@angular/material' as mat; html { color-scheme: light dark; @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0 )); }另一种常见策略是把color-scheme定义在某个 CSS 选择器下,使模式取决于该 class 是否被应用。下面示例中,应用默认始终显示亮色主题,除非给<body>添加dark-mode类:
@use '@angular/material' as mat; html { color-scheme: light; @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0 )); } body.dark-mode { color-scheme: dark; }Angular Material 不会根据prefers-color-scheme、prefers-contrast等用户偏好媒体查询自动应用不同样式或主题。它刻意把灵活性留给你:可以依赖color-scheme: light dark,也可以自定义媒体查询,或读取已保存的用户偏好来应用样式。
多主题(Multiple Themes)
mat.thememixin 可以被调用多次,以在应用中应用多套不同的配色方案。
上下文专属主题
下面的示例按上下文定制组件主题:为一段删除数据提示的容器应用 cyan 色板,使其中的按钮等组件获得独特、醒目的强调样式:
@use '@angular/material' as mat; html { @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0, )); } .example-bright-container { @include mat.theme(( color: mat.$cyan-palette, )); }这种做法的底层机制是:每次调用 mixin 都会在当前选择器下重新发射一组--mat-sys-*CSS 变量(见 tokens/_system.scss 中current-selector-or-root的变量输出逻辑),子容器内组件读取到的变量因此被局部覆盖,从而实现局部换肤。
使用主题样式(Using Theme Styles)
应用的自定义组件可以直接使用mat.theme定义的 CSS 变量来应用主题的颜色与排版:
- 颜色变量适合强调重要文本与操作、强化应用品牌、确保 surface 与 on-surface 元素间有足够的对比度;
- 排版变量适合在整个应用中建立清晰的信息层级与文本一致性。
下面示例演示组件使用颜色与排版变量实现一个向用户呈现重要信息的全宽横幅:
.my-component { background: var(--mat-sys-primary-container); color: var(--mat-sys-on-primary-container); border: 1px solid var(--mat-sys-outline-variant); font: var(--mat-sys-body-large); }也可以改用工具类达到同样的效果:
<div class="mat-bg-primary-container mat-text-on-primary-container mat-border-variant mat-font-body-lg"></div>这些变量与工具类的完整清单、使用场景以及组件对它们的依赖方式,可参阅同仓库的 Theming your components 指南。
自定义 Design Tokens(Customizing Tokens)
Angular Material 组件还允许通过overrides mixin对特定 Token 进行精准定制,实现细粒度的调整——既可修改系统级主题 CSS 变量,也可修改单个组件的 Token(如组件边框颜色或标题字号)。
overrides API 会校验自定义 Token 的拼写是否正确,并可在未来版本 Token 被新增、移动或重命名时用于保证向后兼容。
系统级 Token(System Tokens)
通过mat.theme-overridesmixin 可更改系统级 Token,它会重新定义应用中使用到的 CSS 变量。下面示例为应用应用 violet 色板,但把primary-containerToken 改为特定蓝色调:
@use '@angular/material' as mat; html { color-scheme: light dark; @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0 )); .example-orange-primary-container { @include mat.theme-overrides(( primary-container: #84ffff )); } }另一种方式:在mat.thememixin 中传入可选的override map,直接替换 mixin 应用的值:
@use '@angular/material' as mat; html { color-scheme: light dark; @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0 ), $overrides: ( primary-container: orange, )); }从源码看,theme-overrides mixin 会先合并 M3 的 color/typography/elevation/shape/state 全量系统变量名,然后逐个校验传入的 override 键是否存在于系统变量名集合中(不存在则被忽略),再以--mat-sys-*前缀输出覆盖值。同时,mat.theme的$overrides参数在发射颜色、排版、高度、形状、状态各系统变量时,都会优先使用 override 值(如map.get($overrides, $name) or $value,见 tokens/_system.scss)。
组件级 Token(Component Tokens)
每个 Angular Material 组件都定义了各自的overridesmixin,用于定制其颜色、排版与密度相关的 Token。各组件可用 Token 的完整清单可在其文档页面的Styling标签下查看。
下面示例使用 Card 的overridesAPI 将背景改为红色、增大圆角、并指定更大的标题字号:
html { @include mat.card-overrides(( elevated-container-color: red, elevated-container-shape: 32px, title-text-size: 2rem, )); }直接样式覆盖(Direct Style Overrides)
Angular Material 支持上述方式定制颜色、排版与密度,但强烈不鼓励、也不直接支持在本主题 API 之外覆盖组件 CSS。组件的 DOM 结构与 CSS 类被视为私有实现细节,随时可能变化;Angular Material 组件使用的 CSS 变量应通过overridesAPI 定义,而不是显式自行定义。
强焦点指示器(Strong Focus Indicators)
默认情况下,大多数组件通过改变背景色来指示浏览器焦点(符合 Material Design 规范)。但这种行为可能无法满足可访问性要求——例如 WCAG 4.5:1 要求对浏览器焦点给出更强的指示。
Angular Material 支持在获得焦点的元素上渲染高可见度轮廓。应用通过调用mat.strong-focus-indicators()启用:
@use '@angular/material' as mat; html { color-scheme: light dark; @include mat.theme(( color: mat.$violet-palette, typography: Roboto, density: 0 )); @include mat.strong-focus-indicators(); }默认情况下,焦点指示器使用主题的secondary 颜色;可通过调用strong-focus-indicators-theme($color)mixin 自定义颜色,也可在默认颜色与背景对比度不足的场景下用它更换焦点指示器颜色。
自定义强焦点指示器
可以向strong-focus-indicators传入配置 map 自定义指示器外观,支持border-color、border-style、border-width与border-radius四个配置项:
@use '@angular/material' as mat; @include mat.strong-focus-indicators(( border-color: red, border-style: dotted, border-width: 4px, border-radius: 2px, ));源码实现见 focus-indicators/_private.scss:mixin 的默认配置为border-color: var(--mat-sys-secondary, black)与display: block,用户配置会与默认配置合并;同时该 mixin 还会为标准 chips 的内部结构补上overflow: visible,以避免与焦点指示器的渲染冲突。
在 Shadow DOM 中使用主题
Angular Material 默认假设所有主题样式以全局 CSS方式加载。如果应用要使用 Shadow DOM,则必须在每个包含 Angular Material 组件的 shadow root 内加载主题样式。可以通过两种方式实现:
- 在每个 shadow root 中手动加载 CSS;
- 使用 Constructable Stylesheets 在 shadow root 间共享主题样式。
总结:一套主题方案的完整决策路径
综合全文,搭建 Angular Material 主题的决策路径可以归纳为四步:
- 选择主题来源:若追求开箱即用,直接在
angular.json引入 预构建主题(推荐 M3 主题);若需要品牌化定制,则编写 Sass 主题文件调用mat.theme; - 确定配色:从 12 个内置色板 中选择或通过
ng generate @angular/material:theme-color生成自定义色板,并按需通过颜色 map 区分 primary/tertiary 与theme-type; - 确定排版与密度:选择单一字体族或 plain/brand 双字体方案,权衡密度 0~-5 对布局紧凑度与可访问性的影响;
- 细化与兼容:借助
mat.theme-overrides与组件级overridesmixin 微调 Token,通过color-scheme实现亮暗模式,调用strong-focus-indicators强化可访问性,并在 Shadow DOM 场景下正确分发主题样式。
每一步的底层行为(默认值、light-dark()生成逻辑、Token 校验、密度变量发射等)都可以在 tokens/_system.scss、theming/_definition.scss 与 focus-indicators/_private.scss 等源码文件中找到对应实现,方便你在遇到边界问题时深入排查。
【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考