Theming and dynamic color extraction
Aliases: dynamic color · color role · tonal palette · seed color
What it is
Material components do not paint hex values. They declare a color role: primary, secondary, tertiary, error, surface, on-surface. Dynamic color takes a seed—wallpaper, a brand seed, or the system’s current extraction—builds a tonal palette, and assigns tones from that palette onto the roles. The same button can render as two different hex values on two devices; the role has not changed. It is still “on-primary type on a primary container.”
This is where color comes from and how it can move with the device without rewriting component structure. It is not a restyle-the-skin workflow. Baking a brand spot color into a button bypasses the role layer; once dynamic color is on, that island will disagree with neighboring surfaces and contrast may fall below readable.
Why it happens
Roles split “what the color is for” from “which sample is used.” A generator expands the seed into an ordered set of tones, then maps tones onto roles for light and dark: light themes put high tones on surfaces and low tones on type; dark themes invert the mapping. Components read roles, so theme switches and wallpaper changes do not require component edits—the mapping table changes. The product still “looks like itself” across devices because role relations (what sits on what, what is emphasized) are preserved, not because a particular hex is preserved.
Error, outline, and disabled overlays are roles as well, not a red mixed on the spot. If warning and primary share a role, dynamic color can make them nearly identical and the semantic channel collapses. Contrast is computed on role pairs (surface versus on-surface), not on two swatches in a mockup; contrast that only holds in the file will not survive the user’s wallpaper.
Where it stops holding
Clinical alerts, financial gain/loss, and map legends already occupy the color channel and must not be rewritten by extraction; lock them to roles that do not participate in the seed, or to non-theme colors. Legally constrained brand spots belong on marks and illustration in the content layer, not in every primary role—otherwise the mark drifts with wallpaper. If the user has forced a high-contrast scheme, dynamic color must yield rather than overlay a low-contrast seed pairing. Devices with no wallpaper and no extraction (some vehicle screens, television, embedded panels) should fall back to a static role table instead of pretending a seed exists. Print and projection have no “current wallpaper”; the mapping is meaningless there.
Applying it
- Reference color roles from components and pages; do not hard-code values on controls. Feed the brand as a seed, not as the fill of every button.
- Give error, warning, and success their own roles, and check contrast of those pairs in light, dark, and at least two extreme wallpapers.
- Exempt marks, illustration, and data colors from the mapping, and document which colors do not participate.
- Verify by changing wallpaper or toggling light/dark: the primary button, surface, error, and body type must remain distinguishable as roles—emphasis still on primary, error still unlike primary, body still on the surface. Any patch that is readable only after a designer repaints it had values written into the component. Then enable system high contrast and confirm dynamic color does not override it.