A literal like cat matches one exact character at each position. A character class matches any one character from a set you define, using square brackets. [aeiou] matches a single vowel — not all five in a row, just one character that happens to be one of those five.
/[aeiou]/.test("sky"); // false — no vowel in "sky"
/[aeiou]/.test("cat"); // true — matches the "a"
With the g flag, the demo finds every vowel in the string individually — e, o, o — because each match consumes just one character before the engine moves on to look for the next one.
Ranges
Inside [...], a hyphen between two characters defines a range, so you don’t have to list every character by hand. [a-z] matches any lowercase letter, [A-Z] any uppercase letter, [0-9] any digit, and you can combine ranges: [a-zA-Z0-9] matches any letter or digit.
/[a-z]/.test("HELLO"); // false — no lowercase letters
/[a-zA-Z0-9]/.test("!!!"); // false — no letters or digits at all
Negation with [^...]
Putting a caret ^ immediately after the opening bracket flips the meaning: the class now matches any character not in the set.
/[^0-9]/.test("42"); // false — every character is a digit
/[^0-9]/.test("42a"); // true — the "a" isn't a digit
The demo matches a, b, and c — everything in "abc123" that isn’t a digit — and correctly skips over 1, 2, 3.
Watch out
A caret only means “negate” when it’s the very first character inside the brackets. [a^z] matches a, ^, or z literally — the caret has no special meaning there because it’s not in the first position.
Shorthand classes
Because certain character classes come up constantly, regex gives them single-letter shortcuts:
| Shorthand | Equivalent to | Matches |
|---|---|---|
\d |
[0-9] |
Any digit |
\D |
[^0-9] |
Any non-digit |
\w |
[a-zA-Z0-9_] |
Any “word” character (letters, digits, underscore) |
\W |
[^a-zA-Z0-9_] |
Any non-word character |
\s |
[ \t\n\r\f\v] |
Any whitespace (space, tab, newline, etc.) |
\S |
[^ \t\n\r\f\v] |
Any non-whitespace |
These are especially handy combined with the quantifiers you’ll meet next lesson — \d+ means “one or more digits in a row”:
The demo pulls out 12345, 67, and 89 as three separate digit runs, stopping each match as soon as it hits a non-digit character.
The dot: match (almost) anything
You saw the dot . briefly in the last lesson — it’s technically its own thing, not a character class, but it behaves similarly: it matches any single character except (by default) a newline.
/c.t/.test("cat"); // true
/c.t/.test("cot"); // true
/c.t/.test("ct"); // false — dot requires exactly one character
Notice the last match: c.t even matches "c t" (with a literal space between them), because the dot doesn’t care which character it matches, only that there’s exactly one.
Try it yourself
Using the demo above, change the pattern from c.t to c[aeiou]t — a character class instead of a dot. Compare which lines still match.
What should happen
c[aeiou]t only matches cat and cot (and would match cut, cet, cit too) because the middle character is restricted to vowels — the "c t" match disappears since a space isn’t in the [aeiou] set. This is the key difference between the dot (any character at all) and a character class (a specific, chosen set of characters).
What’s next
Character classes control which characters can appear at a position. The next lesson covers anchors — ^, $, and \b — which control where in the string a match is allowed to happen.