Material 3 Color System
Understanding the Material 3 color system and how to use it effectively with React Native Dynamic Theme.
Overview
Material 3 introduces a new approach to color that emphasizes personal expression while maintaining usability and accessibility. The system is built around tonal palettes that generate harmonious color schemes from a single source color.
Core Concepts
Source Color
The source color is the foundation of your color scheme. It can come from:
- User's wallpaper (Android 12+ Material You)
- Brand colors
- Custom selections
- Image extraction
import { getExtendedDynamicSchemeFromSourceColor } from 'react-native-dynamic-theme';
// Generate scheme from brand color
const scheme = getExtendedDynamicSchemeFromSourceColor('#1976D2');
Tonal Palettes
Each color scheme generates 6 tonal palettes:
- Primary - Main brand color and its tones
- Secondary - Complementary color for accents
- Tertiary - Contrasting accent color
- Error - Error states and warnings
- Neutral - Surface and background colors
- Neutral Variant - Subtle backgrounds and outlines
Each palette contains 13 tone values from 0 (black) to 100 (white):
import { getExtendedDynamicScheme } from 'react-native-dynamic-theme';
const scheme = getExtendedDynamicScheme('#FF5722');
// Access specific tones
const primaryDark = scheme.palettes.primary[20]; // Dark primary
const primaryNormal = scheme.palettes.primary[40]; // Normal primary
const primaryLight = scheme.palettes.primary[80]; // Light primary container
// Available tones: 0, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100
Color Roles
Material 3 defines semantic color roles that ensure proper contrast and usability:
Primary Colors
// Primary colors for main actions and branding
{
primary: string; // Main brand color
onPrimary: string; // Text/icons on primary
primaryContainer: string; // Prominent containers
onPrimaryContainer: string; // Text/icons on primary container
inversePrimary: string; // Primary color in dark theme
}
Secondary Colors
// Secondary colors for less prominent actions
{
secondary: string;
onSecondary: string;
secondaryContainer: string;
onSecondaryContainer: string;
}
Tertiary Colors
// Tertiary colors for contrasting accents
{
tertiary: string;
onTertiary: string;
tertiaryContainer: string;
onTertiaryContainer: string;
}
Surface Colors
// Surface colors for backgrounds and containers
{
background: string; // Main background
onBackground: string; // Text on background
surface: string; // Component backgrounds
onSurface: string; // Text on surface
surfaceVariant: string; // Subtle surface variation
onSurfaceVariant: string; // Text on surface variant
// Surface containers (elevation levels)
surfaceContainerLowest: string; // Level 0
surfaceContainerLow: string; // Level 1
surfaceContainer: string; // Level 2
surfaceContainerHigh: string; // Level 3
surfaceContainerHighest: string; // Level 4
}
Usage Guidelines
When to Use Each Color
Primary
- Main actions (floating action buttons, prominent buttons)
- Active states (selected items, progress indicators)
- Brand elements
<TouchableOpacity style={{
backgroundColor: colors.primary,
// Use for main call-to-action buttons
}}>
<Text style={{ color: colors.onPrimary }}>
Sign Up
</Text>
</TouchableOpacity>
Secondary
- Less prominent actions
- Filter chips and toggles
- Secondary navigation
<View style={{
backgroundColor: colors.secondaryContainer,
// Use for secondary actions and containers
}}>
<Text style={{ color: colors.onSecondaryContainer }}>
Filter Options
</Text>
</View>
Tertiary
- Contrasting accents
- Highlighting content
- Input field accents
<View style={{
backgroundColor: colors.tertiaryContainer,
// Use for contrasting elements that need attention
}}>
<Text style={{ color: colors.onTertiaryContainer }}>
New Feature!
</Text>
</View>
Surface Containers
Different elevation levels for layered interfaces:
// Level 0 - Lowest elevation
<View style={{ backgroundColor: colors.surfaceContainerLowest }}>
<Text>Base content</Text>
</View>
// Level 1 - Cards at rest
<View style={{ backgroundColor: colors.surfaceContainerLow }}>
<Text>Card content</Text>
</View>
// Level 2 - Raised cards
<View style={{ backgroundColor: colors.surfaceContainer }}>
<Text>Elevated card</Text>
</View>
// Level 3 - Dialogs and menus
<View style={{ backgroundColor: colors.surfaceContainerHigh }}>
<Text>Dialog content</Text>
</View>
// Level 4 - Navigation drawers
<View style={{ backgroundColor: colors.surfaceContainerHighest }}>
<Text>Navigation content</Text>
</View>
Contrast Levels
Material 3 supports multiple contrast levels for accessibility:
import { getExtendedDynamicScheme } from 'react-native-dynamic-theme';
const scheme = getExtendedDynamicScheme('#006971');
// Standard contrast (default)
const standardColors = scheme.schemes.light;
// Medium contrast (enhanced readability)
const mediumContrastColors = scheme.schemes.lightMediumContrast;
// High contrast (maximum accessibility)
const highContrastColors = scheme.schemes.lightHighContrast;
// Choose based on user preference or accessibility needs
const colors = userNeedsHighContrast
? highContrastColors
: standardColors;
Color Harmonies
Material 3 automatically creates harmonious color relationships:
Analogous Colors
Colors that are adjacent on the color wheel:
const scheme = getExtendedDynamicScheme('#2196F3'); // Blue
// These will be harmonious
const primary = scheme.schemes.light.primary; // Blue
const secondary = scheme.schemes.light.secondary; // Blue-green
const tertiary = scheme.schemes.light.tertiary; // Blue-purple
Complementary Accents
The tertiary color provides complementary contrast:
const warmScheme = getExtendedDynamicScheme('#FF5722'); // Orange
// Orange primary with blue-green tertiary for contrast
const warmPrimary = warmScheme.schemes.light.primary; // Orange
const coolAccent = warmScheme.schemes.light.tertiary; // Blue-green
Custom Color Mappings
Map Material 3 colors to your design system:
import { ColorScheme } from 'react-native-dynamic-theme';
// Create semantic color mappings
export const createSemanticColors = (colors: ColorScheme) => ({
// Status colors
success: colors.tertiary, // Use tertiary for success
warning: colors.error, // Error palette for warnings
info: colors.secondary, // Secondary for info
error: colors.error, // Primary error color
// Interactive states
link: colors.primary, // Links use primary
visited: colors.tertiary, // Visited links use tertiary
focus: colors.primary, // Focus rings use primary
// Content hierarchy
textPrimary: colors.onSurface, // Main text
textSecondary: colors.onSurfaceVariant, // Supporting text
textDisabled: colors.onSurface + '61', // 38% opacity
// Borders and dividers
border: colors.outline, // Standard borders
borderSubtle: colors.outlineVariant, // Subtle borders
divider: colors.outlineVariant, // Content dividers
});
// Usage
const MyComponent = () => {
const { colors } = useTheme();
const semanticColors = createSemanticColors(colors);
return (
<View style={{ borderColor: semanticColors.border }}>
<Text style={{ color: semanticColors.textPrimary }}>
Primary text
</Text>
<Text style={{ color: semanticColors.textSecondary }}>
Secondary text
</Text>
</View>
);
};
Best Practices
Accessibility
- Always use paired colors: Use
onPrimary
text onprimary
backgrounds - Test contrast ratios: Ensure minimum 4.5:1 contrast for normal text
- Provide high contrast options: Support users with visual impairments
// ✅ Good - proper color pairing
<View style={{ backgroundColor: colors.primary }}>
<Text style={{ color: colors.onPrimary }}>Readable text</Text>
</View>
// ❌ Bad - poor contrast
<View style={{ backgroundColor: colors.primary }}>
<Text style={{ color: colors.secondary }}>Hard to read</Text>
</View>
Consistency
- Use semantic roles: Don't use colors arbitrarily
- Maintain hierarchy: Primary for main actions, secondary for supporting
- Test both themes: Ensure designs work in light and dark modes
Performance
- Memoize color calculations: Prevent unnecessary recalculations
- Use selective color hooks: Only subscribe to needed colors
- Cache tonal palettes: Store generated palettes for reuse
Color Tools and Utilities
Color Picker Component
const ColorPicker = ({ onColorSelect }: { onColorSelect: (color: string) => void }) => {
const presets = [
'#006971', '#FF5722', '#2196F3', '#9C27B0',
'#4CAF50', '#FF9800', '#E91E63', '#795548'
];
return (
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
{presets.map(color => (
<TouchableOpacity
key={color}
style={{
backgroundColor: color,
width: 50,
height: 50,
borderRadius: 25,
margin: 5,
}}
onPress={() => onColorSelect(color)}
/>
))}
</View>
);
};
Palette Visualizer
const PaletteVisualizer = ({ sourceColor }: { sourceColor: string }) => {
const scheme = getExtendedDynamicScheme(sourceColor);
return (
<ScrollView>
{Object.entries(scheme.palettes).map(([paletteName, palette]) => (
<View key={paletteName}>
<Text>{paletteName}</Text>
<View style={{ flexDirection: 'row' }}>
{Object.entries(palette).map(([tone, color]) => (
<View
key={tone}
style={{
backgroundColor: color,
width: 30,
height: 30,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text style={{
color: parseInt(tone) > 50 ? '#000' : '#FFF',
fontSize: 8,
}}>
{tone}
</Text>
</View>
))}
</View>
</View>
))}
</ScrollView>
);
};
Understanding and properly implementing the Material 3 color system ensures your app provides a cohesive, accessible, and beautiful user experience that adapts to user preferences while maintaining your brand identity.