Development

Using a Palette Generator for Flutter Themes

Flutter expects colors to arrive with jobs already assigned — a primary, a surface, an "on" color for every background. Here is how to map a generated palette onto that structure.

Three mobile app mockups showing a coordinated teal, coral and cream color palette for theme design

Flutter's theming system expects colors to arrive with jobs already assigned — a primary, a surface, and an "on" color for the text that sits on each. A raw palette from a palette generator is just colors; the work is mapping them onto that structure so the whole app themes from one place.

Key takeaways

  • Assign roles before writing code: primary, secondary, surface, on-surface.
  • Every background color needs a verified "on" color — that is a contrast decision, not a taste one.
  • Keep raw colors in one constants file and reference them by name everywhere else.
  • One well-built palette can drive both light and dark schemes.

Generate with roles in mind

Before touching code, decide which generated color plays which part. A five-color palette maps naturally onto the roles Flutter expects:

  • The most saturated color becomes your primary.
  • A darker or complementary color becomes the secondary or accent.
  • The lightest color becomes a surface or background.
  • The darkest becomes your on-surface text color.

If two generated colors compete for the same role, that is a signal the palette is unbalanced — regenerate with one of them locked rather than forcing the mapping.

Every color needs an "on" color

The part beginners miss: for each background color, Flutter wants to know the color of text that sits on top of it. This is a contrast decision, not an aesthetic one. Run each pairing through the contrast checker and pick white or a dark ink based on which one passes. A primary that fails against both is a primary you cannot put text on — useful as a decorative fill, unusable as a button.

Define your palette as named constants once, then reference those names everywhere. Hard-coding the same hex in twenty widgets guarantees an inconsistent app the first time the palette changes.

Structure the color constants

Keep the raw palette in one file as plain color constants, mirroring the hex codes from the generator exactly:

// palette.dart
const brandPlum   = Color(0xFF8B2D55);
const brandBlush  = Color(0xFFFFA8C2);
const brandInk    = Color(0xFF28151F);
const brandCream  = Color(0xFFFFF9FB);
const brandDeep   = Color(0xFF6E2143);

Note the 0xFF prefix Flutter uses for full opacity, followed by the same six hex digits the generator gave you. Copy the digits verbatim — transcription errors in this file are unusually expensive, because everything downstream inherits them silently.

Build the scheme, then the theme

Assemble those constants into a color scheme, assigning each to its role and pairing it with the correct on-color, then feed the scheme into your app's theme. Because you validated contrast up front, the resulting theme is accessible by construction rather than by luck — and any widget that opts into theme colors inherits that guarantee for free.

Leave room for states and elevation

Buttons and cards need more than one shade of primary: pressed, hovered, disabled and elevated surfaces all want their own value. Derive them from the same hue rather than inventing new colors — a slightly darker primary for pressed, a desaturated version for disabled, and a lighter surface tint for raised cards. Generating a short monochrome ramp from each role color gives you all of these in one pass.

Light and dark from one palette

A well-built palette can drive both themes by swapping which colors serve as surface and on-surface, keeping the same hues throughout. Our post on designing a dark mode palette covers how to derive the dark variant properly instead of inverting everything and hoping.

Test the theme, not the screens

The point of theming from a single palette is that you can verify the colors independently of the app. Build one throwaway screen containing every component the theme touches — filled and outlined buttons in all states, cards at each elevation, text at every scale, form fields including errors, chips, dividers and a snackbar — and review it in both light and dark. Problems that would otherwise surface weeks later, on one obscure screen, appear immediately here: a disabled label that vanishes, an error color that clashes with the primary, a card that no longer separates from its background. Keep the screen in the project and open it whenever the palette changes.

Frequently asked questions

Should I use a seed color instead of a full palette?

A seed color is a fast way to get a complete, accessible scheme, and it is an excellent default. Supply a hand-tuned palette when brand colors are fixed and must be reproduced exactly.

Where should the palette file live?

In its own file, imported by the theme and by nothing else. Widgets should read colors from the theme, not from the palette directly.

Does this apply to other frameworks?

Yes. The role-plus-on-color pattern is the same in SwiftUI, Jetpack Compose and the web; only the syntax changes.

The same discipline in a browser context is covered in CSS custom properties for palettes.

Try it in the generator

Open the free palette generator and put this into practice in seconds.

Open the generator