マテリアルカラー、やめました。

私たちはKotlinMultiplatformでアプリを開発しています。
クロスプラットフォームでの設計と実装を進める中で、UI設計において避けては通れないのが「デザインシステム」の選定です。

その中でもよく採用されるのが、Googleが提唱する「Material Design」。この記事では、

- なぜマテリアルカラーを採用しなかったのか
- 代わりにどのようにテーマを構築したのか

について、実体験をもとにお話しします。

## 1. そもそもマテリアルカラーとは？

マテリアルデザインは、Googleが提唱するデザインシステムで、UI設計におけるガイドラインやコンポーネント、そして配色ルールを提供します。

その中でも「マテリアルカラー」は、

- `primary`（主要色）
- `secondary`（補助色）
- `surface` や `background` といった背景用の色

など、**“役割ベース”のカラー命名** が特徴です。

### 💡 利用するメリットは？

- UI全体に統一感が出る
- デザイナーとエンジニアの共通言語になる
- アクセシビリティ（視認性）の基準を満たしやすい

つまり、**迷わず使える**。そう、**迷わなければ**。


## 2. なぜマテリアルカラーを使わなかったのか

正直なところ、最初はマテリアルカラーを使っていました。けれど、次第に違和感を覚えるようになったのです。

### 2.1 命名がフィットしない

たとえば、リンクの色。

> 「このリンクの色は primary（青）と同じ。でも、これをprimaryと呼ぶのは何か違う……」

マテリアルの命名は「役割ベース」ですが、**私たちのUI設計は意味ベース** だったのです。進捗バーの青、完了マークの緑、注意喚起の赤──色はその“意味”を伝えるためのものでした。

### 2.2 デザイン思想とのズレ

マテリアルデザインは、「色でユーザーを誘導する」思想に基づいています。ボタンやFABが浮かび上がり、アクセントカラーで行動を促す設計です。

でも、私たちのアプリでは、**あくまで「コンテンツが主役」**。

色やUIが主張しすぎず、ユーザーがコンテンツに集中できる静かな世界観を重視していました。操作が直感的であれば、派手なボタンで誘導する必要はないのです。

その結果、マテリアルカラーは UI の意図と噛み合わず、命名や実装の両面で迷いが生じてしまったのです。

## 3. カスタムテーマ、こう実装しました

マテリアルカラーを使わないと決めた以上は、自分たちで配色ルールを設計する必要があります。そこで、以下の方針に基づいてカスタムテーマを実装しました。


### 3.1 カラースキームの構造定義

まずは、アプリ内で使う色をカテゴリごとに整理し、それぞれのスキーム（色設計）をデータクラスとして定義します。

```kotlin
data class ColorSchemes(
    val layout: LayoutColorScheme,
    val dialog: DialogColorScheme,
    val text: TextColorScheme,
)

data class TextColorScheme(
    val title: Color,
    val link: Color,
)
```

- `ColorSchemes` は、全体のカラースキーマをまとめたエントリーポイント。
- 各セクション（`layout`, `dialog`, `text`）で使う色をスキームごとに分けています。



### 3.2 明暗テーマ別のカラー定義

ライトモード／ダークモードに応じて、それぞれのテーマ用カラーを定義します。

```kotlin
fun lightTextColors(): TextColorScheme =
    TextColorScheme(
        title = Color(0xFF000000),
        link = Color(0xFF007AFF),
    )

fun darkTextColors(): TextColorScheme =
    TextColorScheme(
        title = Color(0xFFFFFFFF),
        link = Color(0xFF8AB4F8),
    )
```

※ `lightLayoutColors()` や `lightDialogColors()` は省略していますが、同様に定義します。


### 3.3 テーマごとの具体的なカラースキームを構築

```kotlin
val CustomLightColors = ColorSchemes(
    layout = lightLayoutColors(),
    dialog = lightDialogColors(),
    text = lightTextColors(),
)

val CustomDarkColors = ColorSchemes(
    layout = darkLayoutColors(),
    dialog = darkDialogColors(),
    text = darkTextColors(),
)

```


### 3.4 グローバルで使えるテーマオブジェクトの提供

```kotlin
val LocalCustomColors = staticCompositionLocalOf { CustomLightColors }

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) {
    val colors = if (darkTheme) CustomDarkColors else CustomLightColors
    CompositionLocalProvider(LocalCustomColors provides colors) {
        content()
    }
}

object CustomTheme {
    val colors: ColorSchemes
        @Composable
        get() = LocalCustomColors.current
}

```

- `MyAppTheme` でテーマを切り替え、`CustomTheme.colors.xxx` として各所で使用可能になります。


### 3.5 UIへの適用例

```kotlin
Text(
    text = text,
    color = CustomTheme.colors.text.title,
)

```

このように、明確な意味に基づいた命名でカラーを管理することで、「primary か secondary か」といった迷いはなくなり、**この色は何のためにあるのか** がコード上でも明確になります。


## おわりに

マテリアルカラーは非常に優れた仕組みです。しかし、それがすべてのプロジェクトにフィットするとは限りません。

UIに強い色の主張がいらない。意味で色を使い分けたい。そんなときには、自分たちのルールで配色を設計するのも、立派な選択肢です。

デザインと実装の“しっくりくる”を求めて、私たちはカスタムテーマを選びました。 

同じように悩んでいる方の参考になれば嬉しいです。