Regex for a hex color (#RGB, #RGBA, #RRGGBB, #RRGGBBAA)

Two tested patterns for CSS hexadecimal colors: one that accepts all four notations the CSS Color specification defines, and a stricter one for tools that only take #RRGGBB.

All CSS hex notations

Rule: CSS Color Module Level 4 <hex-color>: a "#" followed by 3, 4, 6 or 8 hexadecimal digits, case-insensitive.

/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/

Open in tester Loads the pattern with sample text, JavaScript flavor.

Parts of the pattern

#
The hash character. It is not special in a regular expression, so it needs no escape (it only starts a comment in free-spacing mode, flag x in PCRE2).
[0-9a-fA-F]{3,4}
Three or four digits: #RGB and #RGBA, where each digit is doubled (CSS: "f" means "ff").
[0-9a-fA-F]{6}
Six digits: #RRGGBB.
[0-9a-fA-F]{8}
Eight digits: #RRGGBBAA, where the last pair is the alpha channel.

Test cases

16 of 16 rows agree with the rule.

Test cases for All CSS hex notations
Input Rule says Pattern says Result Note
#fff valid match pass 3 digits.
#FFF valid match pass Upper case.
#0a9f valid match pass 4 digits (RGBA).
#ff69b4 valid match pass 6 digits (hot pink).
#FF69B480 valid match pass 8 digits, half-transparent.
#00000000 valid match pass Fully transparent black.
#ff invalid no match pass 2 digits.
#12345 invalid no match pass 5 digits: not one of 3, 4, 6 or 8.
#1234567 invalid no match pass 7 digits.
#123456789 invalid no match pass 9 digits.
ff69b4 invalid no match pass No hash.
#gg69b4 invalid no match pass "g" is not hexadecimal.
#␣fff invalid no match pass Space after the hash.
#fff␣ invalid no match pass Trailing space.
#fff\n invalid no match pass Trailing newline: JavaScript's $ rejects it; PCRE2, Python and Java's $ would accept it.
#ffffff; invalid no match pass A CSS declaration would end with a semicolon, which is not part of the color.

Six digits only (#RRGGBB)

Rule: A "#" followed by exactly six hexadecimal digits, as used by tools that have no alpha channel.

/^#[0-9a-fA-F]{6}$/

Open in tester Loads the pattern with sample text, JavaScript flavor.

Parts of the pattern

#[0-9a-fA-F]{6}
Hash and exactly six hexadecimal digits.

Test cases

9 of 9 rows agree with the rule.

Test cases for Six digits only (#RRGGBB)
Input Rule says Pattern says Result Note
#ff69b4 valid match pass Six digits.
#000000 valid match pass Black.
#FFFFFF valid match pass White.
#abcdef valid match pass All six letters of the hex alphabet.
#fff invalid no match pass Valid CSS but only three digits; expand it to #ffffff first.
#ff69b480 invalid no match pass Eight digits.
ff69b4 invalid no match pass No hash.
#ff69b invalid no match pass Five digits.
#ff69bg invalid no match pass Non-hex digit.

How it works

CSS Color Module Level 4 says the syntax of a <hex-color> is a hash token whose value consists of 3, 4, 6 or 8 hexadecimal digits, and explains how the number of digits decides how the notation is decoded. MDN adds that each component is a case-insensitive hexadecimal number and that a single digit is duplicated: "f" means "ff". The pattern lists the digit counts as alternatives: {3,4} merges the two short forms, {6} and {8} are the long ones.

The hexadecimal alphabet is spelled out as [0-9a-fA-F] instead of using a case-insensitive flag, so the pattern keeps its meaning if it is pasted into a context with different flags. Using \d or \w would be wrong: \w accepts letters up to z, and \d is a Unicode class in some flavors.

The test table includes every digit count from 2 to 9 on purpose. The common bug is a quantifier like {3,8}, which accepts 5 and 7 digits; the table has a row for each of those.

Known false positives

  • A syntactically valid hex color is not necessarily a color you want: #00000000 is fully transparent, and the pattern does not know about your design system.
  • Context matters. In a CSS selector, #abc is an ID selector, not a color; the pattern checks only text, so if you run it on selectors it will report a color where there is none.

Known false negatives

  • Named colors (red), functional notations (rgb(), hsl()), the transparent keyword and currentColor are all valid CSS colors but have no hash and are rejected. This page is about hex notation only.
  • The patterns require the leading hash. Some systems store hex colors without it (ff69b4); add an optional #? if you want to accept both, then normalise.

Flavor notes

  • [0-9a-fA-F] works identically in all five flavors. Adding a case-insensitive flag lets you write [0-9a-f], which works in JavaScript (i flag), PCRE2, Python, Java and Go ((?i)).
  • Trailing newline: in PCRE2 (default), Python and Java the $ also matches before a final newline, so "#fff" followed by a line feed would pass. In JavaScript and Go it does not. The cross-engine tests here use \z for PCRE2.
  • The patterns use only classes, counted repeats and alternation, so JavaScript, PCRE2 and Go all accept them and are tested here. Python and Java are notes only on this site; nothing is executed on them.

Why not regex here?

In a browser you can ask the browser itself whether a string is a valid CSS color: CSS.supports("color", value) returns whether the browser supports that declaration (MDN). That covers named colors and every other notation, and follows the browser's own parser. It validates against what this browser supports, so it can differ between browsers; use the regex when you need the same answer on a server and in tests.

How to use

  1. Copy the pattern for the variant that fits your rule. Variants differ in strictness, and the rule line says exactly what each accepts.
  2. Check how it is anchored: the patterns use ^ and $ to test a whole string. To find the same thing inside longer text, remove the anchors (and add word-boundary or lookaround checks) and re-run the cases.
  3. If your language is not JavaScript, read Flavor notes and change $ to \z or use a full-match function.
  4. Open it in the tester to see the explanation of each token and try your own inputs.

Worked examples

Expand #RGB to #RRGGBB

After the CSS pattern accepts #f0a, each digit is doubled to give #ff00aa. In the replace tester, use /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i with the replacement "#$1$1$2$2$3$3".

Find colors in a stylesheet

Remove the anchors and add a lookahead so that you do not take the first six digits of a longer hex run: #[0-9a-fA-F]{3,8}\b catches digit runs of all lengths, and you then filter by length in code.

Limits & gotchas

  • The pattern cannot say whether a color is readable, accessible or on-brand.
  • It treats #abcd as a valid RGBA color. Very old browsers that predate four- and eight-digit hex colors do not; check your support matrix.

FAQ

Why does #12345 fail?

CSS defines hex colors with 3, 4, 6 or 8 digits. Five digits is not any of those, so it is not a hex color.

Do hex colors have to be lower case?

No. MDN describes each component as a case-insensitive hexadecimal number, so #FFF and #fff are the same color. The pattern accepts both.

How do I read the alpha channel?

In the 4- and 8-digit forms the last digit (or pair) is the alpha: 00 is fully transparent and ff fully opaque, according to MDN.

Should I use \w or \d in the character class?

No. \w matches letters beyond f and underscore, and \d is not the same in every flavor. [0-9a-fA-F] is exact.

Sources

  1. W3C: CSS Color Module Level 4: hex notation Used for: A hex color has 3, 4, 6 or 8 hexadecimal digits, case-insensitive.
  2. MDN: <hex-color> Used for: #RGB, #RGBA, #RRGGBB, #RRGGBBAA, case-insensitive.
  3. MDN: CSS.supports() Used for: CSS.supports(property, value) returns whether the browser supports a CSS declaration.
  4. MDN: Input boundary assertion: ^, $ Used for: ^ and $ are the start and end of input, or of each line with the m flag.
  5. PCRE2: pcre2pattern Used for: Syntax and semantics: groups, named groups, lookbehind rules, atomic groups, possessive quantifiers, \d \s \w with and without UCP, dollar and newline handling, \A \Z \z.
  6. Python docs: re: Regular expression operations (3.13) Used for: Syntax, (?P<name>), atomic groups and possessive quantifiers (3.11+), fixed-length lookbehind, Unicode \d \s \w, $ before trailing newline, \A \Z, re.sub replacement syntax, inline flags at start only (3.11+).
  7. Oracle (Java SE 21): java.util.regex.Pattern Used for: Construct table, \d \s \w without UNICODE_CHARACTER_CLASS, possessive and atomic constructs, named groups, line terminators and $, \A \Z \z.
  8. Go: regexp/syntax Used for: Full syntax table; no lookaround or backreferences; \d \s \w are ASCII-only; $ is \z; (?P<name>) and (?<name>); repetition limit 1000; \A and \z.
  9. MDN: Character class escape: \d, \D, \w, \W, \s, \S Used for: \d is [0-9]; \w is letters, digits and underscore; \s is whitespace plus line terminators.

Every document above was opened and read on 2026-10-02. Documentation changes; if a page here disagrees with the current docs, trust the docs and tell us.