Material 3 색 구성표 및 시드 색상
ColorScheme.fromSeed를 사용해 하나의 시드 색상에서 조화로운 색 구성표를 생성합니다.
Material 3 색 구성표 및 시드 색상은(는) CoddyKit의 무료 Flutter Mobile Development 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Flutter Mobile Development 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Flutter Mobile Development 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
What Is a Color Scheme in Material 3?
Material 3 (M3) introduced a structured color scheme — a set of named color roles that your entire app uses consistently. Instead of scattering raw hex values across widgets, you define roles like primary, onPrimary, surface, error, and Flutter's theming engine applies them automatically.
- primary — the brand color used for buttons, FABs, active tabs
- onPrimary — text/icons drawn on top of primary
- surface — background of cards, sheets, dialogs
- onSurface — text drawn on surfaces
- error / onError — validation and alert colors
Every role comes in both a light and a dark variant, letting you support both themes with one color scheme definition.
The Problem ColorScheme.fromSeed Solves
Before M3, developers had to hand-pick every color role — primary, secondary, tertiary, surfaces, containers, and their 'on' counterparts. That's 25+ values, and getting accessible contrast ratios right manually is error-prone.
ColorScheme.fromSeed solves this by accepting a single seed color and using Google's HCT color space algorithm (Hue, Chroma, Tone) to generate a full, harmonious, WCAG-accessible palette automatically.
- One input → 25+ output roles
- Guaranteed contrast between foreground and background pairs
- Light and dark variants from the same seed
- Consistent with the Material You design language
Your First ColorScheme.fromSeed
Using ColorScheme.fromSeed is straightforward. Pass it to ThemeData inside your MaterialApp. Flutter does the rest.
The seedColor parameter accepts any Color — typically your brand color. Flutter then derives the entire palette from it.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'M3 Theming Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4), // brand purple
),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Color Scheme Demo')),
body: const Center(child: Text('Hello Material 3')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.palette),
),
);
}
}Light and Dark from the Same Seed
One of the biggest wins of ColorScheme.fromSeed is that you can generate both a light and a dark theme from the same seed color, ensuring visual consistency across modes.
Pass the brightness parameter to switch between them. Flutter adjusts tones automatically — dark themes use lighter tones of your seed on dark surfaces.
import 'package:flutter/material.dart';
const Color _brandColor = Color(0xFF00897B); // teal brand
ThemeData lightTheme() => ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: _brandColor,
brightness: Brightness.light,
),
useMaterial3: true,
);
ThemeData darkTheme() => ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: _brandColor,
brightness: Brightness.dark,
),
useMaterial3: true,
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: lightTheme(),
darkTheme: darkTheme(),
themeMode: ThemeMode.system, // follows device setting
home: const Scaffold(
body: Center(child: Text('Adaptive theme')),
),
);
}
}Accessing Color Roles in Widgets
Once your ColorScheme is set up, access its roles anywhere via Theme.of(context).colorScheme. This keeps your widgets decoupled from hard-coded colors and automatically respects light/dark mode.
Common roles you will use in widgets:
colorScheme.primary— highlight color for interactive elementscolorScheme.onPrimary— text/icon color on primary backgroundscolorScheme.surface— card and dialog backgroundscolorScheme.onSurface— default text colorcolorScheme.error— error state indicators
import 'package:flutter/material.dart';
class StatusBadge extends StatelessWidget {
final String label;
final bool isActive;
const StatusBadge({super.key, required this.label, required this.isActive});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: isActive ? cs.primaryContainer : cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
),
child: Text(
label,
style: TextStyle(
color: isActive ? cs.onPrimaryContainer : cs.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
),
);
}
}Container Roles: primaryContainer and Friends
M3 introduced container roles — softer, lower-chroma versions of core colors designed for backgrounds of UI elements like chips, cards, and filled buttons.
primaryContainer— soft primary background (great for selected chips, filled tonal buttons)onPrimaryContainer— text on top of primaryContainersecondaryContainer/tertiaryContainer— analogous roles for secondary and tertiary paletteserrorContainer— soft red for inline error banners
Using containers instead of raw primary for backgrounds gives you visual hierarchy and keeps contrast ratios safe without manually adjusting opacity.
import 'package:flutter/material.dart';
class InfoCard extends StatelessWidget {
final String title;
final String body;
const InfoCard({super.key, required this.title, required this.body});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Card(
color: cs.primaryContainer,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: cs.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
body,
style: TextStyle(color: cs.onPrimaryContainer),
),
],
),
),
);
}
}Overriding Specific Roles
Sometimes the algorithmically generated color for a specific role doesn't match your brand guide exactly. You can override individual roles by using ColorScheme.fromSeed(...).copyWith(...). This keeps all the auto-generated roles while letting you pin specific ones.
This pattern is useful when, for example, your design system requires an exact red for error that differs from what the HCT algorithm produces.
import 'package:flutter/material.dart';
ThemeData buildTheme() {
// Start from seed, then pin specific roles
final base = ColorScheme.fromSeed(
seedColor: const Color(0xFF1565C0), // deep blue brand
);
return ThemeData(
colorScheme: base.copyWith(
// Exact error red from our design system
error: const Color(0xFFB00020),
onError: Colors.white,
// Pin tertiary to our accent orange
tertiary: const Color(0xFFE65100),
onTertiary: Colors.white,
),
useMaterial3: true,
);
}Dynamic Color: Adapting to Wallpaper
Android 12+ supports Dynamic Color — the system extracts a seed color from the user's wallpaper and applies it to all apps. Flutter supports this via the dynamic_color package.
The pattern is: try to load the system color scheme; fall back to your own seed if unavailable (iOS, older Android, or when the user hasn't set a wallpaper).
This makes your app feel native and personalized on supported devices while remaining consistent everywhere else.
// pubspec.yaml: dynamic_color: ^1.7.0
import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
const Color _fallbackSeed = Color(0xFF6750A4);
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
final lightScheme = lightDynamic ??
ColorScheme.fromSeed(
seedColor: _fallbackSeed,
brightness: Brightness.light,
);
final darkScheme = darkDynamic ??
ColorScheme.fromSeed(
seedColor: _fallbackSeed,
brightness: Brightness.dark,
);
return MaterialApp(
theme: ThemeData(colorScheme: lightScheme, useMaterial3: true),
darkTheme: ThemeData(colorScheme: darkScheme, useMaterial3: true),
themeMode: ThemeMode.system,
home: const Scaffold(
body: Center(child: Text('Dynamic Color')),
),
);
},
);
}
}Previewing Your Palette with a Color Swatch Grid
While developing, it's useful to render all key color roles on-screen so you can verify the generated palette visually. This debug screen is a quick way to check contrast and harmony before shipping.
Build a simple grid that maps role names to their colors and 'on' counterparts, using the live ColorScheme from context.
import 'package:flutter/material.dart';
class PalettePreviewScreen extends StatelessWidget {
const PalettePreviewScreen({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final roles = [
('Primary', cs.primary, cs.onPrimary),
('PrimaryContainer', cs.primaryContainer, cs.onPrimaryContainer),
('Secondary', cs.secondary, cs.onSecondary),
('SecondaryContainer', cs.secondaryContainer, cs.onSecondaryContainer),
('Tertiary', cs.tertiary, cs.onTertiary),
('Surface', cs.surface, cs.onSurface),
('Error', cs.error, cs.onError),
('ErrorContainer', cs.errorContainer, cs.onErrorContainer),
];
return Scaffold(
appBar: AppBar(title: const Text('Color Palette')),
body: ListView(
children: roles.map((r) {
final (name, bg, fg) = r;
return Container(
height: 56,
color: bg,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(name, style: TextStyle(color: fg, fontWeight: FontWeight.w500)),
);
}).toList(),
),
);
}
}Common Pitfalls with Color Schemes
Avoid these mistakes when working with M3 color schemes:
- Hard-coding hex colors in widgets — Always use
Theme.of(context).colorScheme.*so widgets respond to theme changes and dark mode. - Using Colors.white / Colors.black directly — These break in dark mode. Use
colorScheme.onSurfaceorcolorScheme.surfaceinstead. - Forgetting
useMaterial3: true— Without this flag, Flutter uses M2 defaults and your M3 color roles may not be applied correctly to built-in widgets. - Using
primaryColorfrom ThemeData — That's an M2 concept. In M3, all widgets read fromcolorScheme. SetThemeData.colorScheme, notThemeData.primaryColor. - Mismatching brightness and background — Generating a
Brightness.darkscheme but pairing it with a white scaffold will produce unreadable UI.
Putting It All Together: A Themed App Shell
Here is a complete minimal app shell that demonstrates best practices: seed-based color scheme, light/dark support, useMaterial3: true, and widgets that read roles from context — no hard-coded colors anywhere.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
const Color _seed = Color(0xFF2E7D32); // forest green brand
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Green Theme',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: _seed,
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: _seed,
brightness: Brightness.dark,
),
useMaterial3: true,
),
themeMode: ThemeMode.system,
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
appBar: AppBar(
title: Text('Dashboard', style: TextStyle(color: cs.onPrimary)),
backgroundColor: cs.primary,
),
body: Center(
child: FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.eco),
label: const Text('Go Green'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: cs.tertiary,
foregroundColor: cs.onTertiary,
child: const Icon(Icons.add),
),
);
}
}Quick Check: ColorScheme.fromSeed
Test your understanding of how ColorScheme.fromSeed works in Material 3.
Lesson Recap: Material 3 Color Schemes and Seed Colors
Here's what you learned in this lesson:
- ColorScheme in M3 defines a full set of named color roles (primary, surface, error, containers, and their 'on' counterparts) that Flutter widgets consume automatically.
- ColorScheme.fromSeed generates all 25+ roles from a single brand color using Google's HCT algorithm, guaranteeing harmony and accessibility.
- Pass
brightness: Brightness.lightorBrightness.darkto generate appropriate variants from the same seed — always pair withMaterialApp.darkThemefor full dark mode support. - Access roles in widgets via
Theme.of(context).colorScheme— never hard-code hex values in widget files. - Use container roles (
primaryContainer,onPrimaryContainer) for backgrounds of UI components; they provide visual hierarchy without manual opacity tweaks. - Override individual roles with
.copyWith()when a specific brand color must be exact. - On Android 12+, Dynamic Color via the
dynamic_colorpackage extracts the system seed; always provide a fallback for other platforms. - Always set
useMaterial3: trueonThemeDataso built-in widgets read from the M3 color scheme correctly.
자주 묻는 질문
“Material 3 색 구성표 및 시드 색상” 강의는 무료인가요?
네 — “Material 3 색 구성표 및 시드 색상” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Flutter Mobile Development 강의 전체를 잠금 해제할 수 있습니다. Flutter Mobile Development 강의에는 총 4개의 강의가 포함되어 있습니다.
“Material 3 색 구성표 및 시드 색상”에서 뭘 배우나요?
ColorScheme.fromSeed를 사용해 하나의 시드 색상에서 조화로운 색 구성표를 생성합니다. 브라우저에서 직접 실행하는 실습 코드로 Flutter Mobile Development을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Flutter Mobile Development을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Flutter Mobile Development은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“Material 3 색 구성표 및 시드 색상” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Flutter Mobile Development 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Flutter Mobile Development 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- Material 3 색 구성표 및 시드 색상
- 동적 색상 및 적응형 라이트·다크 테마
- 브랜드 토큰을 위한 사용자 지정 ThemeExtension
- 반응형 타이포그래피 및 구성 요소 테마