FISYCO

Public beta
The gapImpactHow it worksProductSecurity
Sign in

Finding hard-coded colours in a React codebase

5 min read

Engineering
A code card with highlighted colour literals and a magnifier over one orange swatch next to a ranked list of files

A hard-coded colour is a value written straight into code, #2563eb instead of a token, and it quietly opts that element out of the design system. It won't follow a rebrand or a dark theme. This note gives you search commands that find them in a React codebase in minutes, the cases that produce false alarms, and how to stop new ones at review time.

Key takeaways
- Search for five kinds of colour: hex, colour functions, named colours, raw palette tokens and framework escape hatches such as Tailwind's bg-[#…].
- Exclude generated code and comments before you count, or the numbers will lie.
- Turn the search into a lint rule so the count only goes down.
- A hard-coded colour isn't always wrong; allow exceptions with a written reason.

What counts as a hard-coded colour

CSS accepts more colour syntaxes than people remember. CSS Color 4 lists hex with 3, 4, 6 or 8 digits, rgb() and rgba(), hsl() and hsla(), hwb(), lab(), lch(), oklab(), oklch() and color() (CSS Color Module Level 4). On top of that come about 150 named colours (MDN: named-color).

In a React codebase, they hide in five places:

The last two are easy to miss. A primitive palette token like gray.100 looks like a token but behaves like a hard-coded value: it keeps the same value in every theme. Tailwind's square brackets "generate a class on the fly with any arbitrary value" (Tailwind: arbitrary values), which is handy but bypasses the theme entirely.

Search commands for each kind of colour

ripgrep searches a whole repository quickly and respects .gitignore, so build output stays out by default. Its default regex engine has no lookbehind, which is why the hex pattern uses a character class instead. Start with the two patterns that catch most cases:

# Hex colours, not preceded by a letter, & or ( (skips – and issue refs like (#619))
rg -n -e '(^|[^&\w(])#[0-9a-fA-F]{3,8}\b' src

# Colour functions (color( is left out: it matches too many JavaScript calls)
rg -n -e '\b(rgba?|hsla?|hwb|lab|lch|oklab|oklch)\(' src

Then the React-specific ones:

# Raw palette tokens in style props (adjust the palette names to your theme)
rg -n -e '["'"'"'`](gray|red|blue|green)\.\d{2,3}["'"'"'`]' src

# Tailwind arbitrary colours
rg -n -o -e '\b(bg|text|border|fill|stroke)-\[#[0-9a-fA-F]{3,8}\]' src

# Named colours in JS style props (a bare word search drowns in noise; plain CSS needs a Stylelint rule)
rg -n -e '\b(color|background|backgroundColor|bg|borderColor|fill|stroke)\s*[:=]\s*\{?\s*["'"'"'](white|black|red|blue|green|gray|grey)["'"'"']' src

On a small test project with one of each case, these five commands found every hard-coded value, plus three false alarms that the next section removes.

Remove the noise

A raw count is almost always too high. Three sources inflate it:

  1. Comments. A comment such as // Fixes the bug from #619 matches the hex pattern. We hit exactly this in our own CSS check, where a (#619) inside a comment was reported as a colour. Drop comment lines before counting.
  2. Generated code. Icon components exported from Figma carry their original colours. When we first measured a demo repository, the icons our own sync had exported counted as about 590 hard-coded colours. The code was fine; the metric wasn't. Exclude generated folders with a glob.
  3. The theme itself. The file that maps semantic tokens to the palette must mention the palette. Exclude it by name.

Put together, with a count per file:

rg -n --no-heading -g '!src/icons/**' -g '!src/theme.ts' \
  -e '(^|[^&\w(])#[0-9a-fA-F]{3,8}\b' \
  -e '\b(rgba?|hsla?|hwb|lab|lch|oklab|oklch)\(' src \
  | rg -v '^[^:]+:\d+:\s*(//|/\*|\*)' \
  | cut -d: -f1 | sort | uniq -c | sort -nr

The output is a ranked list of files; start at the top. The filter drops whole-line comments only, so trailing comments still count: skim the first results before trusting the total.

Decide what's allowed

Not every hard-coded colour is a mistake. A brand logo's exact colour, the white canvas of an email template, the black behind a video player: these are values, not roles. The problem is an exception nobody can see.

Our rule is simple: an exception gets a comment with a reason on the same line or the line above, and the check skips it.

// design-tokens-allow: many email clients don't support CSS variables
const EMAIL_CANVAS = "#ffffff";

A reviewer can read every exception with one search, and "why is this hard-coded?" has an answer in the code.

Stop new ones at review time

A search tells you where you are. A lint rule keeps you from sliding back.

  • Stylelint. The built-in color-no-hex rule does what its name says. The plugin stylelint-declaration-strict-value goes further: it requires a variable, function or keyword for the properties you list, such as color, fill and stroke.
  • ESLint. Atlassian's ensure-design-token-usage rule flags hard-coded colours and raw spacing in JavaScript and enforces its token() function. It's specific to Atlassian's tokens, but it shows the shape of a rule worth writing for your own.
  • A script in CI. If your styling lives in props, a short script built on the patterns above is often the fastest route. Ours runs as pnpm design:check in CI on every pull request and before every commit.

Start the rule in warning mode, fix the top files from your ranked list, then switch it to error. The count should only go down from there.

Where hard-coded colours fit in a health check

Hard-coded colours are one kind of bypass: code that skips the system even when a token exists. Counting them is one of the four signals in our guide to measuring design system health, and it's the part of an audit you can run tonight. The design system audit in one afternoon puts it next to the other checks.

See every bypass in your React codebase next to the design it should follow.
#Drift#Design tokens
KK

Written by

Kirill Karpov

Founder, FISYCO

Kirill Karpov is the founder of FISYCO, a design system assurance tool that keeps Figma and production code in sync.

Get new articles by email

Design systems, tokens and design-to-code delivery. No spam, and you can leave in one click.

By subscribing you agree to get blog emails from FISYCO. Details are in the privacy policy.

Keep your design system and your code in sync

FISYCO connects Figma libraries and your codebase, finds drift and opens the fix as a pull request.

Start for free

Related articles

5 min read

Engineering

An SVG sprite from Figma, and when it beats components

One file of symbols instead of one component per icon: when an SVG sprite is the better choice, how to use it in React, and the gotchas that trip people up.

7 min read

Engineering

Figma icons to React components, end to end

From an icon component in Figma to a merged pull request: every step, what it produces, and the three delivery formats with real generated output.

6 min read

Engineering

Style Dictionary in practice: design tokens to CSS, SCSS and TypeScript

A working Style Dictionary 5 setup for DTCG tokens, tested on a real build: references, modes, units, and a rename experiment that breaks CSS silently.

Find hard-coded colours in a React codebase | FISYCO