Color in the Interface: The Basics for Your First Color Palette
Neutrals, primary color, accent and status colors: how to build your first interface color palette without color theory, so it is easy to understand and has enough contrast.
A small farm shop wants to start selling vegetable boxes online. Two people build the shop: one designs, one develops. The logo is orange, so at first everything turns orange. The buttons, the headings, the links, the prices, the notice about delivery dates. Then the first error message shows up in the checkout form. It is red and sits right next to an orange button. On a phone in direct sunlight, the two look almost identical. And the white text on the orange “Add to cart” button is harder to read than anyone would like.
This is not a matter of taste. What is missing is a color palette in which every color has a clear job. That is what this article is about: how to build a first palette for an interface without color theory or color wheels, one that looks good, is easy to understand and has enough contrast. The farm shop example walks you through every step. All color values and contrast ratios in the text are examples you can check yourself.
What a color palette in an interface actually is
A color palette is a defined set of colors used to design a product. In an interface, it is less about mood and more about function. Every color plays a role: it shows where you can click, what is currently selected, what went wrong or which text matters.
That is why large design systems think of colors in terms of roles rather than hues. Google’s Material Design, for example, distinguishes a primary color, a secondary and a tertiary color, an error color and surface colors, among others. Each of these roles has a matching “on” color for text and icons placed on top of it. The Flutter documentation describes the primary color as the color displayed most frequently across an app’s screens and components, and the “on” color as one that is clearly legible on the primary color.
You don’t need that many roles to start. For a first palette, four groups are enough:
- Neutrals: white, grays and a very dark tone for backgrounds, surfaces, borders and text.
- Primary color: the color for the most important action, for links and for active states.
- Accent color: optional, for rare highlights that should not compete with the primary color.
- Status colors: error, success, warning and information.
The order of this list is also the best order for building your palette. That may sound surprising, since most teams start with the brand color. The reason becomes clear in the first step.
Before you start: what is a contrast ratio?
One term runs through the whole article, so let’s cover it first. The contrast ratio describes how much two colors differ in brightness. It is expressed as a ratio ranging from 1:1 (two identical colors) to 21:1 (black on white). The higher the number, the easier it is to read text on a background.
Which values are enough is defined by the WCAG, the international guidelines for accessible web content. For the common level AA, there are three thresholds worth remembering:
- 4.5:1 for normal text.
- 3:1 for large text. According to WCAG, large means at least 18 points (about 24 pixels) or at least 14 points bold (about 18.5 pixels).
- 3:1 for user interface components and meaningful graphics, such as the border of an input field, an icon without a label or the indicator of the active tab.
Two details matter in practice. First, there is no rounding: a ratio of 4.49:1 does not meet the 4.5:1 requirement. Second, disabled elements are exempt, for example a button that only becomes active once a form is complete. Logos and purely decorative elements are exempt as well.
You don’t have to calculate these values by hand. Tools like the WebAIM Contrast Checker show you the ratio as soon as you enter two colors and tell you which level it meets.
Step 1: Start with the neutrals
Look at any screen in an app you use every day and estimate how much of it is actually colorful. Usually it’s just a small percentage of the area: a button here, an icon there. The rest is white, light grays for surfaces, mid grays for borders and dark tones for text. These neutrals carry the interface. If they are muddled, even the nicest brand color won’t help.
To start, you need five to seven shades. The farm shop defines these:
- Background: white (#FFFFFF) for the page.
- Surface: a very light, slightly warm gray (#FAFAF9) for cards and separate sections.
- Divider: light gray (#E7E5E4) for lines between list items.
- Border for controls: mid gray (#78716C) for input fields and checkboxes.
- Secondary text: darker gray (#57534E) for hints, dates and labels.
- Primary text: almost black (#1C1917) for headings and body text.
Why two different grays for lines? Because they do different jobs. A divider between two products is decoration. It can be subtle. The border of an input field, on the other hand, shows where you can type. It needs to meet the 3:1 threshold. The light gray #E7E5E4 only reaches about 1.3:1 on white. As the border of an input field, it would be too weak. The mid gray #78716C reaches about 4.8:1 on white and is safely above the line.
Many projects make the same mistake with secondary text: it gets so light that it looks elegant but is hard to read. A gray like #A8A29E looks pleasantly subtle on white but only reaches about 2.5:1. That is not enough for text. The chosen #57534E comes in at about 7.6:1 and still looks noticeably calmer than the primary text.
One more tip: pure grays can look cold next to a strong brand color. That is why many teams give their neutrals a hint of the brand color, a slightly warm gray for a warm brand or a slightly bluish one for a cool brand. The farm shop went with warm grays because they suit the orange better.
Step 2: Define the primary color
The primary color is how your interface says: something happens here. It marks the most important action on a screen, links and active states such as the selected menu item. It is usually derived from the brand color, and that is exactly where the farm shop runs into trouble.
The brand color is not automatically the interface color
The orange from the logo is #E8711A. It works well in the logo. As the background of a button with white text, however, it only reaches about 3.1:1. That is not enough for button text at normal size, which needs 4.5:1. The team has three options:
- A darker version of the brand color: a deeper orange like #C2410C reaches about 5.2:1 with white text. It looks a little earthier but is clearly part of the same color family.
- Dark text on the brand color: almost black text (#1C1917) on the logo orange reaches about 5.7:1. That works technically but changes the character of the buttons considerably.
- Large text only: white text on the logo orange meets 3:1 for large text. For small buttons, links and forms, the problem remains.
The farm shop chooses the first option. The logo stays as it is. In the interface, #C2410C becomes the primary color.
Before: “Add to cart” button with white text on #E8711A, contrast about 3.1:1. In sunlight or at low screen brightness, the text blurs into the background.
After: The same button with white text on #C2410C, contrast about 5.2:1. The brand is still recognizable, and the text stays readable in poor conditions.
One color, several shades
A single primary color is rarely enough in practice. You usually need a light version for backgrounds, such as a selected list item, and a slightly darker one for a button’s hover state, meaning how it looks when the mouse pointer is over it.
Large color systems work with many shades. The default Tailwind CSS palette has eleven steps per color, from 50 (very light) to 950 (very dark). Radix Colors uses twelve steps and describes a use case for each: the lightest for backgrounds, middle steps for borders, the most saturated for solid fills like buttons and the darkest for text. These systems make good templates, but you don’t need to start with twelve steps.
The farm shop gets by with four shades of its orange: a very light orange (#FFF7ED) for notice areas and selected elements, the logo orange (#E8711A) for illustrations and decorative accents, the primary color #C2410C for buttons and links, and a dark orange (#9A3412) for the hover state. White on the dark orange reaches about 7.3:1, so the hover state stays readable too.
What the primary color is not for
The most common temptation: the primary color ends up everywhere because it looks so nicely on brand. Headings turn orange, icons turn orange, dividers turn orange. The result is that nobody can tell what is clickable and what isn’t.
A simple rule helps: the primary color belongs to things you can operate and things that are currently active. Headings stay in the dark text color. If three elements on a screen glow in the primary color, it should be because there are three important actions, not because someone wanted to add some color.
Step 3: An accent color, if you need one
An accent color is a second, clearly different color used deliberately for rare highlights. Typical examples are a “New” badge, a marker for a special offer or a section that should stand out. In Material Design, the tertiary color takes on this job and, according to the documentation, serves as a contrasting accent.
For a first palette, the accent color is optional, and it is often wiser to start without one. Every additional color is another decision the team has to make again and again: is this a case for the accent color or the primary color? If the answer isn’t obvious, you end up with exactly the mess the palette is meant to prevent.
A good test: can you say in one sentence what the accent color is for? For the farm shop, the sentence is: “The accent color marks seasonal products only.” The team picks a muted green (#15803D) that is clearly distinct from the orange. An accent color should never be used for the most important action on a screen. Otherwise it competes with the primary color for attention.
Wait, green? This is where the next step comes in, because green is also the usual color for success messages.
Step 4: Status colors that can’t be confused
Status colors show the state of a system: something worked, something went wrong, something needs attention. The common mapping is red for errors, green for success, yellow or orange for warnings and blue for neutral information. Don’t break this convention without good reason, because people bring it with them from countless other products.
Resolving conflicts with your own palette
The farm shop has two conflicts right away. The primary color is a strong orange that comes fairly close to an error red. And the warning color would traditionally be orange too. If warnings appear in the same color as the “Order now” button, things get confusing.
The solution in the example:
- Error: a deep red (#B91C1C) that is noticeably darker and cooler than the orange. With white text, it reaches about 6.5:1.
- Warning: yellow instead of orange. For fills, a strong yellow (#EAB308) with dark text (about 9.1:1). White text on this yellow would only reach about 1.9:1, so it’s ruled out. For warning text on white, the team uses a dark ochre (#A16207) that reaches about 4.9:1.
- Success: the same green as the accent color (#15803D), about 5.0:1 with white text. Because success messages always appear with an icon and text, and seasonal products always carry the word “Seasonal”, the double role is acceptable. If you want to avoid it, choose a different accent color.
- Information: a clear blue (#1D4ED8), about 6.7:1 with white text.
Pay special attention to greens. A typical “success green” like #16A34A looks fresh but only reaches about 3.3:1 with white text. That is not enough for text. One step darker, and the problem is solved.
For notice boxes, every status color also needs a very light version as a background. The farm shop’s error message, for example, uses dark red text (#B91C1C) on a pale pink (#FEF2F2) and reaches about 5.9:1.
Color must never be the only signal
One rule matters more for status colors than any color value: the information has to come across without color. WCAG sets this out in success criterion 1.4.1. Color must not be the only visual means of conveying information, indicating an action or distinguishing an element.
The reason is very practical. According to the organization Colour Blind Awareness, about 1 in 12 men and 1 in 200 women have a color vision deficiency. The most common form is red-green color blindness, which makes reds and greens hard to tell apart. A red error message and a green success message that differ only in color are barely distinguishable for these people. Add situations that affect everyone: bright sunlight, a poor monitor, a black-and-white printout.
In practice, this means: an error message gets an icon and text that describes the error. A required field isn’t just outlined in red but gets a message below it. A link in body text is underlined. If links are meant to stand out by color alone, the WCAG techniques call for a contrast of at least 3:1 between the link and the surrounding text. The orange #C2410C reaches about 3.4:1 against the primary text #1C1917. The farm shop still underlines its links because that is the quickest cue for everyone to understand.
You’ll find more on the different forms of color vision deficiency and further guidelines in the article Color Blindness in UX Design.
Step 5: Test the palette before it goes into the product
A palette on paper is quick to make. Whether it works only becomes clear when you go through every combination that actually appears in the product. Three simple checks help here.
The contrast table
List all text colors in rows and all background colors in columns. In each cell, enter the contrast ratio. For the farm shop, that’s six text colors and five backgrounds, so 30 cells. Many of them never occur in the product, such as red text on an orange background. Cross those out. For all the others, mark whether they reach 4.5:1 for text or 3:1 for large text and controls.
This table is more than a one-time check. It becomes a rule: anything not marked as passing doesn’t go into the product. To check directly in Figma, plugins like Stark work well. Stark checks contrast and can also simulate color vision deficiencies.
The grayscale test
Take a screenshot of a typical screen and convert it to grayscale. Can you still tell which button matters most? Can you see where an error occurred? If the hierarchy falls apart in grayscale, the design relies too heavily on color.
The simulation
Chrome’s developer tools include a simulation of various vision impairments. In the “Rendering” panel, you’ll find the option “Emulate vision deficiencies”. There you can choose protanopia (no red), deuteranopia (no green), tritanopia (no blue) and achromatopsia (no color), among others. Running through the checkout once with each setting takes only a few minutes and quickly shows where status colors are too similar.
The finished palette of the example team
After these five steps, the farm shop has a palette that fits on a single page:
- Neutrals: #FFFFFF (background), #FAFAF9 (surface), #E7E5E4 (divider), #78716C (control borders), #57534E (secondary text), #1C1917 (primary text).
- Primary color: #FFF7ED (light), #E8711A (brand, decorative only), #C2410C (buttons and links), #9A3412 (hover).
- Accent: #15803D, for seasonal products only.
- Status: error #B91C1C, warning #EAB308 with dark text and #A16207 for text, success #15803D, information #1D4ED8, each with a very light version for notice boxes.
That’s around 20 color values. It sounds like a lot, but it’s manageable because every value has exactly one job. Before, the shop used more than 30 different color values, and nobody could say what most of them were for.
Common mistakes in a first palette
A few mistakes show up in almost every first color concept. Knowing them saves you a round of revisions.
Too many colors at once
Five brand colors, three accent colors and a separate color for every category: that looks lively on a mood board but restless in an interface. Start with one primary color and the status colors. Add more colors when there is a concrete reason.
Adopting the brand color without checking it
Brand colors are often chosen for logos, posters and social media, not for small text on buttons. Always test them with text at the sizes used in the interface. If they don’t pass, a darker interface version is no betrayal of the brand.
Gray text that is too light
Light gray text looks elegant in mockups, especially on large, bright monitors. On a phone outdoors, it becomes unreadable. Check every gray used for text against 4.5:1.
Status by color alone
A field outlined in red without an error message is invisible to many people. Always combine status colors with an icon and text.
Dark mode by inverting
If you plan a dark mode later, meaning a dark version of the interface, you can’t simply invert all colors. The primary color usually needs a lighter version on a dark background, and every combination has to be checked again. That is a project of its own, and a clean palette with clear roles is the best foundation for it.
From palette to system
Once the palette is in place, it pays to name colors by their role, for example “Text default”, “Surface”, “Action primary” or “Error”. If you change a color later, you change it in one place, and every screen follows. In design systems, these named values are called design tokens. How to set them up in Figma with variables, including dark mode, is covered in Design Tokens in Figma. Why a small design system pays off even for small teams is explained in Design Systems for Small Teams.
Conclusion
A good first color palette doesn’t come from color theory but from clear jobs. Neutrals carry the interface, the primary color shows what you can interact with, an optional accent color highlights rare things, and status colors report what happened. Every color is checked against its backgrounds, and no information depends on color alone.
In the end, the farm shop doesn’t have a spectacular color concept, but one that works. The “Add to cart” button is readable in sunlight, errors and actions can no longer be confused, and new screens can be designed without debating every color again. When you start your own palette, tackle the neutrals first. The rest will fall into place more easily than you think.
Checklist
- Define the neutrals first: background, surface, divider, border, secondary text, primary text.
- Borders of input fields and controls at least 3:1 against the background.
- Every text color at least 4.5:1, large text at least 3:1. No rounding.
- Test the brand color with white and dark text, and choose a darker interface version if needed.
- Use the primary color only for actions, links and active states.
- Add an accent color only if you can describe its purpose in one sentence.
- Define status colors for error, warning, success and information, and check that they can’t be confused with the primary color.
- Never show status by color alone: add an icon and text, underline links.
- Create a contrast table and use it as a rule.
- Run a grayscale test and simulate color vision deficiencies.
- Name colors by role so they can live on as design tokens.
Sources
- W3C: Web Content Accessibility Guidelines (WCAG) 2.2
- W3C: Understanding Success Criterion 1.4.3, Contrast (Minimum)
- W3C: Understanding Success Criterion 1.4.11, Non-text Contrast
- W3C: Understanding Success Criterion 1.4.1, Use of Color
- W3C: Technique G183, Using a contrast ratio of 3:1 with surrounding text
- Colour Blind Awareness: About Colour Blindness
- National Eye Institute: Types of Color Vision Deficiency
- Material Design 3: Color roles
- Flutter API: ColorScheme class
- Tailwind CSS: Colors
- Radix Colors: Understanding the scale
- Chrome for Developers: Emulate vision deficiencies
- WebAIM: Contrast Checker