In Angular Material v15, many of the components have been refactored to be based on the official Material Design Components for Web (MDC). The components from the following imports have been refactored:
Import path | Summary of changes |
---|---|
@angular/material/autocomplete | Style changes only |
@angular/material/button | Style changes, API changes |
@angular/material/card | Style changes only |
@angular/material/checkbox | Style changes, changes to event behavior |
@angular/material/chips | Complete rewrite |
@angular/material/core | Style changes only |
@angular/material/dialog | Style changes, changes to change detection behavior |
@angular/material/form-field | Style changes, some appearances removed, API changes |
@angular/material/input | Style changes only |
@angular/material/list | Style changes, API changes |
@angular/material/menu | Style changes, API changes |
@angular/material/paginator | Style changes only |
@angular/material/progress-bar | Style changes only |
@angular/material/progress-spinner | Style changes only |
@angular/material/radio | Style changes only |
@angular/material/select | Style changes only |
@angular/material/slide-toggle | Style changes only |
@angular/material/slider | Complete rewrite |
@angular/material/snack-bar | Style changes, API changes |
@angular/material/table | Style changes only |
@angular/material/tabs | Style changes, API changes |
@angular/material/tooltip | Style changes only |
The refactored components offer several benefits over the old implementations, including:
The new components have different internal DOM and CSS styles. However, most of the TypeScript APIs and component/directive selectors for the new components have been kept as close as possible to the old implementation. This makes it straightforward to migrate your application and get it running with the new components.
Due to the new DOM and CSS, you will likely find that some styles in your application need to be adjusted, particularly if your CSS is overriding styles on internal elements on any of the migrated components.
There are a few components with larger changes to their APIs that were necessary in order to integrate with MDC. These components include:
See below for a comprehensive list of changes for all components.
The old implementation of each new component is now deprecated, but still available from a "legacy"
import. For example, you can import the old mat-button
implementation can be used by importing the
legacy button module.
import {MatLegacyButtonModule} from '@angular/material/legacy-button';
You can start your migration by running Angular Material's automated refactoring tool. This tool, implemented as an Angular Schematic, updates the majority your code to the new component versions. While some follow-up is necessary, you can reduce the manual effort by following these best practices:
You can reduce the amount of manual effort needed by ensuring that your application follows good practices before migrating.
Angular Material includes a schematic to help migrate applications to use the new MDC-based components. To get started, upgrade your application to Angular Material 15.
ng update @angular/material@15
As part of this update, a schematic will run to automatically move your application to use the "legacy" imports containing the old component implementations. This provides a quick path to getting your application running on v15 with minimal manual changes.
After upgrading to v15, you can run the migration tool to switch from the legacy component implementations to the new MDC-based ones.
ng generate @angular/material:mdc-migration
This command updates your TypeScript, styles, and templates to the new implementations, updating as much as it can automatically.
Depending on the size and complexity of your application, you may want to migrate a single component or small group of components at a time, rather than all components at once.
You may also want to migrate your app one module at a time instead of all together. You can use both
the old implementation and new implementation in the same application, as long as they aren't used
in the same NgModule
.
The script will prompt you for the directory and components you want to migrate.
In situations where the migration tool is not able to automatically update your code, it will attempt to add comments for a human to follow up. These TODO comments follow a common format, so they can be easily identified.
// TODO(mdc-migration): ...
To search for all comments left by the migration tool, search for TODO(mdc-migration):
in your
IDE.
After running the migration and addressing the TODOs, manually verify that everything is working correctly.
Run your tests and confirm that they pass. It's possible that your tests depended on internal DOM or async timing details of the old component implementations and may need to be updated. If you find you need to update some tests, consider using component harnesses to make the tests more robust.
Run your application and verify that the new components look right. Due to the changes in internal DOM and CSS of the components, you may need to tweak some of your application's styles.
Component size, color, spacing, shadows, and animations all change slightly across the board. These changes generally improve spec-compliance and accessibility.
The DOM structure for all components has changed to improve accessibility and better follow the Material Design spec.
CSS classes applied to components use the mat-mdc-
prefix, whereas before it was simply a mat-
prefix. Elements that roughly correspond to element in the old implementation have been given the
same class name (aside from the prefix). For example, the button’s host class is mat-mdc-button
instead of mat-button
. However, not all elements in the previous implementation have an
equivalent element in the new implementation.
The styles associated with the mat-typography
class are no longer generated automatically. You
have to include them using the mat.typography-hierarchy
mixin.
Default typography levels defined by mat.define-typography-config
have been updated to reflect
changes to the Material Design spec.
All components now have themeable density. Styles for the default density level (0) will be included by default when you include a theme mixin.
@use '@angular/material' as mat;
$theme: mat.define-light-theme((
color: ...
));
// Adds density level 0 styles
@include mat.all-component-themes($theme);
If you prefer a different default density level, you can set it in your theme config:
$theme: mat.define-light-theme((
color: ...,
density: -1
));
Long options now wrap instead of truncating.
Option heights are no longer capped at 48px
.
Option list now has an 8px
top and bottom padding.
Options list now has an animation when opening and closing.
Icon button height and width are 48px
instead of 40px
.
State colors (hover, focus, active) are slightly different to improve text contrast ratios.
Letter-spacing is 1.25px
instead of normal
.
FAB supports text with the extended
input attribute.
Theming mixins are split into three separate mixins:
mat.mdc-button-theme
mat.mdc-icon-button-theme
mat.mdc-fab-theme
Icons in the button content are placed before the button text. Add the iconPositionEnd
attribute to place them after the button text.
Icons in the button content inherit the text font-size
. Buttons with only icons and no text do
not align properly (this does not apply to the icon-button).
By default, mat-card does not apply any internal padding. Instead, this padding is defined on the
card content areas: <mat-card-content>
, <mat-card-header>
, and <mat-card-actions>
.
<mat-card-content>
no longer sets any typography styles, users are free to add whatever
typography styles make sense for their application, either to <mat-card-content>
itself or any
child elements as appropriate. For example:
@use '@angular/material' as mat;
@include mat.typography-hierarchy();
<mat-card>
<mat-card-content class="mat-body-1">...</mat-card-content>
</mat-card>
Clicks on the checkbox now trigger directly on the native checkbox element rather than a shim div.
Native checkboxes have bizarre behavior when calling preventDefault
on their click
event, so
users should not call preventDefault
on click
.
Checkbox touch targets are larger, now 40px instead of 16px, which is more accessible. Be sure to allow enough space in your layout so that the touch target does not overlap other components. If you are not concerned with accessibility you can match the previous size by using density -1 for the checkbox.
@use '@angular/material' as mat;
@include mat.checkbox-density(-1);
Checkbox color may be changed to white or black due to a change in heuristics based on the application’s theme. Previously, the check’s color would be set to the theme’s background color. With MDC, it is determined by whether white or black has the most contrast against the primary color.
Focus state is slightly darker, improving contrast ratio.
Text styles are not inherited; you will need to specifically target the checkbox’s label
to
override typography properties.
After toggling a checkbox with the mouse, the ripple will remain visible instead of animating out.
Chips used to be directives, but they're now components. This means that they can't be applied to other components anymore.
The chips component has been split into multiple variants corresponding with more appropriate
interaction patterns for accessibility. The original mat-chip-list
used role="listbox"
, but
this interaction pattern is not suited for all use-cases. The new chips have:
<mat-chip-listbox>
with <mat-chip-option>
- this is the closest to the previous interaction
pattern. This is the only variant that supports selection state for chips. This pattern aligns
with the filter chips pattern specified in the Material Design spec. This pattern should be used
when you want the user to select one or more values from a list of options.
<mat-chip-grid>
with <mat-chip-row>
- this pattern should be used for any text input + chips
interaction.
<mat-chip-set>
with <mat-chip>
- this variant has no accessibility pattern and assumes one
will be applied at the application level. This allows the application to implement a custom
accessibility pattern with the chips visuals.
The migration tool always changes the legacy <mat-chip-list>
to <mat-chip-listbox>
to minimize
differences before and after. You should separately consider changing to <mat-chip-grid>
or
<mat-chip-set>
on a case-by-case basis. See Chips Interaction Patterns for more guidence on
choosing the appropriate component for your use case.
The .mat-dialog-container
does not contain a 24px padding anymore. Instead, the inner dialog
directives are responsible for adding the right padding. This will be apparent if your dialog does
not use any of the directives like <mat-dialog-content>
.
mat-dialog-content
uses the font-settings specified by the Material Design spec, which includes
a rather roomy line-height. If you have an information-dense dialog that doesn't look good with
these settings, you can avoid using <mat-dialog-content>
and just use a div with custom padding,
or use custom typography settings that can be applied with the mat.mdc-dialog-typography
mixin.
The old dialog triggered an extra change detection, which may have masked change detection issues in your application that need to be fixed when migrating.
The "legacy" and "standard" form-field appearance settings no longer exist, as these have been dropped from the Material Design spec.
The removed "legacy" appearance promoted input placeholders to the floating label if the label was
not specified. All newer appearance settings require explicitly specifying a <mat-label>
if one
was not provided before. This change addresses an accessibility best practice of not using labels
and placeholders interchangeably.
By default, MatFormField still reserves exactly one line of space below the field for hint or
error text. However, there is a new option @Input() subscriptSizing: 'fixed'|'dynamic'
. When
this setting is set to fixed
(default), the form-field reserves enough space in the layout to
show one line of hint or error text. When set to dynamic
, the form-field expands and contracts
the amount of space it takes in the layout to fit the error / hint that is currently shown.
The text inside <mat-hint>
is larger and darker in order to meet W3C text guidelines.
While the previous form-field had a single directive for prefixes (matPrefix
) and a single
directive for suffixes (matSuffix
), the MDC-based form-field distinguishes between text
prefix/suffixes which are baseline aligned with the input text, and icon prefix/suffixes which are
center aligned in the form-field. Use matTextPrefix
or matTextSuffix
to indicate a text
prefix/suffix, and matIconPrefix
or matIconSuffix
to indicate an icon prefix/suffix. The old
matSuffix
and matPrefix
APIs will behave like icons, though they are now deprecated.
The floatLabel
input no longer accepts 'never'
. floatLabel="never"
was only supported by the
legacy form-field appearance which has been dropped. It was used to achieve a floating label that
behaved like a placeholder. If you need this behavior, use the placeholder
property on <input>
instead.
Custom form field controls may need their styles adjusted to account for the fact that the surrounding form field DOM and styles have changed.
MatInput must be inside <mat-form-field>
. Previously it was (unintentionally) possible to use an
<input matInput>
without the form field if the page loaded form field styles elsewhere.
The MDC-based MatInput hides the native calendar picker indicator associated with
<input matInput type="date">
, if you want this indicator to appear for your inputs, use the
following styles:
input.mat-mdc-input-element::-webkit-calendar-picker-indicator {
display: block;
}
The API has been reworked to support text wrapping and better integration with the Material Design specification.
Previously, list items were commonly created using multiple span
elements with the matLine
directive applied. Each span
resulting in a line, and the first one becoming the primary line.
With the new API, the matLine
directive has been split into two more granular and meaningful
directives:
matListItemTitle
matListItemLine
Text outside of a matListItemLine
(so-called "unscoped content") will result in an additional
line being acquired (as if the content was put into a line).
<mat-list-item>
<span matListItemTitle>Title</span>
Second line
</mat-list-item>
The list automatically infers the number of lines of text content. For example, in the snippet
above, the list item renders space for two lines. With the new API, you can set an explicit number
of lines on the <mat-list-item>
to manually control wrapping.
<mat-list-item lines="3">
<span matListItemTitle>Title</span>
This text will wrap into the third line. Space for three lines is acquired by the
list item.
</mat-list-item>
Note that text inside a matListItemTitle
or matListItemLine
will never wrap. Only unscoped
content will wrap/take up the remaining space based on explicit number of lines provided.
Aside from the differences in how lines are composed, some other directives have been renamed to use more explicit naming:
matListIcon
is now matListItemIcon
matListAvatar
is now matListItemAvatar
Lastly, also a new directive (matListItemMeta
) is available to put content into the meta section
of a list item (usually the end of the list item). Previously unscoped content in a list item was
put into the meta section.
Recommended migration steps for common use of a list item:
matLine
to matListItemTitle
matLine
's to matListItemLine
matListIcon
to matListItemIcon
matListAvatar
to matListItemAvatar
matLine
) in a matListItemMeta
container.The icon for a menu item appears before the text, regardless of the order in the DOM.
If you have a piece of content such as an <img>
that you want to use in place of a
<mat-icon>
use ngProjectAs="mat-icon"
to project it into the icon slot.
If you need your icon to appear at the end of the item (not officially supported by the spec) you can wrap both the text and your icon in a span, for example:
<span>
<span>Text</span>
<mat-icon>end_icon</mat-icon>
</span>
The text in menu items wraps instead of being hidden with an ellipses.
Menu item heights are no longer capped at 48px
.
Menu elevation starts from level 8, instead of level 4.
Long options now wrap instead of truncating with an ellipsis.
Option heights are no longer capped at 48px
.
mat-paginator
only supports the appearance
options offered by the new
form-field (fill
and outline
).Visibility on internal elements is now set to visible
. Setting visibility: hidden
will no
longer hide all internal elements. Instead, style it with opacity: 0
, display: none
, or
remove it completely with ngIf
.
Height is always set to 4px and does not get shorter or taller using height
styles.
display: block
which may affect layout. To fix layout issues add
display: block
back to the element.Radio button labels are no longer width: 100%
. This helps prevent users from accidentally
selecting a radio when clicking on whitespace that appears inert on the page.
Labels are smaller and further away from the radio button to align with the Material Design spec.
The touch target is now much larger and more accessible. Be sure to allow enough space in your layout so that the touch target does not overlap other components. If you are not concerned with accessibility you can match the previous size by using density -1 for the radio.
@use '@angular/material' as mat;
@include mat.radio-density(-1);
MatSelect
no longer aligns the selected option in the dropdown menu with the trigger text.
Long options now wrap instead of truncating.
Option heights are no longer capped at 48px
.
Option list now has an 8px
top and bottom padding.
Option list animation has changed.
Previously the dropdown menu width could be wider than the parent form-field, but now the dropdown is the same width as the form-field
To improve accessibility, the MDC-based version uses <button role="switch">
to represent the
toggle rather than <input type="checkbox">
. Slide toggle will no longer respond to native form
validation. Consider alternative approaches to form validation.
The touch target is much larger and more accessible. Be sure to allow enough space in your layout so that the touch target does not overlap other components. If you are not concerned with accessibility you can match the previous size by using density -1 for the slide-toggle.
@use '@angular/material' as mat;
@include mat.slide-toggle-density(-1);
The label is closer to the enabled toggle
Sliders now work with mobile device screen readers.
The slider template API has changed from a single <mat-slider>
element to a <mat-slider>
element which contains one or two <input>
elements (depending on whether the slider should)
be a standard or range slider. E.g.
<!-- Single slider -->
<mat-slider>
<input matSliderThumb>
</mat-slider>
<!-- Range slider -->
<mat-slider>
<input matSliderStartThumb>
<input matSliderEndThumb>
</mat-slider>
The new discrete
property on the <mat-slider>
now controls whether the slider has tick marks
and a value indicator tooltip. It replaces thumbLabel
.
<!-- Before -->
<mat-slider thumbLabel></mat-slider>
<!-- After -->
<mat-slider discrete>
<input matSliderThumb>
</mat-slider>
The tickInterval
property has been removed. To switch to the new API, use showTickMarks
to
create a slider with tick marks, and the interval for your tick marks will match your slider's
step
. The tickInterval
property is under consideration to be added back in future releases.
<!-- Before -->
<mat-slider tickInterval="5" step="5"></mat-slider>
<!-- After -->
<mat-slider step="5" showTickMarks>
<input matSliderThumb>
</mat-slider>
The displayValue
property has been removed. The suggested alternative for controlling the
value indicator text is to provide a function via displayWith
.
<!-- Before -->
<mat-slider [displayValue]="myDisplayValue"></mat-slider>
<!-- After -->
<mat-slider [displayWith]="myDisplayWithFn">
<input matSliderThumb>
</mat-slider>
The valueText
property is now removed in favor of directly using the native input's
aria-valuetext or providing a displayWith
function.
<!-- Before -->
<mat-slider [valueText]="myValueText"></mat-slider>
<!-- After (Option 1) -->
<mat-slider>
<input [attr.aria-valuetext]="myValueText" matSliderThumb>
</mat-slider>
<!-- After (Option 2) -->
<mat-slider [displayWith]="myDisplayWithFn">
<input matSliderThumb>
</mat-slider>
The slider API has also changed such that there are two new components: MatSliderThumb
and
MatSliderRangeThumb
. They provide the following properties:
@Input() value: number
@Output() valueChange: EventEmitter<number>
@Output() dragEnd: EventEmitter<MatSliderDragEvent>
@Output() dragStart: EventEmitter<MatSliderDragEvent>
percentage: number
And the following methods:blur
focus
To accommodate range sliders, the implementation has changed from the <mat-slider>
element being
the form control to the <mat-slider>
element containing 1-2 <input>
elements (the slider
"thumbs") that act as the form control(s). The value, associated events (input
, change
), and
labels (aria-label
) now live on the <input>
elements instead.
Vertical sliders and inverted sliders are no longer supported, as they are no longer part of the
Material Design spec. As a result, the invert
and vertical
properties have been removed.
For simple, text-based snack-bars, there are no significant changes.
For simple snack-bars with an action button, they use the MDC-based mat-button, so your application will need to include the Sass theming mixin for the MDC-based button.
For snack-bars that use custom structured content (if you call MatSnackBar.openFromComponent
or
MatSnackBar.openFromTemplate
), you should use the following new directives to annotate your
content:
matSnackBarLabel
to mark the text displayed to usersmatSnackBarActions
to mark the element containing the action buttonsmatSnackBarAction
to mark individual action buttonsTests that open a snack-bar now require calling flush()
before attempting to access the content
of the snackbar. Updating your tests to use component harnesses
before running the migration tool should make this transition seamless.
All cells have a 16px
left and right padding instead of just the leftmost and rightmost cells
having a padding of 24px
.
Header cells have the same color and text size as the data rows instead of having more grayish and smaller text.
Cell text no longer wraps by default. Cell wrapping can be enabled by applying
white-space: normal
to the table cells.
Row height is 52px
instead of 48px
.
Cell box-sizing is border-box
instead of content-box
. This may affect custom width styles.
The table's last row does not include a bottom border row because the table is expected to have a border.
The paginator property of the MatTableDataSource
has a generic interface that matches most, but
not all of the paginator API. You may need to explicitly type the paginator to access the full
API, for example: new MatTableDataSource<MyData, MatPaginator>();
Flex tables (<mat-table>
) display a border on the cells instead of rows.
Flex table (<mat-table>
) row height is set with height
instead of min-height
.
Header label text color matches the theme color when the tab header is selected.
Header labels stretch to fill the container's width. This can be turned off by
setting the <mat-tab-group>
input mat-stretch-tabs
to false
.
The <mat-tab-nav-bar>
requires a reference to a <mat-tab-nav-panel>
using the tabPanel
input. The <mat-tab-nav-panel>
must wrap the content connected to the nav-bar. This allows the
component to provide correct labeling for assistive technology.
<!-- Before -->
<mat-tab-nav-bar>...</mat-tab-nav-bar>
<!-- After -->
<mat-tab-nav-bar [tabPanel]="tabPanel">...</mat-tab-nav-bar>
<mat-tab-nav-panel #tabPanel>...</mat-tab-nav-panel>
Background color is opaque instead of slightly transparent. This improves accessibility.
Default font-size is 12px
instead of 10px
.
Line height is normal
instead of 16px
.
Text overflow is ellipsis
instead of clip
.
There is a new minimum width of 40px
.
Text alignment for single line tooltips is center
. Multi-line tooltips use left
alignment.