Drop a JPG or PNG and get the dominant colors as ready-to-paste :root variables. Flat white and near-black backgrounds are skipped automatically so the real colours in your image shine through. Everything runs locally in your browser.
Drag & drop your image here
or browse files · JPG / PNG · stays on your device
Most designers treat color as a matter of taste. Professionals treat it as a system. A color system is the small set of rules and tokens that decide which color goes where, why it goes there, and what happens when the product grows. Without one, a design inevitably drifts: five slightly different blues, three "greens" that all mean success, and a dark mode that looks like a different website. This tutorial walks through the three layers of a professional color system, shows how to build a palette from an image, and converts that palette into maintainable CSS variables.
A professional system is built in layers. Each layer has a different job, and mixing them up is the most common source of chaos.
The primary color is the brand's signature hue. It appears on the most important interactive element on the page — usually the primary button — and it carries brand recognition across screens. Because it is used so heavily, it must be chosen for function, not just for looks: it needs to survive on white, on gray, and in dark mode, and it needs to pass contrast checks when used with white text. A practical rule is to commit to exactly one primary hue and let every variation (hover, active, disabled, subtle background) be a generated step of that same hue rather than a new color.
Secondary colors support the primary without competing with it. They are used for accents, illustrations, charts, badges, or the occasional call-to-action that must be visually separated from the main flow. A healthy ratio is roughly 60/30/10: the neutral background dominates, the primary color occupies the second-largest share, and secondary accents make up the remainder. Keep the secondary set small — one or two hues is usually enough — and make sure they are clearly distinguishable from the primary at a glance. If a user cannot tell a secondary button from a primary one, the system has failed.
Semantic colors describe meaning, not brand. Success, warning, error, and information are the classic four. Their value is that they stay stable even when the brand palette is redesigned: a red error message should look like an error in every theme. Semantic colors are almost never used as raw values in a component; they are referenced through a semantic name such as --color-feedback-danger. This separation is what allows you to swap a brand refresh in one file without rewriting a single component.
Brands rarely arrive with a ready-made palette. More often you get a photograph, a hero illustration, or a client's product shot, and the colors must be pulled out of it. Done well, this produces a palette with an authentic emotional tone that no color-wheel shortcut can match.
Pick an image that actually represents the brand's mood, not one that merely looks pretty. It should contain a dominant subject with clean, readable color regions — a product photo, a landscape, an interior. Images with heavy grain, heavy gradients, or strong filters are poor sources because the sampled colors will not sit well on a flat UI surface.
Use an eyedropper or a palette-extraction tool to sample five to seven points across the image: the dominant area, one or two mid-tones, a dark accent, and a highlight. Avoid sampling pixels at the boundary between two colors, since those will be muddy. Then push each sample toward a usable UI value: increase saturation slightly (photographic colors tend to look gray once placed on a flat background) and normalise lightness so the set has a consistent feel.
A single sampled color is not a system. Convert each chosen hue into a scale of steps — typically 50, 100, 200, 300, 400, 500, 600, 700, 800, and 900 — where 50 is the lightest tint and 900 the darkest shade. Your sampled color usually becomes the 500 or 600 step. Verify that text on the 50 and 100 steps passes contrast requirements, and that the 600 and 700 steps can hold white text. Finally, test every pair against WCAG AA (4.5:1 for body text) before you commit.
The last step turns the palette into code. CSS custom properties are the right tool: they are inherited, themeable, and readable by JavaScript.
Use a two-tier naming scheme. The first tier describes the raw color; the second describes its purpose. Never name a variable after what it looks like (--blue-button), because the moment the brand turns purple the name becomes a lie.
:root {
/* Tier 1 — raw palette */
--palette-primary-500: #2f6bff;
--palette-primary-600: #2456d6;
--palette-accent-500: #ff8a3d;
--palette-success-500: #1f9d55;
--palette-danger-500: #d92d20;
--palette-warning-500: #b7791f;
--palette-neutral-0: #ffffff;
--palette-neutral-900: #101828;
/* Tier 2 — semantic aliases */
--color-bg-surface: var(--palette-neutral-0);
--color-text-primary: var(--palette-neutral-900);
--color-action-primary: var(--palette-primary-500);
--color-action-hover: var(--palette-primary-600);
--color-feedback-danger: var(--palette-danger-500);
--color-feedback-success: var(--palette-success-500);
}
Components should reference only the semantic tier. A button does not know that the primary color is blue; it only knows it uses --color-action-primary. This indirection is what makes theming trivial: redefining the tier-1 values inside a [data-theme="dark"] selector instantly re-skins the entire product without touching a component rule.
.button--primary {
background: var(--color-action-primary);
color: var(--palette-neutral-0);
}
.button--primary:hover {
background: var(--color-action-hover);
}
Before shipping, confirm four things. First, the system has exactly one primary hue with a full scale. Second, every semantic state has a dedicated token. Third, all text/background pairs pass contrast checks in both light and dark themes. Fourth, no component contains a raw hex value — every color is reached through a variable. When those four hold, color stops being a matter of taste and becomes a piece of engineering you can hand to the next developer with confidence.