Skip to content

Build a Web Palette From a Photo That Passes Contrast

7 min read Updated June 8, 2026

Pull dominant colors from a photo, then do the step everyone skips and turn those swatches into accessible web roles that actually pass contrast.


You point a color extractor at a photo of a forest or a plate of food, and it hands back eight swatches. They look lovely together. Then you drop the prettiest one onto a heading, set body text in another, and the page reads like a watercolor left in the rain. The swatches were never wrong. They were just never meant to be text.

Extractor tools and “50 gorgeous palettes” listicles are everywhere. Almost none of them close the loop to colors you can ship on a real website without failing accessibility. This walks the whole workflow: pull the dominant colors, then turn those raw swatches into roles that hold up.

TL;DR: Extract the colors, but don’t use them as-is. Sort them into roles (background, surface, text, accent), then darken or lighten whichever pairs fall short of WCAG contrast. Vivid colors go to headings and accents, not body text.

What you actually get from a photo

A palette extractor scans an image and reports the colors that take up the most area, usually clustered into five to ten swatches. A photo of sage leaves on a cream plate might return a soft sage green, a warm cream, a muddy olive, a near-white highlight, and a shadow brown.

Those are the colors of the scene. They are weighted toward whatever fills the frame, which tends to be mid-tones and gentle pastels. That weighting is exactly why the result feels calm and cohesive, and also why so few of the swatches work as text. Photos rarely contain a true dark or a crisp paper white in any quantity, so the extractor has nothing near-black to hand you for body copy.

So treat extraction as step one of two. You have raw material. Now you make it usable.

Contrast ratio, briefly

Contrast ratio compares the relative luminance of two colors: how much light each one reflects, weighted for how the eye actually perceives red, green, and blue. The result is a number from 1:1 (identical) up to 21:1 (pure black on pure white).

WCAG, the accessibility guideline most teams target, sets two everyday thresholds for the AA level:

  • 4.5:1 for normal body text against its background
  • 3:1 for large text (roughly 24px regular or 18.66px bold and up) and for UI parts like icons, form field borders, and focus rings

The key point for palette work: contrast depends on luminance distance, not on whether two colors “look different.” A bold magenta and a deep teal can clash hard to the eye and still fail contrast, because they sit at similar lightness. Two colors need real separation in luminance to pass.

Sort swatches into roles before you judge them

A website needs a small set of jobs filled, not a bag of pretty colors. Map your extracted swatches onto four roles:

  • Background: the page behind everything, usually your lightest or darkest swatch
  • Surface: cards, panels, and inputs that sit on the background, a half-step off it
  • Text: the color of body copy, which must hit 4.5:1 against both background and surface
  • Accent: links, buttons, highlights, where a vivid extracted color earns its place

Most extracted swatches are perfect for background, surface, and accent. The text role is where photos let you down, because the dark you need for legible body copy is usually absent from the image. You will often invent it by taking one of the photo’s hues and pushing it much darker.

A worked example

Say the sage-on-cream photo gives you these:

SwatchHexRole candidate
Cream#F4EFE3Background
Mid sage#9CAE8BAccent or text?
Warm white#FBF8F1Surface
Olive#6E7A57Accent

The temptation is to set body text in mid sage on cream, because the pair looks soft and on-brand. Check it:

  • Mid sage #9CAE8B on cream #F4EFE3 gives about 1.7:1

That fails. It fails badly. It misses normal text (4.5:1), large text (3:1), and even the UI minimum (3:1). Two mid-tones, too close in luminance. As a fill color or a decorative block it is fine. As text it is unreadable for many people and invisible for some.

Now fix it without throwing away the hue. Keep the green, drop the lightness until the pair clears the bar:

  • Darkened sage #3C4A2E on cream #F4EFE3 gives about 8.4:1

That passes AA for body text with room to spare, and it still reads as the same sage family. You kept the brand feel and made it legible. The original mid sage isn’t wasted either. Promote it to a large heading or a card border, where the 3:1 bar is reachable:

  • Mid sage #9CAE8B on cream needs 3:1 for large text and still misses at 1.7:1, so it stays decorative
  • Olive #6E7A57 on cream gives about 3.4:1, which clears large text and UI but not body copy

The pattern is consistent. The vivid and mid-tone colors handle headings, buttons, borders, and fills. One darkened or near-black color carries the reading. Always test the real pair you intend to ship, foreground against its actual background, not against an assumed white.

A repeatable routine

  1. Extract the palette from your image and write down the hex values.
  2. Pick the lightest swatch for background, a near-twin for surface.
  3. For text, take a hue you like and darken it until it hits 4.5:1 on both background and surface. Test the pair, adjust, retest.
  4. Assign the leftover vivid swatches to accents and large headings, checking each accent pair against 3:1.
  5. Spot-check the combinations you will really render: link on background, button text on button fill, placeholder on input.

Two habits save the most trouble. First, never let a mid-tone do body text; if a swatch sits in the middle of the lightness range, it belongs to fills and accents. Second, check the pair, not the swatch. A color is neither accessible nor inaccessible on its own; only a foreground-background combination has a ratio.

Free in-browser palette and contrast tools are coming to color.hivly.net, so you can extract from a photo, nudge a swatch lighter or darker, and watch the ratio update against a real background, all in the page with nothing uploaded. Until then, the routine above plus any contrast checker gets you a palette you can actually build with.

The colors in a photo are a mood. A web palette is a set of jobs. The work is moving from one to the other without losing the mood or the legibility, and most of that work is just darkening one swatch far enough.

Try the color toolsBuild palettes, check contrast, blend and convert, and generate gradients.

Frequently asked questions

What contrast ratio do I need for body text?
WCAG AA wants 4.5:1 for normal body text against its background. Large text (about 24px regular or 18.66px bold and up) and UI components like icons and form borders only need 3:1.
Why does my extracted palette look great but fail contrast?
Extractors pull the colors that dominate the image, which are usually mid-tones and pastels chosen for mood, not legibility. Mid-tones rarely have enough luminance distance from white or near-black to pass as text.
Can I use a vivid extracted color for text if I love it?
Reserve vivid colors for large headings, buttons, and accents where the 3:1 bar is easier to clear. For body text, darken or lighten that hue until the pair reaches 4.5:1, or pair it with a neutral instead.
How many colors should a web palette have?
Four roles cover most sites. You want a page background, a surface for cards, a text color, and one accent. You can extract a dozen swatches from a photo, but you only promote a handful into real roles.

Keep reading

Building something bigger?

Hivly is made by CodingEagles, a software studio that ships production web apps. If you have a real project, get in touch.

See what CodingEagles does →