How to Choose Brand Colors That Don't Suck (A Developer's Guide)

Color theory for developers: learn the rules designers use to pick brand palettes, then skip the work entirely with AI-generated color systems.

February 5, 20269 min readBy Yann Lephay

You've done it. I've done it. We've all done it.

You spin up a new project, install Tailwind, and start reaching for bg-blue-500 like it's muscle memory. Buttons? Blue. Links? Blue. Header? Also blue. Hover state? blue-600, obviously. By the time you ship, your app looks like a Facebook clone that got lost on the way to production.

Choosing brand colors is one of those tasks that seems trivial until you actually try to do it well. Designers spend years building intuition for this. But you don't need years. You need a framework, some ground rules, and about 20 minutes of focused effort.

Or 60 seconds, if you use a brand color palette generator — but we'll get to that. (And if you're looking for a standalone tool, check out our Tailwind color palette guide.)

The 60-30-10 rule

Interior designers figured this out decades ago, and it maps perfectly to UI design. The idea is simple: distribute your colors in a 60/30/10 ratio.

  • 60% — Primary (neutral base): Backgrounds, cards, large surfaces. This is usually white, near-white, or a dark gray in dark mode. It's the canvas everything else sits on.
  • 30% — Secondary: Supporting color for secondary buttons, borders, muted text, section backgrounds. This creates contrast against your primary without competing for attention.
  • 10% — Accent: Your brand color. CTAs, active states, links, key indicators. This is the blue-500 you keep reaching for — but it should be the exception, not the default.

The mistake most developers make is inverting this ratio. They splash their brand color everywhere because it "looks more branded." In reality, restraint is what makes a brand color powerful. When everything is blue, nothing is blue.

Here's what this looks like in practice:

Code
:root {
  /* 60% — Neutral base */
  --color-background: #ffffff;
  --color-surface: #f8fafc;
 
  /* 30% — Secondary */
  --color-border: #e2e8f0;
  --color-text-muted: #64748b;
  --color-secondary: #475569;
 
  /* 10% — Accent (your brand color) */
  --color-primary: #6d28d9;
  --color-primary-hover: #5b21b6;
  --color-primary-light: #ede9fe;
}

Look at any well-designed SaaS product — Linear, Vercel, Stripe — and you'll see this ratio in action. The brand color is used surgically, not plastered everywhere.

Color psychology: what colors actually signal

You don't need a degree in psychology to use this, but understanding what colors communicate will stop you from making weird choices. Like using bright red for a meditation app.

Blue — Trust, stability, professionalism. There's a reason every bank, enterprise tool, and social network defaults to blue. It's the "safe" pick. That's both its strength and its weakness: it won't offend anyone, but it also won't differentiate you.

Purple — Creativity, premium, innovation. Popular with dev tools and design products (Figma, Framer, Heroku). It signals that your product is a cut above.

Green — Growth, success, money. Fintech, health, sustainability. Also universally understood as "positive" in UI (success states, confirmations).

Red/Orange — Energy, urgency, passion. Works for food, entertainment, and marketplaces. Handle with care in SaaS — red usually means "error" in interfaces, so using it as a brand color requires intentional separation.

Black/Dark gray — Sophistication, luxury, minimalism. The developer's secret weapon. A monochrome palette with one accent color is nearly impossible to mess up.

Yellow — Optimism, warmth, caution. Tricky as a primary brand color because of readability issues on light backgrounds. Works better as an accent or highlight.

The real advice: pick based on what your product does and who it's for, not what you personally like. If you're building a security product, purple might confuse users who expect blue. If you're building a creative tool, blue might feel too corporate.

The accessibility trap

Here's where most developer-designed brands fall apart — not in the color choice itself, but in how the colors are applied.

WCAG 2.1 requires a 4.5:1 contrast ratio for normal text and 3:1 for large text (18px+ or 14px+ bold). These aren't suggestions. They're the baseline for your product to be usable by the ~15% of people with some form of color vision deficiency.

Common accessibility failures:

Light text on medium backgrounds. That text-gray-400 on bg-gray-50 looks subtle and elegant on your 5K monitor. On a cheap laptop in sunlight? Invisible.

Brand color as text color. Many brand colors that look great on buttons fail miserably as text. A vibrant #6d28d9 purple has a contrast ratio of 7.2:1 on white (passes), but #a78bfa light purple is only 3.1:1 (fails for body text).

Color as the only indicator. "Required fields are shown in red" is useless to someone with red-green color blindness. Always pair color with another signal: an icon, a label, a border.

Ignoring dark mode contrast. Colors that pass on white backgrounds often fail on dark backgrounds, and vice versa. You need to check contrast in both themes.

Test your palette with these tools before you commit:

  • WebAIM Contrast Checker
  • Chrome DevTools' built-in contrast overlay (inspect an element, click the color swatch)
  • The axe-core browser extension for automated audits

A contrast-safe palette isn't optional. It's the starting point.

Building a palette that works

So you have a single brand color you like. How do you turn that into a complete color system that covers backgrounds, text, borders, interactive states, success/error feedback, and dark mode?

Step 1: Generate a full shade scale

One hex code isn't a palette. You need a range from light (for backgrounds and highlights) to dark (for text and hover states). Most design systems use a 50-950 scale:

Code
:root {
  --brand-50: #faf5ff;
  --brand-100: #f3e8ff;
  --brand-200: #e9d5ff;
  --brand-300: #d8b4fe;
  --brand-400: #c084fc;
  --brand-500: #a855f7;  /* Your starting color */
  --brand-600: #9333ea;
  --brand-700: #7e22ce;
  --brand-800: #6b21a8;
  --brand-900: #581c87;
  --brand-950: #3b0764;
}

Step 2: Define semantic roles

Map those shades to specific purposes in your UI:

Code
:root {
  /* Surfaces */
  --color-bg: #ffffff;
  --color-bg-subtle: var(--brand-50);
  --color-surface: #ffffff;
  --color-surface-raised: #f8fafc;
 
  /* Text */
  --color-text: #0f172a;
  --color-text-secondary: #475569;
  --color-text-muted: #94a3b8;
  --color-text-on-primary: #ffffff;
 
  /* Interactive */
  --color-primary: var(--brand-600);
  --color-primary-hover: var(--brand-700);
  --color-primary-active: var(--brand-800);
  --color-primary-subtle: var(--brand-100);
 
  /* Borders */
  --color-border: #e2e8f0;
  --color-border-strong: #cbd5e1;
  --color-ring: var(--brand-500);
 
  /* Feedback */
  --color-success: #16a34a;
  --color-warning: #d97706;
  --color-error: #dc2626;
  --color-info: #2563eb;
}

Step 3: Add dark mode values

Don't just invert your colors. Dark mode needs intentional adjustments — lighter brand shades for text, muted backgrounds instead of pure black, and re-checked contrast ratios:

Code
.dark {
  --color-bg: #0c0a09;
  --color-bg-subtle: #1c1917;
  --color-surface: #1c1917;
  --color-surface-raised: #292524;
 
  --color-text: #fafaf9;
  --color-text-secondary: #a8a29e;
  --color-text-muted: #78716c;
 
  --color-primary: var(--brand-400);
  --color-primary-hover: var(--brand-300);
  --color-primary-subtle: var(--brand-950);
 
  --color-border: #292524;
  --color-border-strong: #44403c;
}

Notice the shift: in light mode, your primary interactive color is brand-600 (darker). In dark mode, it's brand-400 (lighter). This maintains visual weight and contrast in both contexts.

Translating to Tailwind and design tokens

Once you have your palette defined, you need it in a format your tools can use.

Tailwind config

Code
// tailwind.config.ts
import type { Config } from "tailwindcss";
 
const config: Config = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#faf5ff",
          100: "#f3e8ff",
          200: "#e9d5ff",
          300: "#d8b4fe",
          400: "#c084fc",
          500: "#a855f7",
          600: "#9333ea",
          700: "#7e22ce",
          800: "#6b21a8",
          900: "#581c87",
          950: "#3b0764",
        },
        background: "var(--color-bg)",
        foreground: "var(--color-text)",
        muted: {
          DEFAULT: "var(--color-text-muted)",
          foreground: "var(--color-text-secondary)",
        },
        primary: {
          DEFAULT: "var(--color-primary)",
          hover: "var(--color-primary-hover)",
          subtle: "var(--color-primary-subtle)",
          foreground: "var(--color-text-on-primary)",
        },
        border: "var(--color-border)",
        success: "var(--color-success)",
        warning: "var(--color-warning)",
        error: "var(--color-error)",
      },
    },
  },
};
 
export default config;

This gives you the best of both worlds: raw shade values when you need precision (brand-600) and semantic tokens that respect dark mode (primary).

If you want a deeper dive on the Tailwind-specific setup, check out how to create the perfect Tailwind color palette.

Design tokens (JSON)

For teams that need to share colors across platforms, design tokens are the standard format:

Code
{
  "color": {
    "primary": {
      "value": "#9333ea",
      "type": "color",
      "description": "Main brand color for CTAs and interactive elements"
    },
    "primary-hover": {
      "value": "#7e22ce",
      "type": "color"
    },
    "background": {
      "value": "#ffffff",
      "type": "color"
    },
    "text": {
      "value": "#0f172a",
      "type": "color"
    }
  }
}

If you're new to design tokens, our design tokens explainer covers how they work end-to-end.

Common palette mistakes to avoid

Before you finalize your colors, run through this checklist:

Too many colors. If your palette has more than 6-8 distinct hues, you're overcomplicating things. Most successful brands use 1-2 hues plus neutrals.

Competing accents. Two strong accent colors will fight each other for attention. Pick one as dominant, and make the second muted or use it sparingly.

Forgetting about text. You need at least three text levels: primary (near-black), secondary (medium gray), and muted (light gray). Each one needs to pass contrast checks on every background it'll sit on.

No hover/active states. Every interactive element needs at least three states: default, hover, and active/pressed. Plan these upfront instead of guessing darken(10%) later.

Skipping the "screenshot test." Take a screenshot of your app, shrink it to thumbnail size, and squint. Can you still tell what's clickable and what's content? If not, your color hierarchy isn't working.

Or just let AI do it

If you've read this far, you now know more about brand color palette generation than most developers. You understand the 60-30-10 rule, color psychology, accessibility requirements, and how to structure a full color system with semantic tokens.

But here's the honest truth: even with all this knowledge, going from theory to a polished, cohesive palette takes time. You'll second-guess your choices. You'll spend an hour tweaking shades in a color picker. You'll realize your accent looks weird next to your success green. And you haven't even started on font pairing or design tokens yet.

Want to test color harmony modes right now? Our free brand color palette generator lets you pick a color, explore complementary/analogous/triadic/split/mono harmonies, and export full shade scales with WCAG contrast checking. Then feed the result into the Tailwind theme generator for a complete theme config.

OneMinuteBranding does all of this in 60 seconds. Describe your product, and AI generates a complete brand system: color palette with full shade scales, light and dark mode, Tailwind config, design tokens, accessible contrast ratios, and a logo to match.

You can always fine-tune the output. But starting from a professionally generated palette and adjusting is 10x faster than starting from bg-blue-500 and hoping for the best.

Not sure how we compare to picking colors manually in tools like Coolors? See the OneMinuteBranding vs. Coolors comparison.


Your users don't evaluate your code quality. They evaluate how your product looks and feels. A solid color system is the fastest way to make that first impression count — whether you build it by hand or let AI handle the grunt work.

Y
Yann Lephay@YannBuilds

Vibe coder & Indie Hacker. Building tools to help devs ship faster. Creator of OneMinuteBranding.

Ready to create your brand?

Generate a complete brand system with Tailwind config in 60 seconds.

Generate your brand