Back to blog

How to setup semantic Tailwind CSS colors

Save time and create better products by properly setting up your Tailwind CSS config

Irvin Zhan

July 3, 2024

Tailwind CSS ships terrific color palettes by default. But one common mistake developers make is not giving semantic names to their colors earlier.

Adopting semantic colors early will allow you to:

  1. Spend less time guessing which colors to use
  2. Build consistent product experiences
  3. Easily refactor colors into a future design system
  4. Be thanked endlessly by your designer :)

In this in-depth guide, we'll dive into why this is true and teach you how to add semantic colors to your tailwind.config.js.

What are semantic colors?

Semantic color names are meaningful names that give context to how a color should be used. For example, “error” is a semantic name for when an app cannot do what a user wants. But the actual color could be red, pink, rose.

The hierarchy of colors in most Tailwind-based design systems start from least to most semantic:

  1. Hex code – the actual color (e.g. #E0E7FF)
  2. Tailwind color – the preset color by Tailwind (e.g. bg-blue-100). Skipped if your brand uses non-preset colors.
  3. Global color – describes the basic semantic role of the color, such as brand or error
  4. Alias color – gives advanced semantic meaning, usually specifying how a color should be used

Semantic color hierarchySemantic color hierarchy

Some setups add a fifth layer of colors called component colors (e.g. bg-button-brand), but this is usually optional.

How to use semantic colors

As a rule of thumb, you should always be using the color with the most semantic meaning. This means using alias colors whenever possible.

// ❌ Don't use default Tailwind colors directly
<Button className="bg-red-600 hover:bg-red-700">Delete</Button>

// ✅ Choose a global color
<Button className="bg-error-600 hover:bg-error-700">Delete</Button>

// ✅ Or even better: use an alias color
<Button className="bg-error-primary hover:bg-error-primary-hover">Delete</Button>

You can incrementally migrate to semantic colors by using Tailwind CSS and global colors first, then gradually replacing them with alias colors as you start to see patterns.

By using alias colors you make styling elements easier. Want to create a primary button? Just use bg-brand-primary hover:bg-brand-primary-hover

When you take the guesswork out of colors, you not only build faster but also create more consistent product experiences.

Using semantic colors with Tailwind CSS

1. Create a naming system for your colors

We recommend the [role]-[prominence]-[interaction] format for your alias colors:

  1. Type – the Tailwind prefix like bg, text, border that this color will be used for
  2. Role – describes how the color should be used, such as brand, neutral, error, warning, success. For foreground elements like white text on top of brand colors, onbrand, onneutral, onerror, etc is used.
  3. Prominence – describes how much visual emphasis the color has, typically primary, secondary, tertiary
  4. Interaction (optional) – describes colors used for specific UI states such as hover or pressed

Breakdown of [role]-[prominence]-[interaction] formatBreakdown of [role]-[prominence]-[interaction] format

A few examples of colors we use internally at Subframe:

  1. bg-brand-primary: background for components like primary buttons
  2. bg-brand-primary-hover: hover color for components with bg-brand-primary
  3. bg-brand-secondary: same as primary but for secondary emphasis like secondary buttons
  4. text-onbrand-primary: text color used when applied on top of bg-brand-primary components
  5. text-neutral-primary: text color for most text
  6. text-neutral-emphasis: text color for bold text like headers

Example of colors applied to buttonsExample of colors applied to buttons

2. Add your colors to Tailwind config

Step 1: Add your global colors

We recommend adding these five palettes:

  • Brand
  • Neutral
  • Error
  • Success
  • Warning

If you will be using preset Tailwind colors for your global colors, Tailwind makes it easy to setup:

const colors = require("tailwindcss/colors")

module.exports = {
  theme: {
    colors: {
      brand: colors.blue,
      neutral: colors.slate,
      error: colors.red,
      success: colors.green,
      warning: colors.yellow,
    },
  },
}

You can also create global colors with custom hex values:

const colors = require("tailwindcss/colors")

module.exports = {
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#eff6ff",
          100: "#dbeafe",
          200: "#bfdbfe",
          300: "#93c5fd",
          400: "#60a5fa",
          500: "#3b82f6",
          600: "#2563eb",
          700: "#1d4ed8",
          800: "#1e40af",
          900: "#1e3a8a",
          950: "#172554",
        },
      },
    },
  },
}

Step 2: Add alias colors

You'll need to modify your Tailwind config file for the specific Tailwind prefix (bg, text, border). We recommend at minimum having primary / secondary colors for all of your global colors for each Tailwind prefix:

const colors = require("tailwindcss/colors")

// moved to separate object so they can be referenced within the theme
const globalColors = {
  brand: colors.blue,
  neutral: colors.slate,
  error: colors.red,
  success: colors.green,
  warning: colors.yellow,
}

module.exports = {
  theme: {
    extend: {
      colors: globalColors,
      backgroundColor: {
        "brand-primary": globalColors.brand[600],
        "brand-primary-hover": globalColors.brand[500],
        "brand-secondary": globalColors.brand[100],
        "brand-secondary-hover": globalColors.brand[200],
        "neutral-primary": globalColors.white,
        "neutral-secondary": globalColors.neutral[50],
        // ...your other global colors like errors, success, warning
      },
      borderColor: {
        "brand-primary": globalColors.brand[400],
        "neutral-primary": globalColors.brand[300],
        // ...your other global colors like neutrals, errors, success, warning
      },
      textColor: {
        "brand-onprimary": colors.white,
        "brand-onsecondary": globalColors.brand[800],
        // NOTE: "neutral-*" can even be "primary", "secondary", and "emphasis" given most text is neutral
        "neutral-primary": globalColors.neutral[800],
        "neutral-secondary": globalColors.neutral[500],
        "neutral-emphasis": globalColors.neutral[900],
        // ...your other global colors like errors, success, warning
      },
    },
  },
}

Step 3: Verify that things worked

Try creating a simple button:

function Example() {
  return (
    <button className="bg-brand-primary hover:bg-brand-primary-hover text-brand-onprimary py-2 px-3 rounded-lg">
      It worked!
    </button>
  )
}

And seeing it rendered correctly:

What you should seeWhat you should see

Now your designer can quickly re-theme your entire app by tweaking a single tailwind.config.js file.

To wrap up, setting up semantic colors in Tailwind helps you:

  1. Speed up development
  2. Build consistent product experiences

This guide is intended as a starting point to be expanded as your needs change. It's the culmination of best practices we learned setting up semantic colors for thousands of developers at Subframe. If you’d like to automate setting up a Tailwind config, check out our free Tailwind theme picker.

Join thousands of happy builders

Subframe lets you build stunning UI with beautifully crafted components and a drag-and-drop visual editor.