FigmaのVariablesからFlutterのColorSchemeまで!変更に強い色設計フローを実案件で試した話
🔥 はじめに
アプリ開発において、FigmaなどのデザインツールからFlutterへの落とし込みで「カラー設計をどう実装・管理すべきか?」という課題に直面したことはありませんか?
特に以下のような悩みは、実務でもよく聞かれます。
- 複数人開発でカラーの使い方がブレてしまう
- 色定義の粒度がバラバラで実装に迷う
- ダークモードやブランドカラー変更に柔軟に対応できない
この記事では、実際の案件で試して効果を感じた「FigmaのVariables → FlutterのColorScheme設計フロー」をベースに、
- Primitive token(色の原子)
- Semantic token(意味を持つ色)
- ThemeExtension(Flutterの拡張テーマ)
といった概念を交えながら、変更に強くチームで扱いやすいカラー設計の方法を私なりにまとめます。
TL;DR
- Figma では Primitive → Semantic の2階層で “意味づけ” しておく
- Flutter では Semantic を ColorScheme に直マップ、足りない分を ThemeExtension に逃す
- こうすると ブランドカラー変更もダークモードも 1 箇所修正で完結
- theme_tailor で copyWith/lerp 問題もさよなら!
🫡 【結論から紹介】私のカラー設計フロー
Figma側
Figmaでは以下のようにカラー変数(Variables)を階層構造で管理します。
PrimitiveToken(例: gray100, orange500)
↓
SemanticToken(例: primary, surface, onPrimary)
SemanticTokenは、最終的にFlutterのColorSchemeへとマッピングされるよう設計しています。
画像はFigmaのvariablesで定義しているSemantic Token。

Flutter側
Flutterでは、Material Design準拠のColorSchemeを軸にSemanticTokenを定義。
必要に応じて、ThemeExtensionで独自定義(例: inactive, onInactive)を追加しています。
Theme.of(context).colorScheme.primaryのように利用することで、UI側の実装も統一され、メンテナンス性が向上します。
class AppColorScheme {
// ライトテーマ
static const light = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFFFE782F),
onPrimary: Color(0xFFFFFFFF),
secondary: Color(0xFFEEC223),
onSecondary: Color(0xFFFFFFFF),
error: Color(0xFFEE2323),
onError: Color(0xFFFFFFFF),
surface: Color(0xFFFFFFFF),
onSurface: Color(0xFF1E1E1E),
outline: Color(0xFFFE782F),
outlineVariant: Color(0xFFA2A2A2),
);
// ライトテーマ専用の ThemeExtension
static const colorSchemeLightX = AppColorSchemeX(
inactive: Color(0xFFAFAFAF),
onInactive: Color(0xFFFFFFFF),
);
// ダークモードに対応する場合は以下
// static const dark = ColorScheme(
// brightness: Brightness.dark,
// primary: Color(0xFFFE782F),
// onPrimary: Color(0xFFFFFFFF),
// secondary: Color(0xFFEEC223),
// onSecondary: Color(0xFFFFFFFF),
// error: Color(0xFFEE2323),
// onError: Color(0xFFFFFFFF),
// surface: Color(0xFFFFFFFF),
// onSurface: Color(0xFF1E1E1E),
// outline: Color(0xFFFE782F),
// outlineVariant: Color(0xFFA2A2A2),
// );
// static const colorSchemeLightX = AppColorSchemeX(
// inactive: Color(0xFFAFAFAF),
// onInactive: Color(0xFFFFFFFF),
// );
⚙️ Design Tokenでカラーを定義する意味
FigmaなどのDesign Systemであらかじめカラーを定義する理由は明確です。
- アプリ / Web / 広告 などで横断的に統一感ある世界観を出せる
- チーム間で「使っていい色(ダメな色)」が共有できる
- ブランド変更にも柔軟に対応できる
このとき、まず定義されるのがPrimitive tokenと呼ばれる原始的な色たちです(例:gray100, blue500など)。
この辺りのTokenについてもっと詳しく知りたい方は以下のFigma公式の動画をご覧ください!
動画冒頭で、なぜPrimitive Tokenを作成するのかなどが視覚的に紹介されていて、かなりわかりやすいです!
🎯 Flutterで扱うべきはSemantic Token?
Design Systemでよく登場するのが Primitive Token(gray100 や blue500 などの純粋な色定義)ですが、Flutterでそのままコード化するのは避けたほうが良いと感じています。
理由はシンプルで、
どの色を使えばいいのかわからない😅🌀
たとえば gray100 や gray300 のような色を自由に使えてしまうと、意図しない色の使い分けが生まれ、デザインの統一感が崩れやすくなります。
✅ Semantic Tokenの導入が効果的
Semantic Token とは、「意味のある用途にひもづいた色」のことです。
- gray100 → backgroundSubtle
- blue500 → buttonPrimary
というように「この色は何のためにあるのか?」を明確にした定義がSemantic Tokenです。
FlutterではこのSemantic Tokenを ColorScheme や ThemeExtension にマッピングしておくことで、設計意図がブレない UI 実装が可能になります。
🔧 Primitive Tokenはあえてコードにしない
Primitive Token自体をコードにしてしまうと、再び「gray100 vs gray200問題」が発生します。
そのため、コード化するのはSemantic Tokenのみに限定するのがベターだと感じています。
- Primitive Token(gray100, blue500 など)はFigma上の変数にとどめておく
- Semantic Token(primary, surfaceなど)だけをFlutterで定義
このルールを定めることで、実装者が迷わずに済み、設計の意図を守ったまま実装できます。
🎨 FlutterのColorSchemeクラスについて
Flutterでは ThemeData.colorScheme を使うことで、マテリアルデザインに沿った色の定義ができます。
これは以下のような利点があります👇
ColorSchemeで色を指定するメリット
- マテリアルデザインとの親和性が高い
- primary, secondary, error, background などの意味があらかじめ定義されている
- 各UIコンポーネントが自動的にそれらの色を使用してくれる(例:FilledButtonの背景色はprimary)
- Light / Dark Theme 両対応がしやすい
- 同じ ColorScheme に対して、ThemeMode.light / ThemeMode.dark に応じて色を切り替えられる
- 設計ルールを定着させやすい
- 意味ベースで colorScheme.surface のように指定することで「なぜこの色か」が明確になる(=Semantic Token)
ColorSchemeの実装例
※AppColorSchemeXの実装については後述します。
このクラス内でcolorSchemeLightXを記載することで、カラー定義が1ファイルにまとまるところが個人的にはおすすめポイントです💡
class AppColorScheme {
// ライトテーマ
static const light = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFFFE782F),
onPrimary: Color(0xFFFFFFFF),
secondary: Color(0xFFEEC223),
onSecondary: Color(0xFFFFFFFF),
error: Color(0xFFEE2323),
onError: Color(0xFFFFFFFF),
surface: Color(0xFFFFFFFF),
onSurface: Color(0xFF1E1E1E),
outline: Color(0xFFFE782F),
outlineVariant: Color(0xFFA2A2A2),
);
// ライトテーマ専用の ThemeExtension
static const colorSchemeLightX = AppColorSchemeX(
inactive: Color(0xFFAFAFAF),
onInactive: Color(0xFFFFFFFF),
);
// ダークモードに対応する場合は以下
// static const dark = ColorScheme(
// brightness: Brightness.dark,
// primary: Color(0xFFFE782F),
// onPrimary: Color(0xFFFFFFFF),
// secondary: Color(0xFFEEC223),
// onSecondary: Color(0xFFFFFFFF),
// error: Color(0xFFEE2323),
// onError: Color(0xFFFFFFFF),
// surface: Color(0xFFFFFFFF),
// onSurface: Color(0xFF1E1E1E),
// outline: Color(0xFFFE782F),
// outlineVariant: Color(0xFFA2A2A2),
// );
// static const colorSchemeLightX = AppColorSchemeX(
// inactive: Color(0xFFAFAFAF),
// onInactive: Color(0xFFFFFFFF),
// );
🤔 AppColorsクラスなどではダメか?
もちろん、すべての色を AppColors.primary のように定義して使う設計もできます。
(私も使っていたし、プロジェクトによっては利用する)
ただし、それでは以下のような問題が発生します。
- ボタンやカードなどのマテリアルウィジェットが自動的に ColorScheme を使ってくれなくなる
- Themeの切り替え時(特にダークモード)に自前で切り替え処理を実装する必要がある
- ThemeData に色がバインドされていないため、チーム開発で一貫性が崩れやすくなる
そのため、基本的には ColorScheme に semantic tokenに該当する色のみを登録し、それ以外を拡張したい場合に次の方法が役立ちます👇
😡 【番外編】そもそもColorSchemeってどこに利用されているかわかりづらい!
わかります。
そんな方のために、以下のソースコードおよびWebアプリを用意しています!
READMEを確認して実際に色を変更してみてください!
▼ GitHub
▼ Webサイト
ThemeExtension(Flutterの拡張テーマ)を利用する
Flutter 3.0以降では、ThemeExtension を使って ColorSchemeでは定義できない独自のテーマプロパティを拡張できます。
🧩 どんな時に使う?
success, warning, info などマテリアルで定義されていないカラーなどを追加できます。
また、今回は色について記事なので詳細は省略しますが、Padding, BorderRadius, Shadowなど、UIスタイルの定義も可能です。
✍️ ThemeExtensionの実装例
マテリアルデザインではカバーされない「独自の意味を持つ色」には、ThemeExtensionを使って拡張します。
@TailorMixin()
class AppColorSchemeX extends ThemeExtension<AppColorSchemeX>
with _$AppColorSchemeXTailorMixin {
const AppColorSchemeX({required this.inactive, required this.onInactive});
@override
final Color inactive;
@override
final Color onInactive;
}
theme_tailorを使えば、copyWithやlerpの定義を自動生成できるので管理も楽です。
詳細は以下をご覧ください!
💡 ブランドカラーの変更やダークモード対応に強くなる理由
この設計の最大のメリットは、変更に強いアーキテクチャになることです。
✅ ブランド変更に対応しやすい
たとえばブランドカラーが#FE782F → #2D9CDBに変更になったとしても、AppColorScheme.light.primaryの値を変更するだけ。
ColorSchemeに基づいて構成されたUIなら、全体が一貫して置き換わります。
これは特に開発初期段階でカラーリングがFIXしていないけど、スケジュール的に実装はしていきたい...みたいな時に有効だったと感じています!
🌗 ダークモードも1定義で切り替え
ThemeMode.lightとThemeMode.darkの切り替えに応じて、ColorScheme.light()とColorScheme.dark()を切り替えるだけ。
手動で色を変える必要はほぼなくなります。
🧠 まとめ
- FigmaではPrimitive Token → Semantic Tokenの順でColorを設計
- FlutterではSemantic Tokenに該当するものだけをColorSchemeで管理
- 足りない色はThemeExtensionで拡張して柔軟に管理
- theme_tailorを使えば、拡張テーマのメンテも簡単
- ブランドカラーやダークモード対応も一元化でき、設計がブレない
最後に
何か不明点や間違っている部分などあればコメントください🙇🏻
Discussion