What this regex matches
The pattern that covers every CSS hex color form:
^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
Read it left to right:
^#requires the hash. Without it you are matching something that is not a CSS color.- The non-capturing group offers three alternatives, tried in order:
[0-9a-fA-F]{3,4}covers the three-digit#RGBand four-digit#RGBAshorthand.[0-9a-fA-F]{6}is the classic six-digit#RRGGBB.[0-9a-fA-F]{8}is the eight-digit#RRGGBBAAwith alpha.
$anchors the end so trailing junk is rejected.
Why 3, 4, 6, and 8 digits are all legal
CSS defines a shorthand rule: in the 3- and 4-digit forms, each hexadecimal digit is duplicated to produce the full value. So:
| Form | Example | Expands to |
|---|---|---|
#RGB | #f0a | #ff00aa |
#RGBA | #f0a8 | #ff00aa88 |
#RRGGBB | #ff00aa | itself |
#RRGGBBAA | #ff00aa88 | itself |
That is why the regex must accept both 3 and 4 (not just 3) and both 6 and 8 (not just 6). A pattern that hardcodes {6} silently breaks #fff, which is one of the most common colors on the web.
The case-insensitive option
Hex digits are case-insensitive: #FFF equals #fff. You can write the class as [0-9a-fA-F], or write [0-9a-f] and apply the i flag at compile time. Both match the same strings. Pick the one your language makes cleaner; just do not forget one of them, or #ABCDEF will be rejected.
Whether to allow a missing # is a product decision. In a strict CSS context the hash is mandatory. In a user input field you may accept 1a2b3c and prepend the hash yourself. The regex above requires the hash; the alternate shows the optional version.
Regex cannot tell you if the color will render
This is the core point, and it is worth stating plainly: a regex can only tell you that a string looks like a hex color. It cannot tell you the color is valid CSS, will be applied, or means anything in context.
In a browser you already have a far better validator built in. Use the CSS engine:
function isColor(value) {
return CSS.supports('color', value);
}
CSS.supports parses the value the same way the browser does, so it accepts #fff, rebeccapurple, rgb(0 0 0 / 50%), and rejects nonsense with the real parser. It also future-proofs you: when a new color syntax ships, the regex breaks and CSS.supports does not.
If you are not in a browser, parse the value yourself or lean on a small, well-tested color library instead of a hand-rolled regex. The regex is fine for "does this string resemble a color in a log file", not for "is this a real color".
Common mistakes
| Wrong | Problem | Right |
|---|---|---|
^#[0-9a-f]{6}$ | Rejects #fff, #ffff, #ffffffff. | Include 3,4,6,8 forms. |
^[0-9a-f]{6}$ | Forgets the mandatory #. | Require # or decide deliberately. |
^#[0-9a-fA-F]{8}$ | Misses the common 3- and 6-digit forms. | All four lengths. |
Mixing up #ffff and #ffffff | Four digits is RGBA, not a broken six-digit. | Treat 4 as RGBA, 8 as RRGGBBAA. |
#fff versus #ffff
A frequent source of confusion: #fff is three digits (shorthand RGB). #ffff is four digits, which means #ffff is shorthand RGBA with full alpha, not a malformed six-digit color. Likewise #ffffff is six-digit RGB and #ffffffff is eight-digit RGBA. The digit count alone tells you which channel layout you have, and the regex's alternation reflects exactly that.
Extracting every color from text
Sometimes you do not want to validate one value but to pull all hex colors out of a block of text (a stylesheet dump, a log, user paste). Drop the anchors and use the global flag:
/#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})/g
The boundary stops #1234567 from matching the first six digits as if they were a color. Remember this is extraction, not validation of intent: a hex string that happens to look like a color is not automatically a color the page uses.
When a regex is the right tool
To be fair, the regex earns its place in narrow cases:
- Sanity-checking a single value in a non-browser language where no CSS parser exists.
- Quickly scanning logs or pasted text for color-like tokens.
- A fast reject at a form's edge before deeper parsing.
But the moment the question is "will this color actually be used", reach for the platform parser. The regex answers "does it look right"; the parser answers "is it right".