Angular Materialの見た目をカスタマイズしたい

Angular v15 がリリースされて既に数ヶ月が経過しました。 Angular チームが開発を行っている Angular Material も同時に v15 がリリースされています。 Angular Material v14 までは Material 2 の仕様に準拠したコンポーネント群を独自の実装で提供されていましたが、 v15 からは実験的に提供されていた Material Components for Web (以下 MDC ) をラップした内容へと大幅に中身が変更されています。本記事は MDC ベースの Angular Material において見た目をカスタマイズする方法について紹介します。


はじめに

冒頭にも記載の通り、2022年11月にリリースされた Angular Material v15 では実装の内容がガラッと変更され、独自の実装から MDC をラップする方式になりました。筆者は MDC ベースの実装を実験的に提供していたことは知っていたものの v15 で切り替わることは認知していなかったため、 v15 のベータ版がリリースされてから大慌てで動作検証を実施しました(本記事執筆時点では未だに終わっていません)。

そんな筆者の苦労話はさておき、本記事では MDC ベースの Angular Material の見た目をカスタマイズする方法について、配色・タイポグラフィ・密度の3つの内容について紹介します。 Material Design そのものについて、および MDC や Angular Material の各コンポーネントについての使い方や細かな解説は行いませんので、ご承知置き下さい。

試した環境

Angular CLI: 15.1.2
Node: 18.13.0
Package Manager: npm 8.19.3
OS: win32 x64

Angular: 15.1.1
... animations, cdk, common, compiler, compiler-cli, core, forms
... material, platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1501.2
@angular-devkit/build-angular   15.1.2
@angular-devkit/core            15.1.2
@angular-devkit/schematics      15.1.2
@angular/cli                    15.1.2
@schematics/angular             15.1.2
rxjs                            7.8.0
typescript                      4.9.4

Angular Material の見た目をカスタマイズする

Angular Material のスタイルは Sass で実装されており、配色・タイポグラフィ・密度を変更するための仕組みが元々備わっています。具体的な方法は以下のドキュメントに記載されていますので、本記事の内容で不明な点があればドキュメントをご確認ください。

Theming Angular Material | Angular Material
Customizing Typography | Angular Material

配色

Material Design では Primary と Secondary の2つの色の組み合わせてアプリケーションのスタイルを定義します。 Primary はアプリケーションのテーマカラーであり、 Secondary はボタンやリンク等に利用されるアクセントカラーです。

The color system - Material Design


Angular Material ではアプリケーションで使用する配色として Primary と Accent (Secondary) 、そして Warn の3つの色が定義されています。 Warn は警告色ですので、 Material Design に規定されている通り Primary と Accent の2つでアプリケーションの配色が決定されます。また、いわゆるダークテーマ(黒系の背景に白い文字色の見た目)にも対応しています。

Angular Material の公式サイトでは右上のドロップダウンから4パターンの配色を選択することができますので、配色変更時にどのような見た目となるのかイメージがしやすいです。

Angular Material公式サイトの配色一覧


実際に Primary および Secondary の色を指定する前に、パレットについても紹介します。
パレットは色相が異なる複数の色が含まれる色のコレクションで、 Angular Material の配色はパレットから選択する必要があります。標準のパレットには色が明るい方から暗い方に50、100、200、300、、、900、そして Accent に使用できるA100、A200、A400、A700の合わせて14個の色が含まれます。またそれぞれの色の上に描画する文字の色も定義されています。

標準のパレットの一部
The color system - Material Design より


それでは実際にアプリケーションの配色を作成してみましょう。
標準のパレットのみを使用して配色を指定する場合はとてもシンプルで、 mat.define-palette 関数を使用して Primary と Accent の色定義を作成します。第1引数には色相の番号を指定します(未指定の場合 500 が使用されます)。
Primary と Accent の定義を作成したら、続いて mat.define-light-theme または mat.define-dark-theme を使用してテーマを作成します。後者を利用するとダークテーマとなります。

@use "@angular/material" as mat;

// アプリケーションの色定義を作成(Warnはオプションです)
$app-primary: mat.define-palette(mat.$indigo-palette);
$app-accent: mat.define-palette(mat.$pink-palette, A200);
$app-warn: mat.define-palette(mat.$red-palette);

// アプリケーションのテーマを作成
$app-theme: mat.define-light-theme((
 color: (
   primary: $my-primary,
   accent: $my-accent,
   warn: $my-warn,
 )
));

作成したテーマをアプリケーションに対して適用するには mat.all-component-themes を呼び出します。

@use "@angular/material" as mat;

// (略)

$app-theme: mat.define-light-theme((...));

// 配色等に影響されない共通のスタイル
@include mat.core();

// 配色等が反映される全てのコンポーネントのスタイル
@include mat.all-component-themes($app-theme);


続いて標準パレット以外から色を指定する方法についてです。標準パレット以外から色を指定する場合、通常使用する色に加えてその色を明るくした色( ligher )と暗くした色( darker )を1つずつ、そしてその3色の上に表示される文字色をそれぞれ指定した独自のパレットを作成します。ただし筆者が確認したところ Angular Material v14 までは明るく/暗くした色を使用している箇所があったのですが、 v15 では使用されていないように見えました。
試しに1つ配色を定義してみたいと思います。 Primary に #3f51b5 と黒い文字色、 Accent に #ff4081 と白い文字色を指定してみましょう。明るくした色と暗くした色は適当です。

@use "@angular/material" as mat;
@use "@angular/material/core/theming/palette" as mat-palette;

// PrimaryColorのパレット
$app-primary-palette: (
  // 色相のキーは数字でなくても良い
  default: #3f51b5,
  lighter: #757ce8,
  darker: #002884,
  // 文字色(色相のキーに対応する色を指定する)
  contrast: (
    default: mat-palette.$dark-primary-text,
    lighter: mat-palette.$dark-primary-text,
    darker: mat-palette.$light-primary-text
  )
);

// AccentColorのパレット
$app-accent-palette: (
  default: #ff4081,
  lighter: #ff79b0,
  darker: #c60055,
  contrast: (
    default: mat-palette.$light-primary-text,
    lighter: mat-palette.$dark-primary-text,
    darker: mat-palette.$light-primary-text
  )
);

// パレットからそれぞれ色相を指定
$app-primary: mat.define-palette($app-primary-palette, default, lighter, darker);
$app-accent: mat.define-palette($app-accent-palette, default, lighter, darker);
$app-warn: mat.define-palette(mat.$red-palette);

// テーマ作成
$app-theme: mat.define-light-theme((
  color: (
    primary: $app-primary,
    accent: $app-accent,
    warn: $app-warn,
  )
));

// スタイルの適用
@include mat.core();
@include mat.all-component-themes($app-theme);

これで独自配色のアプリケーションが実装できました(実はこの配色で得られるのはインディゴピングのビルドインテーマとほとんど同じなのですが。。。)

余談ですが、上のコードでは文字色をベタに指定せず Angular Material の 標準の文字色 $dark-primary-text および $light-primary-text を使用しています。しかしこの2つは @angular/material からエクスポートされていないため @angular/material/core/theming/palette を直接 @use します。そのままだとコンパイルエラーになってしまいますので、 angular.jsonstylePreprocessorOptions -> includePathnode_modules を指定することを忘れてはいけません。

イイカンジの配色を選択するのはとても難しいですが標準パレットのみでは選択肢が限られます。是非皆様も色々な配色を試してください。

タイポグラフィ

Material Design v2 ではデザインやコンテンツを明確にする目的で文字のスタイルを13種類のカテゴリーで定義しています。コンテンツのどこにどのカテゴリーを適用すべきは Material Design のドキュメントに記載されていますのでそちらをご確認ください。

The type system - Material Design


Angular Material の Sass ではこの13種類のスタイルを指定することでアプリケーションのタイポグラフィをまとめて変更することができます。指定できるのはフォントファミリー、文字の大きさ・太さ、そして行間・文字間です。次のテーブルは指定可能なカテゴリーとそれぞれのデフォルト値です。カテゴリーの種類は Angular Material v14 までと v15 以降で異なりますので、 v14 以前から更新される方は注意してください。

カテゴリー名 font-size line-height font-weight letter-spacing
headline-1 96px 96px light -1.5
headline-2 60px 60px light -0.5
headline-3 48px 50px regular 0.0
headline-4 34px 40px regular 0.25
headline-5 24px 32px regular 0.0
headline-6 20px 32px medium 0.25
subtitle-1 16px 28px regular 0.15
subtitle-2 14px 22px medium 0.1
body-1 16px 24px regular 0.5
body-2 14px 20px regular 0.4
caption 12px 20px regular 0.0
button 14px 36px medium 1.25
overline 12px 32px medium 2.0


タイポグラフィ例
The type system - Material Design より


Angular Material の Sass でタイポグラフィを指定するには mat.define-typography-levelmat.define-typography-config の2つの関数を使用します。前者はカテゴリー毎の個別設定を作成するもので、後者が全てのカテゴリーをまとめたタイポグラフィ設定を作成するものになります。タイポグラフィ設定は特に何も指定しなければデフォルト値が使用されますので全てを指定する必要はありません。
mat.typography-hierarchy はタイポグラフィ設定をコンポーネント外で使用するための mixin です。これを include すると文書中の h1 h2 等の HTML タグや mat-h1 mat-subtitle-1 mat-body-1 等の CSS クラスにタイポグラフィが適用されます。

実際にタイポグラフィ設定を作成してみましょう。いくつかのカテゴリーの大きさを少し小さくしてみます。

@use "@angular/material" as mat;

// アプリケーションのタイポグラフィ設定
$app-typography:  mat.define-typography-config(
  // フォントファミリー
  $font-family: 'Meiryo, "Helvetica Neue", sans-serif',

  // カテゴリー毎の設定上書き
  $headline-6: mat.define-typography-level(
    $font-size: 18px,
    $line-height: 28px,
    $font-weight: 600,
    $letter-spacing: normal
  ),
  $subtitle-2: mat.define-typography-level(
    $font-size: 12px,
    $line-height: 18px,
    $font-weight: 500,
    $letter-spacing: normal
  ),
  $body-1: mat.define-typography-level(
    $font-size: 12px,
    $line-height: 16px,
    $font-weight: 400,
    $letter-spacing: normal
  ),
  $button: mat.define-typography-level(
    $font-size: 14px,
    $line-height: 14px,
    $font-weight: 500,
    $letter-spacing: normal
  ),
  // $headline-1: ...,
  // $headline-2: ...,
  // $headline-3: ...,
  // $headline-4: ...,
  // $headline-5: ...,
  // $subtitle-1: ...,
  // $body-2: ...,
  // $caption: ...,
  // $overline: ...,
);

$app-primary: ...;
$app-accent: ...;
$app-warn: ...;

// テーマ作成
$app-theme: mat.define-light-theme((
  color: (
    primary: $app-primary,
    accent: $app-accent,
    warn: $app-warn,
  ),

  // テーマ作成時にタイポグラフィ設定を指定
  typography: $app-typography,
));

// スタイルの適用
@include mat.core();
@include mat.all-component-themes($app-theme);
@include mat.typography-hierarchy($app-theme);

このタイポグラフィ設定を適用すると一部の文字サイズが小さくなり、行間も縮まります。試しにカードコンポーネントに対してこのタイポグラフィ設定の変更がどのように適用されるのか確認してみましょう。なお両者ともフォントはメイリオとし、カードのコンテンツ部分だけ個別に mat-body-2 の CSS クラスを適用しています。

タイポグラフィ適用例
上がデフォルトのもの、下がカスタマイズしたもの

全体的に一回り小さく表示される様になりました。

Angular Material に含まれるコンポーネントのどのパーツにどのタイポグラフィカテゴリーが適用されるのかは Material Design の仕様から想像はできるものの、試してみるほかは無いでしょう。またタイポグラフィを変更しても意図した表示にならない、または変更したことに起因して表示が崩れることも少なからず発生します。繰り返しになりますが、最適な表示にするには試してみるほかは無いでしょう。。

密度

Material Design において密度( density )とはコンポーネントの余白の取り方を表します。 Material Design で指定された標準の余白は全体的に大きめに取られているためモバイルやタッチ用途では十分ですが、例えばデスクトップでは大量の情報を表示した方が良い場合もあり、必ずしも標準の余白指定をそのまま使用するのが良い場面ばかりではありません。そのため Material Design では余白を縮めてコンポーネントの密度を高めることで UX を向上させる仕組みが備わっています。

Applying density - Material Design
Density - Material Design

密度の効果
Applying density - Material Design より


Angular Material で密度を変更するにはテーマ作成時に密度を数値で指定します。デフォルトが 0 で、 -1 -2 ... とマイナス方向に数字を大きくするとより高密度になっていきます(余白サイズが小さくなっていきます)。

@use "@angular/material" as mat;

$app-primary: ...;
$app-accent: ...;

// テーマ作成
$app-theme: mat.define-light-theme((
  color: (
    primary: $app-primary,
    accent: $app-accent,
    warn: $app-warn,
  ),
  typography: $app-typography,

  // 密度の指定
  density: -1,
));

// スタイルの適用
@include mat.core();
@include mat.all-component-themes($app-theme);
@include mat.typography-hierarchy($app-theme);

実際に密度の値を変更してみます。次の画像ではボタンと FormField を密度を変更して並べてみました。ちょっと分かりづらいかもしれませんが、縦方向の見た目が少しずつ小さくなっていくことが分かるかと思います。

密度の適用例
上から0(デフォルト値)、-1、-2

密度の設定を変更すると数字1つ毎に4px小さくなります。ボタンの標準の高さは36px、 FormField は56pxですので、-1を指定するとそれぞれ32pxと52px、-2では28pxと48pxに縮まります。コンポーネント毎に縮めることができる閾値が存在しするため全体を一括で変更するのは-2が限度のようです。許容量を超える値を指定するとコンパイルエラーが発生します。

密度は多くのコンポーネントでサポートされていますので Angular Material v14 までと比較するとカスタマイズ性はとても向上しています。しかしながら設定値やコンポーネントの組み合わせによって期待の効果を発揮しなくなることもあり、密度もタイポグラフィ同様、試してみる他は無いと言えるでしょう。

なお余談ではありますが、先の複数の密度を適用した画像では密度-2の設定時に Filled FormField のラベルが消えてしまいました。不具合かと思ったのですがソースを眺めてみたところ以下の記述があり、どうやら仕様のようです。なんて紛らわしい。。。。。。

// Whether floating labels for filled form fields should be hidden. MDC hides the label in
// their density styles when the height decreases too much. We match their density styles.
$hide-filled-floating-label: $height < mdc-textfield.$minimum-height-for-filled-label;

コンポーネント毎のカスタマイズ

これまでは Angular Material の見た目をカスタマイズする方法を、配色・タイポグラフィ・密度それぞれについてアプリケーション全体を一括で変更する方法を紹介してきました。全体を一括で変更する方法の他、 Angular Material ではコンポーネント毎に配色・タイポグラフィ・密度を変更する方法が提供されています。

コンポーネント毎に配色・タイポグラフィ・密度を変更するには mat.xxx-color mat.xxx-typography mat.xxx-density の mixin を利用します。 xxx にはそれぞれコンポーネント名が入りますので、例えばボタンですと mat.button-color mat.button-typography mat.button-density になります。また all-component とすれば全てのコンポーネントに対して適用されます。各コンポーネントに対して配色・タイポグラフィ・密度を一括で指定する mat.xxx-theme mixinも提供されています。
このように細かなカスタマイズポイントが提供されていますので、特定のページ・パーツだけスタイルを変更したい、みたいな用途にも十分対応できます。

カスタマイズ例

最後に今筆者が試しているカスタマイズの一部紹介します。

筆者が開発しているアプリケーションはデスクトップかつ業務用途であり、タッチの考慮は不要で、なるべく多くのコンテンツを表示することが求められます。そのためフォントサイズ・余白共に小さめとすることが好ましいです。

@use "@angular/material" as mat;

// フォントサイズなどの設定
@function _get-typography-config() {
  @return mat.define-typography-config(
    $font-family: 'Meiryo, "Helvetica Neue", sans-serif',
    $subtitle-1: mat.define-typography-level(
      $font-size: 14px,
      $line-height: 20px,
      $font-weight: 400,
      $font-family: null,
      $letter-spacing: normal
    ),
    $subtitle-2: mat.define-typography-level(
      $font-size: 12px,
      $line-height: 14px,
      $font-weight: 500,
      $font-family: null,
      $letter-spacing: normal
    ),
    $body-1: mat.define-typography-level(
      $font-size: 12px,
      $line-height: 16px,
      $font-weight: 400,
      $font-family: null,
      $letter-spacing: normal
    ),
    $body-2: mat.define-typography-level(
      $font-size: 12px,
      $line-height: 14px,
      $font-weight: 400,
      $font-family: null,
      $letter-spacing: normal
    ),
    $caption: mat.define-typography-level(
      $font-size: 10px,
      $line-height: 12px,
      $font-weight: 400,
      $font-family: null,
      $letter-spacing: normal
    ),
    $button: mat.define-typography-level(
      $font-size: 14px,
      $line-height: 14px,
      $font-weight: 500,
      $font-family: null,
      $letter-spacing: normal
    ),
  );
}

// アプリの配色設定
@function _get-app-theme-color() {
  $primary-palette: mat.$indigo-palette;
  $secondary-palette: mat.$amber-palette;
  $warn-palette: mat.$red-palette;

  $app-theme-primary: mat.define-palette($primary-palette);
  $app-theme-accent: mat.define-palette($secondary-palette);
  $app-theme-warn: mat.define-palette(mat.$red-palette);

  @return (
    primary: $app-theme-primary,
    accent: $app-theme-accent,
    warn: $app-theme-warn,
  );
}

// アプリテーマ
@function get-app-theme() {
  $app-color: _get-app-theme-color();
  $app-typography: _get-typography-config();

  @return mat.define-light-theme((
    color: $app-color,
    typography: $app-typography,
    density: -1
  ));
}


// スタイルの適用
$app-theme: get-app-theme();

@include mat.core();
@include mat.all-component-themes($app-theme);
@include mat.typography-hierarchy($app-theme);

// ボタンサイズはデフォルトに戻す
@include mat.button-density(0);

// フォームフィールドはより小さめに
@include mat.form-field-density(-4);

表示が大きくなりすぎないようにアプリケーション全体に対してタイポグラフィは一回り小さめとし、密度も1段階高めています。ただしボタンサイズは小さめである必要がないため標準に戻し、またフォームフィールドは-4と大きく縮めています。このスタイルを適用するとアプリの見た目はこのようになります。

カスタマイズスタイルの適用前後


良く見て頂くと分かるかと思うのですが、このカスタマイズしたスタイル適用後の FormField の文字表示位置が上寄りとなってしまっている上、2つの FormField の高さが微妙に異なります。文字の表示位置がずれてしまっているのは FormField の input の高さに24pxが期待されているためで、 FormField の高さが微妙にずれているのは右の項目に表示されている日付ピッカーを表示するためのアイコンが干渉しているためです。前者はタイポグラフィを変更した結果、後者は密度を変更した結果崩れてしまいました。この崩れた見た目を修正するため、次の CSS も含めます。

// フォームフィールドの見た目調整
.mat-mdc-form-field {
  // テキスト位置調整
  .mat-mdc-form-field-infix {
    input.mat-mdc-input-element {
      height: 24px;
    }
  }

  // ヒント・エラー文字の高さ調整
  .mat-mdc-form-field-bottom-align::before {
    height: 12px;
  }

  // DatePickerのアイコン干渉調整
  .mat-datepicker-toggle {
    .mat-mdc-icon-button {
      &.mat-mdc-button-base {
        width: 40px;
        height: 40px;
        padding: 8px;
      }

      .mat-mdc-button-touch-target {
        width: 40px;
        height: 40px;
      }
    }
  }
}

この調整 CSS を適用することでフォントサイズ・余白共に小さめのアプリケーションとなります。

フォームフィールドのスタイル調整後

今回はこれだけでしたが、このような組み合わせは実際にはまだまだ存在します。明らかに崩れてしまうものから数 px の高さの違いまで様々です。いずれにせよディベロッパーツールを開いて DOM 毎の CSS の適用状態を確認し、原因を探っていく地道な作業が待っています。。

終わりに

大変長くなってしまいましたが、 Angular Material v15 を使用するためのスタイルカスタマイズ方法について紹介しました。

MDC をベースとした Angular Material は始まったばかりであり多くの不具合のような挙動が発生していますが、いずれ収束していくでしょう。また Angular では非推奨に指定された API や機能は指定された2バージョン後で削除されるポリシーです。そのため2023年11月頃にリリースが見込まれる Angular Material v17 では v14 までのコンポーネントが完全に削除されるでしょう。

この記事が皆様の Angular Material ライフの手助けになれば幸いです。

執筆担当者プロフィール
中谷 大造

中谷 大造(日本ビジネスシステムズ株式会社)

情報システム部の中谷です。社内用スクラッチアプリの開発をしています。

担当記事一覧