Skip to content

docs(material/theming): rewrite theming-your-components guide #22465

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 186 additions & 97 deletions guides/theming-your-components.md
Original file line number Diff line number Diff line change
@@ -1,135 +1,224 @@
# Theming your custom component with Angular Material's theming system
# Theme your own components with Angular Material's theming system

In order to style your own components with Angular Material's tooling, the component's styles must
be defined with Sass.
You can use Angular Material's Sass-based theming system for your own custom components.

## 1. Define all color and typography styles in a "theme file" for the component
## Reading style values from a theme

First, create a Sass mixin that accepts an Angular Material color configuration and
outputs the color-specific styles for the component. A color configuration is a Sass map.
As described in the [theming guide][theme-map], a theme is a Sass map that contains style values to
customize components. Angular Material provides APIs for reading values from this data structure.

[theme-map]: https://material.angular.io/guide/theming#themes

### Reading color values

To read color values from a theme, you can use the `get-color-config` Sass function. This function
returns a Sass map containing the theme's primary, accent, and warn palettes, as well as a flag
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the palette names be wrapped in backticks?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so- we treat these as terms in the theming guide, but not explicitly as code symbols

indicating whether dark mode is set.

For example, if building a custom carousel component:
```scss
// Import library functions for theme creation.
@import '~@angular/material/theming';

@mixin candy-carousel-color($config-or-theme) {
// Extract the color configuration in case a theme has been passed.
// This allows consumers to either pass a theme object or a color configuration.
$config: mat-get-color-config($config-or-theme);
// Extract the palettes you need from the theme definition.
$primary: map-get($config, primary);
$accent: map-get($config, accent);

// Define any styles affected by the theme.
.candy-carousel {
// Use mat-color to extract individual colors from a palette.
background-color: mat-color($primary);
border-color: mat-color($accent, A400);
}
}
@use 'sass:map';
@use '~@angular/material' as mat;

$color-config: mat.get-color-config($theme);
$primary-palette: map.get($color-config, 'primary');
$accent-palette: map.get($color-config, 'accent');
$warn-palette: map.get($color-config, 'warn');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think (maybe no longer?) you can also get background and foreground so that you could style a raised carousel to have a background color like a 'card' etc? That seems useful to incorporate here for components that have background or foreground (especially with dark modes)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the time being I want to leave the background palettes as a private implementation detail since we will likely need to change them in the not-too-distant future.

$is-dark-theme: map.get($color-config, 'is-dark');
```

Second, create another Sass mixin that accepts an Angular Material typography configuration
and outputs typographic styles. For example:
See the [theming guide][theme-read-hues] for more information on reading hues from palettes.

[theme-read-hues]: https://material.angular.io/guide/theming#reading-hues-from-palettes

### Reading typography values

To read typography values from a theme, you can use the `get-typography-config` Sass function. See
the [Typography guide][typography-config] for more information about the typography config data
structure and for APIs for reading values from this config.

[typography-config]: https://material.angular.io/guide/typography#typography-config

```scss
@mixin candy-carousel-typography($config-or-theme) {
// Extract the typography configuration in case a theme has been passed.
$config: mat-get-typography-config($config-or-theme);

.candy-carousel {
font: {
family: mat-font-family($config, body-1);
size: mat-font-size($config, body-1);
weight: mat-font-weight($config, body-1);
}
}
@use '~@angular/material' as mat;

$typography-config: mat.get-typography-config($theme);
$my-font-family: mat.font-family($typography-config);
```

## Separating theme styles

Angular Material components each have a Sass file that defines mixins for customizing
that component's color and typography. For example, `MatButton` has mixins for `button-color` and
`button-typography`. Each mixin emits all color and typography styles for that component,
respectively.

You can mirror this structure in your components by defining your own mixins. These mixins
should accept an Angular Material theme, from which they can read color and typography values. You
can then include these mixins in your application along with Angular Material's own mixins.

## Step-by-step example

To illustrate participation in Angular Material's theming system, we can look at an example of a
custom carousel component. The carousel starts with a single file, `carousel.scss`, that contains
structural, color, and typography styles. This file is included in the `styleUrls` of the component.

```scss
// carousel.scss

.my-carousel {
display: flex;
font-family: serif;
}

.my-carousel-button {
border-radius: 50%;
color: blue;
}
```

Finally, create a mixin that accepts an Angular Material theme, and delegates to the individual
theming system mixins based on the configurations. A theme consists of configurations for
individual theming systems (`color` and `typography`).
### Step 1: Extract theme-based styles to a separate file

To change this file to participate in Angular Material's theming system, we split the styles into
two files, with the color and typography styles moved into mixins. By convention, the new file
name ends with `-theme`. Additionally, the file starts with an underscore (`_`), indicating that
this is a Sass partial file. See the [Sass documentation][sass-partials] for more information about
partial files.

[sass-partials]: https://sass-lang.com/guide#topic-4

```scss
// carousel.scss

.my-carousel {
display: flex;
}

.my-carousel-button {
border-radius: 50%;
}
```

```scss
@mixin candy-carousel-theme($theme) {
// Extracts the color and typography configurations from the theme.
$color: mat-get-color-config($theme);
$typography: mat-get-typography-config($theme);

// Do not generate styles if configurations for individual theming
// systems have been explicitly set to `null`.
@if $color != null {
@include candy-carousel-color($color);
// _carousel-theme.scss

@mixin color($theme) {
.my-carousel-button {
color: blue;
}
@if $typography != null {
@include candy-carousel-typography($typography);
}

@mixin typography($theme) {
.my-carousel {
font-family: serif;
}
}
```

See the [typography guide](https://material.angular.io/guide/typography) for more information on
typographic customization.
### Step 2: Use values from the theme

## 2. Define all remaining styles in a normal component stylesheet
Now that theme theme-based styles reside in mixins, we can extract the values we need from the
theme passed into the mixins.

Define all styles unaffected by the theme in a separate file referenced directly in the component's
`styleUrl`. This generally includes everything except for color and typography styles.
```scss
// _carousel-theme.scss

@use 'sass:map';
@use '~@angular/material' as mat;

## 3. Include the theme mixin in your application
@mixin color($theme) {
// Get the color config from the theme.
$color-config: mat.get-color-config($theme);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No null checks shown here but then they're using in the theme mixin section, is that intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that mirrors the way our own mixins work- the typography and color mixins always emit, but the -theme mixin only emits if the configuration is defined.


Use the Sass `@include` keyword to include a component's theme mixin wherever you're already
including Angular Material's built-in theme mixins.
// Get the primary color palette from the color-config.
$primary-palette: map.get($color-config, 'primary');

```scss
// Import library functions for theme creation.
@import '~@angular/material/theming';

// Include non-theme styles for core.
@include mat-core();

// Define your application's custom theme.
$primary: mat-palette($mat-indigo);
$accent: mat-palette($mat-pink, A200, A100, A400);
$theme: mat-light-theme((
color: (
primary: $primary,
accent: $accent,
)
));
.my-carousel-button {
// Read the 500 hue from the primary color palette.
color: mat.get-color-from-palette($primary-palette, 500);
}
}

// Include theme styles for Angular Material components.
@include angular-material-theme($theme);
@mixin typography($theme) {
// Get the typography config from the theme.
$typography-config: mat.get-typography-config($theme);

// Include theme styles for your custom components.
@include candy-carousel-theme($theme);
.my-carousel {
font-family: mat.font-family($typography-config);
}
}
```

### Step 3: Add a theme mixin

## Note: using the `mat-color` function to extract colors from a palette

You can consume the theming functions and Material Design color palettes from
`@angular/material/theming`. The `mat-color` Sass function extracts a specific color from a palette.
For example:
For convenience, we can add a `theme` mixin that includes both color and typography.
This theme mixin should only emit the styles for each color and typography, respectively, if they
have a config specified.

```scss
// Import theming functions
@import '~@angular/material/theming';
// _carousel-theme.scss

@use 'sass:map';
@use '~@angular/material' as mat;

.candy-carousel {
// Get the default hue for a palette.
color: mat-color($primary);
@mixin color($theme) {
// Get the color config from the theme.
$color-config: mat.get-color-config($theme);

// Get a specific hue for a palette.
// See https://material.io/archive/guidelines/style/color.html#color-color-palette for hues.
background-color: mat-color($accent, 300);
// Get the primary color palette from the color-config.
$primary-palette: map.get($color-config, 'primary');

// Get a relative color for a hue ('lighter' or 'darker')
outline-color: mat-color($accent, lighter);
.my-carousel-button {
// Read the 500 hue from the primary color palette.
color: mat.get-color-from-palette($primary-palette, 500);
}
}

@mixin typography($theme) {
// Get the typography config from the theme.
$typography-config: mat.get-typography-config($theme);

.my-carousel {
font-family: mat.font-family($typography-config);
}
}

@mixin theme($theme) {
$color-config: mat.get-color-config($theme);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You end up extracting the color-config and typography-config twice to check null in this structure, is that best practice?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really matter, the cost is negligible

@if $color-config != null {
@include color($theme);
}

// Get a contrast color for a hue by adding `-contrast` to any other key.
border-color: mat-color($primary, '100-contrast');
$typography-config: mat.get-typography-config($theme);
@if $typography-config != null {
@include typography($theme);
}
}
```

### Step 4: Include the theme mixin in your application

Now that you've defined the carousel component's theme mixin, you can include this mixin along with
the the other theme mixins in your application.

```scss
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe define what file this goes in - should custom mixins also be top level in styles.scss (I assume yes?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered this, but decided not to because this file can really be called anything. The CLI defaults to styles.scss (which isn't a great name IMO), but people can really call it anything. Inside Google, people often call this something like material-theme.scss. I think the theming guide is probably clear enough on this that I'm not super worried about it, but we can see if we get any feedback.

@use '~@angular/material' as mat;
@use './path/to/carousel-theme' as carousel;

@include mat.core();

$my-primary: mat.define-palette(mat.$indigo-palette, 500);
$my-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);

$my-theme: mat.define-light-theme((
color: (
primary: $my-primary,
accent: $my-accent,
),
typography: mat.define-typography-config(
$font-family: serif,
);
));

@include mat.all-component-themes($my-theme);
@include carousel.theme($theme);
```
1 change: 1 addition & 0 deletions src/material/_index.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Theming APIs
@forward './core/theming/theming' show define-light-theme, define-dark-theme,
define-palette, get-contrast-color-from-palette, get-color-from-palette,
get-color-config, get-typography-config, get-density-config,
$theme-ignore-duplication-warnings;
@forward './core/theming/palette' show $red-palette, $pink-palette, $indigo-palette,
$purple-palette, $deep-purple-palette, $blue-palette, $light-blue-palette, $cyan-palette,
Expand Down