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.
| 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.
| 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
- Copy the pattern for the variant that fits your rule. Variants differ in strictness, and the rule line says exactly what each accepts.
- 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. - If your language is not JavaScript, read Flavor notes and change
$to\zor use a full-match function. - 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
- W3C: CSS Color Module Level 4: hex notation Used for: A hex color has 3, 4, 6 or 8 hexadecimal digits, case-insensitive.
- MDN: <hex-color> Used for: #RGB, #RGBA, #RRGGBB, #RRGGBBAA, case-insensitive.
- MDN: CSS.supports() Used for: CSS.supports(property, value) returns whether the browser supports a CSS declaration.
- MDN: Input boundary assertion: ^, $ Used for: ^ and $ are the start and end of input, or of each line with the m flag.
- 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.
- 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+).
- 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.
- 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.
- 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.