Regex for a hex color code

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

CSS writes a hex color with 3, 4, 6 or 8 hexadecimal digits after a #. Six digits are red, green and blue as two digits each; three are the short form, where #fc3 means #ffcc33; the 4- and 8-digit forms add an alpha channel at the end, so #ffcc3380 is the same yellow at about half opacity. The regex lists the lengths as alternatives, and i accepts upper and lower case alike.

What it accepts

The tester below checks one color per line with g, i and m; for a single value, keep only i.

#fff           matches: short form
#FFCC33        matches
#ffcc3380      matches: with alpha
#0f08          matches: short form with alpha
ffcc33         no: no #
#ffcc3         no: five digits
#ggg           no: g isn't hexadecimal
#ffcc33ff00    no: ten digits

Variations

Without alpha, only 3 or 6 digits:

/^#(?:[0-9a-f]{3}){1,2}$/i

{1,2} repeats the group of three once or twice, which gives exactly 3 or 6 digits.

With the # optional, for a form field where people paste ffcc33:

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

Put the # back before you use the value; CSS needs it.

Finding colors in a stylesheet

To pick every hex color out of CSS, drop the anchors, add g, and end on a word boundary, so a run that’s too long, such as #ffcc33ff00, isn’t half matched:

const colors = css.match(/#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})\b/gi) ?? [];

The longest alternative comes first here. JavaScript takes the first alternative that lets the whole match succeed, not the longest one. With the \b at the end, either order finds the same colors, but with {3,4} first, #ffcc33 would try #ffcc, fail the \b and backtrack before reaching six digits.

A regex checks the spelling of a color, not what it looks like. The Color Converter & Contrast Checker turns a hex code into RGB and HSL.

Try it in the Regex Tester

Flags

4 matches

Matches

#MatchAtGroups
1#fff0–4none
2#FFCC335–12none
3#ffcc338013–22none
4#0f0823–28none

What the pattern means

  • ^The start of a line
  • #The character "#"
  • (?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})Non-capturing group, one of 3 alternatives
    • [0-9a-f]{3,4}One of: 0–9 or a–f, 3 to 4 times, as many times as possible (greedy)
      • 0-9A character from 0 to 9 (U+0030 to U+0039)
      • a-fA character from a to f (U+0061 to U+0066)
    • [0-9a-f]{6}One of: 0–9 or a–f, exactly 6 times
      • 0-9A character from 0 to 9 (U+0030 to U+0039)
      • a-fA character from a to f (U+0061 to U+0066)
    • [0-9a-f]{8}One of: 0–9 or a–f, exactly 8 times
      • 0-9A character from 0 to 9 (U+0030 to U+0039)
      • a-fA character from a to f (U+0061 to U+0066)
  • $The end of a line
  • g global: Find every match, not just the first. exec() and test() carry on from lastIndex.
  • i ignoreCase: Letters match in either case.
  • m multiline: ^ and $ match at the start and end of each line, not just of the whole string.

Runs in your browser. Nothing you enter is sent anywhere.