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.