What this guide covers
- Choose a source image with a clear color story, not a busy scene.
- Remove extraction accidents before assigning any color a job.
- Map colors to primary, surface, text and status roles.
- Measure the text pairs that people actually read.
- Document the source and the decisions so the palette can be defended.
Live tool
Try the workflow
Everything runs in your browser. The image is never uploaded, and no account is required.
Your palette
sample-palette
Developer exports
:root {
--color-primary: #3B6C64;
--color-primary-foreground: #FFFFFF;
--color-secondary: #C8904A;
--color-accent: #915A46;
--color-neutral: #20261F;
--color-background: #F8F4EA;
--color-surface: #FCFAF6;
--color-text: #111827;
--color-muted-text: #111827;
--color-border: #BCBAB1;
--color-success: #16A34A;
--color-warning: #C8904A;
--color-danger: #DC2626;
}
[data-theme="dark"] {
--color-background: #20261F;
--color-surface: #151A1C;
--color-text: #FFFFFF;
--color-muted-text: #FFFFFF;
--color-border: #20261F;
--color-primary: #95C5BE;
--color-primary-foreground: #111827;
}export const photocolors = {
colors: {
primary: {
DEFAULT: "#3B6C64",
foreground: "#FFFFFF"
},
secondary: "#C8904A",
accent: "#915A46",
neutral: "#20261F",
background: "var(--color-background)",
surface: "var(--color-surface)",
text: "var(--color-text)",
muted: "var(--color-muted-text)",
border: "var(--color-border)",
success: "#16A34A",
warning: "#C8904A",
danger: "#DC2626"
}
};:root {
--background: #F8F4EA;
--foreground: #111827;
--card: #FCFAF6;
--card-foreground: #111827;
--primary: #3B6C64;
--primary-foreground: #FFFFFF;
--secondary: #C8904A;
--secondary-foreground: #111827;
--accent: #915A46;
--accent-foreground: #FFFFFF;
--muted: #FAF7F0;
--muted-foreground: #111827;
--border: #BCBAB1;
--destructive: #DC2626;
--destructive-foreground: #FFFFFF;
--ring: #3B6C64;
}
.dark {
--background: #20261F;
--foreground: #FFFFFF;
--card: #151A1C;
--card-foreground: #FFFFFF;
--primary: #95C5BE;
--primary-foreground: #111827;
--secondary: #C8904A;
--secondary-foreground: #111827;
--accent: #CAA091;
--accent-foreground: #111827;
--muted: #1B201E;
--muted-foreground: #FFFFFF;
--border: #20261F;
--destructive: #DC2626;
--destructive-foreground: #FFFFFF;
--ring: #95C5BE;
}{
"color": {
"primary": {
"value": "#3B6C64",
"type": "color"
},
"secondary": {
"value": "#C8904A",
"type": "color"
},
"accent": {
"value": "#915A46",
"type": "color"
},
"neutral": {
"value": "#20261F",
"type": "color"
},
"background": {
"value": "#F8F4EA",
"type": "color"
},
"surface": {
"value": "#FCFAF6",
"type": "color"
},
"text": {
"value": "#111827",
"type": "color"
},
"mutedText": {
"value": "#111827",
"type": "color"
},
"success": {
"value": "#16A34A",
"type": "color"
},
"warning": {
"value": "#C8904A",
"type": "color"
},
"danger": {
"value": "#DC2626",
"type": "color"
}
},
"dark": {
"background": {
"value": "#20261F",
"type": "color"
},
"surface": {
"value": "#151A1C",
"type": "color"
},
"text": {
"value": "#FFFFFF",
"type": "color"
},
"mutedText": {
"value": "#FFFFFF",
"type": "color"
}
}
}{
"primary": "oklch(49.41% 0.0551 182.3)",
"secondary": "oklch(69.42% 0.1102 70.3)",
"accent": "oklch(52.41% 0.0792 40.7)",
"neutral": "oklch(26.01% 0.0155 141.1)",
"background": "oklch(96.75% 0.0139 88.7)",
"foreground": "oklch(21.01% 0.0318 264.7)",
"surface": "oklch(98.55% 0.0057 84.6)",
"success": "oklch(62.71% 0.1699 149.2)",
"warning": "oklch(69.42% 0.1102 70.3)",
"danger": "oklch(57.71% 0.2152 27.3)"
}The palette is the easy part
A color extractor can return five beautiful swatches in under a second. That is the easy part. The hard part is deciding which of those colors should become a button, which should become a background, and which should never leave the photograph.
Most palette tools stop at the swatches because the next step requires context. A color that looks perfect in a strip may be unreadable as body text. A color that feels warm in a photo may look muddy on a dark card. The palette has to be tested in the places it will actually live.
This guide walks through the workflow we use at PhotoColors: choose a source, extract, remove accidents, assign roles, check contrast, and document the result. It is deliberately boring. Boring is what makes a palette survive a real product.
Step 1: choose the right source image
The source image is the evidence for every color decision. If the image is busy, the extractor has to guess which colors matter. A street scene with ten equally important colors will produce a palette that represents the scene but not a brand.
Look for an image with one dominant subject and a limited background. A product on a neutral surface, a landscape with a clear sky and ground, a plate of food on a simple table, or a textile close-up all work well. The color story should be obvious before you run the extraction.
Crop to the part of the image that carries the mood. A food photo may be mostly white tablecloth with a small plate of color. Cropping closer to the plate gives the sampler more useful pixels and produces a palette that feels like the dish rather than the restaurant.
Use the original file when accuracy matters. A screenshot, a compressed social upload or a heavily filtered image may look good but contain colors that were never in the brand. If the filter is part of the brand, use it intentionally and note that decision.
- One dominant subject, limited background.
- Crop to the area that carries the mood.
- Use the original file, not a screenshot.
- Avoid compression artifacts around edges.
Step 2: extract colors, then remove the accidents
The extractor returns the colors that are statistically present. That is not the same as the colors that are meaningful. A white background, a black shadow, a bit of skin, a reflection and an anti-aliased edge can all appear in the result.
Review the palette one swatch at a time. Ask whether the color appears in a solid area of the image or only at an edge. Ask whether it carries the subject or merely surrounds it. Remove the colors that are accidents.
A useful palette usually has three to six meaningful colors. If the extractor returns eight, that does not mean you need eight. The goal is a small set of colors with distinct jobs, not a complete census of the image.
A palette is a selection, not a transcript. Leaving colors out is part of the work.
Step 3: assign jobs, not names
Naming a color 'forest' or 'terracotta' feels good, but it does not help a developer decide where to use it. Assign jobs instead: primary action, page background, card surface, body text, muted text, border, success, warning, danger.
The same color can have different jobs in different contexts, but the system should make the default clear. A primary action color should be the one people click. A surface color should recede. A text color should be readable at body size.
Start with the primary. Then choose the background and surface, because most of the interface is neutral. Then choose text and muted text. Status colors come last, and they should be distinguishable from the primary action.
| Role | Question it answers | What to check |
|---|---|---|
| Primary | What do people click? | Readable foreground |
| Background | What recedes? | Body text contrast |
| Surface | What groups content? | Separation from background |
| Text | What do people read? | 4.5:1 minimum |
| Muted text | What supports the text? | Still readable at small sizes |
| Status | What state is this? | Distinct from primary, plus icon or label |
Step 4: test the pairs people actually read
A swatch strip does not have contrast problems. Text does. Test the pairs that appear in the product: body text on the background, muted text on the background, button text on the primary color, and text on the card surface.
WCAG AA requires 4.5:1 for normal text and 3:1 for large text. AAA requires 7:1 and 4.5:1. The ratio is objective. A pair either passes or it does not, regardless of how good it looks in a moodboard.
If a pair fails, try black or white text first. If neither works, adjust the background or choose a darker or lighter variant of the brand color for the text. Keep the original color for decoration if you love it.
Do not stop at the primary button. Check the muted label, the placeholder, the disabled state and the border. These are the places where a palette quietly falls apart.
- Body text on background.
- Muted text on background and surface.
- Button text on primary.
- Text on card surface.
- Placeholder and helper text.
- Focus ring against background and surface.
Step 5: document the decision
A palette without provenance becomes a mystery. Six months later, nobody remembers why the primary is a particular green or why the dark surface is warmer than the light one. Document the source image, the extracted colors, the roles and the contrast results.
The documentation does not need to be a long report. A short table with the source, the role, the HEX value and the contrast result is enough. If the palette changes, the table can be updated and the reason recorded.
This is also where a deterministic tool helps. If the extraction can be re-run on the same source, the decision is reproducible. If the palette was invented by a model, the provenance is much weaker.
A worked example
Imagine a coastal photograph with a deep green headland, a warm sand path, a dark rock, a pale sky and a rust-colored roof. The extractor returns those five colors plus a blue from the water. The blue is beautiful, but it is not the story of the image.
The deep green becomes the primary action. The warm sand becomes the page background. The pale sky becomes the card surface. The dark rock becomes the body text. The rust roof becomes the accent. The blue is kept as a photography color, not a UI role.
The primary green is tested with white text and fails 4.5:1, so a darker green step becomes the button color while the original green remains the brand accent. The background and text pair passes. The muted text is adjusted until it passes on both the background and the card.
That is the whole workflow: choose, extract, remove, assign, test, document. The palette now has a reason for every color, and the product has a system rather than a moodboard.
What to do next
Run the workflow on one real image and one real screen. Do not start with ten images. The first pass will reveal whether the source is good enough and whether the roles are clear.
Then test the palette in light and dark mode. A color that feels right on cream may need adjustment on a dark surface. That is not a failure; it is the next decision.
If you want a starting point, upload the image to PhotoColors and review the generated roles. The tool does the extraction and the first contrast pass; the judgment about which color leads is still yours.
