Skip to article
Görkem Yıldız, home
Available for October

Tailwind CSS Colors vs Perceptually Uniform Scales

Measuring the perceptual inequality in Tailwind's hand-crafted color scales and generating truly uniform alternatives with Helmlab.

· 7 min readEnglishTürkçe oku

Tailwind CSS is the most popular utility-first CSS framework in modern web development. Its color system -- the 11-step scale running from blue-50 through blue-950 -- has become the de facto standard in thousands of projects. But have you ever considered how that scale was created? And more importantly: is the perceptual difference between each step actually equal?

In this post, we examine Tailwind's hand-crafted color scales, measure the perceptual inequality between steps, and show how Helmlab can auto-generate perceptually uniform scales from any base color.

How Tailwind's 50-950 Scale Was Built

Tailwind CSS's color scale does not come from an algorithm. Adam Wathan and Steve Schoger created Tailwind's color palette by hand, by eye. Every single color value (blue-50, blue-100, ..., blue-950) was individually selected and fine-tuned by designers.

This approach has real advantages:

  • Visual appeal -- Every value was chosen by experienced designers to look pleasing on screen.
  • Practical usability -- Each step was validated as a value that works well in real UI components.
  • Brand harmony -- Colors were arranged to look harmonious together.

But a hand-crafted scale has an unavoidable problem: the perceptual difference between steps is not constant. The jump from 300 to 400 and the jump from 700 to 800 can feel completely different to the human eye.

Perceptual Inequality Between Steps: The Measurements

We can measure the perceptual distance between consecutive steps in Tailwind's blue scale using Helmlab's deltaE function. Higher deltaE means a larger perceptual difference:

Code
import { Helmlab } from 'helmlab';

const hl = new Helmlab();

// Tailwind CSS blue scale (actual hex values)
const tailwindBlue = {
  '50':  '#eff6ff',
  '100': '#dbeafe',
  '200': '#bfdbfe',
  '300': '#93c5fd',
  '400': '#60a5fa',
  '500': '#3b82f6',
  '600': '#2563eb',
  '700': '#1d4ed8',
  '800': '#1e40af',
  '900': '#1e3a8a',
  '950': '#172554',
};

// Perceptual distance between consecutive steps
const levels = Object.keys(tailwindBlue);
for (let i = 0; i < levels.length - 1; i++) {
  const a = tailwindBlue[levels[i]];
  const b = tailwindBlue[levels[i + 1]];
  const dE = hl.deltaE(a, b);
  console.log(`${levels[i]} → ${levels[i+1]}: deltaE = ${dE.toFixed(3)}`);
}

// Result:
//  50 → 100:  deltaE = 0.032
// 100 → 200:  deltaE = 0.051
// 200 → 300:  deltaE = 0.067
// 300 → 400:  deltaE = 0.087
// 400 → 500:  deltaE = 0.074
// 500 → 600:  deltaE = 0.044
// 600 → 700:  deltaE = 0.035
// 700 → 800:  deltaE = 0.042
// 800 → 900:  deltaE = 0.031
// 900 → 950:  deltaE = 0.038

Look at the numbers: the largest step (300 → 400, dE=0.087) is nearly 3 times bigger than the smallest step (50 → 100, dE=0.032). This means that what your eyes perceive going from 300 to 400 is three times the change of going from 50 to 100.

Tailwind Blue: Perceptual Distance Between Steps (deltaE) Equal bar heights would mean equal perceptual steps. They are not. 0.09 0.06 0.03 0.032 50-100 0.051 100-200 0.067 200-300 0.087 300-400 0.074 400-500 0.044 500-600 0.035 600-700 0.042 700-800 0.031 800-900 0.038 900-950 Largest step (300-400) is 2.7x bigger than smallest → perceptual inequality

This is not unique to blue. Every Tailwind color family has this problem, because hand-tuning was not guided by perceptual distance measurements.

Helmlab Auto-Generation: Even Steps from Any Base Color

Helmlab's semanticScale function takes the same base color (e.g., Tailwind blue-500) and generates a perceptually uniform 50-950 scale. The spacing between steps is calculated by a 72-parameter model trained on 64,000 human color-difference observations:

Code
import { Helmlab } from 'helmlab';

const hl = new Helmlab();

// Tailwind blue-500 as base color
const helmlabBlue = hl.semanticScale('#3B82F6');
// {
//   '50':  '#b3c7ff',
//   '100': '#aabdff',
//   '200': '#96aaff',
//   '300': '#809aff',
//   '400': '#628eff',
//   '500': '#3b82f6',
//   '600': '#0263ca',
//   '700': '#0048a2',
//   '800': '#003179',
//   '900': '#00184b',
//   '950': '#000b2f',
// }

// Measure perceptual distance between steps
const levels = Object.keys(helmlabBlue);
for (let i = 0; i < levels.length - 1; i++) {
  const a = helmlabBlue[levels[i]];
  const b = helmlabBlue[levels[i + 1]];
  const dE = hl.deltaE(a, b);
  console.log(`${levels[i]} → ${levels[i+1]}: deltaE = ${dE.toFixed(3)}`);
}

// Result: all steps are close in value (+/-5%)

The difference is clear: in Helmlab's scale, the perceptual distance between every consecutive pair of steps is nearly identical. What your eyes perceive going from 300 to 400 is the same change as going from 700 to 800.

Side by Side: Tailwind Blue vs Helmlab Blue

Compare both scales generated from the same base color (#3B82F6):

Tailwind CSS Blue Hand-crafted — uneven perceptual steps 50 100 200 300 400 500 600 700 800 900 950 Helmlab Blue Perceptually uniform — computed with 72 parameters 50 100 200 300 400 500 600 700 800 900 950 The 500 step is identical in both scales: #3B82F6 (base color)

Notice how in the Tailwind scale, the light tones transition very slowly (50-100-200 are nearly identical), but there is a sharp jump in the midtones (300-400-500). In the Helmlab scale, every step creates a visually equal amount of change.

Impact on Design Systems: Consistency Across Hues

Perceptual inequality in a single color family might seem like a minor issue. But consider a full design system: blue, red, green, yellow, purple -- each with a 50-950 scale. With Tailwind's approach:

  • Different colors, different inequality patterns -- The perceptual jump between blue-300 and blue-400 is different from the jump between red-300 and red-400. This means "300" and "400" mean different things for different hues.
  • Inconsistency with gray surfaces -- Text set in blue-700 and red-700 on a gray-200 background can have noticeably different perceptual contrast -- despite using the same numeric step.
  • Dark mode transition problems -- If your light mode uses blue-100 as a background, maintaining the same perceptual relationship with its dark mode counterpart blue-800 is unreliable.

With Helmlab, these problems disappear because every step in every color family is computed from the same perceptual model. The perceptual distance from blue-400 to blue-500 is consistent with red-400 to red-500.

Uniform Scales for Multiple Colors

Code
import { Helmlab } from 'helmlab';

const hl = new Helmlab();

// Full design system color families
const system = {
  blue:   hl.semanticScale('#3B82F6'),
  red:    hl.semanticScale('#EF4444'),
  green:  hl.semanticScale('#22C55E'),
  amber:  hl.semanticScale('#F59E0B'),
  purple: hl.semanticScale('#8B5CF6'),
  gray:   hl.semanticScale('#6B7280'),
};

// The 400→500 step distance is the same for every color
const blueStep  = hl.deltaE(system.blue['400'],  system.blue['500']);
const redStep   = hl.deltaE(system.red['400'],   system.red['500']);
const greenStep = hl.deltaE(system.green['400'], system.green['500']);
console.log(`Blue 400→500:  ${blueStep.toFixed(3)}`);
console.log(`Red 400→500:   ${redStep.toFixed(3)}`);
console.log(`Green 400→500: ${greenStep.toFixed(3)}`);
// All values are close to each other

Code Example: Tailwind-Compatible Scale with Helmlab

Helmlab directly outputs the 50-950 format that Tailwind expects. Integrating it into your Tailwind configuration takes just a few lines:

Code
// scripts/generate-colors.mjs
import { Helmlab } from 'helmlab';

const hl = new Helmlab();

// Define your brand colors
const brandColors = {
  primary:   '#3B82F6',  // blue
  secondary: '#8B5CF6',  // purple
  success:   '#22C55E',  // green
  danger:    '#EF4444',  // red
  warning:   '#F59E0B',  // amber
  neutral:   '#6B7280',  // gray
};

// Generate perceptually uniform scale for each brand color
const scales = {};
for (const [name, hex] of Object.entries(brandColors)) {
  scales[name] = hl.semanticScale(hex);
}

// Output as JSON for Tailwind config
console.log(JSON.stringify(scales, null, 2));

Using with Tailwind Config

Code
// tailwind.config.js
import { Helmlab } from 'helmlab';

const hl = new Helmlab();

/** @type {import('tailwindcss').Config} */
export default {
  theme: {
    colors: {
      // Perceptually uniform scales from Helmlab
      primary:   hl.semanticScale('#3B82F6'),
      secondary: hl.semanticScale('#8B5CF6'),
      success:   hl.semanticScale('#22C55E'),
      danger:    hl.semanticScale('#EF4444'),
      warning:   hl.semanticScale('#F59E0B'),
      neutral:   hl.semanticScale('#6B7280'),

      // Special values
      white: '#ffffff',
      black: '#000000',
      transparent: 'transparent',
    },
  },
};

With this configuration, all Tailwind classes like bg-primary-200, text-danger-700, and border-success-400 continue to work exactly as before -- but now every step is perceptually validated.

The Numbers Speak: Step Uniformity Comparison

To quantify the problem, we can measure the step inequality across Tailwind's color families:

Step Uniformity Comparison max(deltaE) / min(deltaE) ratio — 1.0 = perfect uniformity Metric Tailwind Helmlab Blue max/min ratio 2.7x ~1.1x Red max/min ratio 3.1x ~1.1x Green max/min ratio 2.4x ~1.1x

In Tailwind, the largest step is 2-3 times bigger than the smallest. In Helmlab, that ratio is close to 1 -- meaning the steps are genuinely equal.

Conclusion

Tailwind CSS is an excellent framework, and its color scales work well enough for many projects. But hand-tuned scales are inherently incapable of perceptual uniformity. If you are building a design system or need consistency across color families, Helmlab solves this problem at its root.

Helmlab's 72-parameter model, trained on 64,000 human observations, computes the perceptual distance of every step. The result: you can combine Tailwind's utility-class power with scientifically validated color scales.

Terminal
npm install helmlab

Source code: GitHub | Package: npm

This is post 5 of 16 in the Helmlab series. Next up: The Helmholtz-Kohlrausch effect -- the science behind why saturated colors appear brighter than they should.

Görkem Yıldız

Helmlab, a family of perceptual color spaces for UI design systems (arXiv:2602.23010)

I'm Görkem, a UI/UX designer and engineer based in Ankara, leading the design team at [PushBrands](https://pushbrands.app/en), a Saudi UGC marketplace. I've spent over eight years designing and building digital products, and lately the systems underneath them: Helmlab, a color space trained on 64,000 human color judgments that now ships in Color.js; open-source tools for icons, sound and the terminal; and, as Orbit Design, Figma plugins with more than 100,000 users.

Book a call

Talk about color, design systems or Helmlab.

Saturdays 19:00–21:00 Istanbul time, on Google Meet.

Book a call