Regexes That Show 'Pattern Valid' and Match Nothing
The worst state this tool can show you
Paste \p{L}+ — Unicode letters, the idiom from Python, PHP, Java, Rust, and PCRE — into the pattern box, with héllo world as the test string:
Pattern Valid
No matches found in test string.
Nothing is wrong with the tool. It is telling you the truth. In JavaScript, \p{L} outside a u-flagged regex is not a Unicode property class: \p is an identity escape that means the letter p, and {L} is not a recognised quantifier so it is treated as literal text. The pattern means "one or more p's followed by the literal characters {L}". Proof, run in Node:
/p+{L}/.test('ppp{L}') // true
'héllo'.match(/p+{L}/g) // null
The status is green because new RegExp() threw nothing. It is empty because the pattern is about something else entirely. Green-with-zero-matches is not "your regex is broken", it is "your regex means something you did not write", and this tool cannot tell you which.
The trap is structural, not incidental
The pattern box builds its expression as new RegExp(pattern, flags) where flags can only ever be some subset of gims. There is no u or v checkbox in the page's markup (flagG, flagI, flagM, flagS are the only flag inputs). That means the entire Unicode-property family, and the case-insensitive-Unicode behaviour people expect from it, is unreachable in this tool by design — and yet all of those patterns pass validation.
Same category, same result:
| You typed | Status | What actually happened |
|---|---|---|
\p{L}+ on héllo world | Valid | Means p+{L} literally → 0 matches |
x\Z on x | Valid | \Z is an identity escape for Z → 0 matches. JS uses $ |
\d+ on ١٢٣123 | Valid | Matches only 123 at INDEX 3 — \d is ASCII-only without u |
(?i)ABC on abc | Syntax Error | Inline flags do not exist in JS; use the i checkbox |
(?>a*)b on aaab | Syntax Error | Invalid group — atomic groups need the v flag |
(?<y>\d{4}) on year 2026 | Valid | Works: MATCH #1 · INDEX 5 2026 |
(a)|(b) on ab | Valid | Works, but see the group-display problem below |
The split matters. (?i) and (?>...) fail loudly — annoying, harmless. \p{L}, \Z, and \d are the dangerous ones: they are accepted, they run, and they silently match the wrong text. \d on Arabic-Indic digits is the one that reaches production, because "digit" in your head is not "digit" in a non-u regex.
Two things the results panel hides
Named groups are rendered by number. (?<year>\d{4}) against year 2026 gives:
MATCH #1 · INDEX 5
2026
Groups: $1: "2026"
The name is parsed and accepted, then dropped from the display. If you built a named-group pattern to read $year out of a substitution or code snippet, this panel will not confirm your names are working — only that the capture happened.
Unmatched groups look exactly like empty matches. (a)|(b) against ab:
MATCH #1 · INDEX 0 a Groups: $1: "a", $2: ""
MATCH #2 · INDEX 1 b Groups: $1: "", $2: "b"
$2: "" on match #1 means did not participate, not matched the empty string. The panel renders both identically. In code they are different (undefined vs ""), and in an alternation this is the normal state of every branch you did not take — so read empty group slots as "branch not taken" by default.
Zero-width matches are honest here
a* against bbb returns four matches at INDEX 0, 1, 2, 3 — one empty match at every position including the end. This is correct ECMAScript behaviour and the reason "0 matches" is sometimes the surprising answer. The engine's loop-guard increments lastIndex when a match is zero-width, so it terminates instead of hanging; that guard is in the shipped code and the run above finished normally.
Making a pattern actually mean what you think
There is no u flag available in this tool, so the fix is to stop relying on the syntax that needs it:
- Unicode letters: use a negated class —
[^\s\d\p...]is out,[^\W\d_]is in.\Wwith nouflag is still ASCII-negated, so this is approximate; if you need real Unicode categories, this is the tool you should not prototype in, and the page above showingPattern Validis not evidence that it works. - End of string:
$, not\Zor\z. With themflag,$matches before each newline — so if you want true end-of-string underm, you have left the tool without a way to say it. - Case-insensitivity: the
icheckbox, never(?i). - Atomic groups / possessive quantifiers: not available. Simulate with lookahead, or test that idea somewhere with the
vflag. - Never read "Pattern Valid" as "Pattern does what I meant." Put a test string in that must match, and one that must not. That two-line habit catches every row in the table above.
Limits of what I ran
The Node harness cannot set the checked property on the flag checkboxes — the DOM stub returns a proxy for unknown properties, so every run here executed with flags gims, which flagsDisplay confirms. The rows above whose behaviour depends on g (a* on bbb, \d+ on ١٢٣123, (a)|(b) on ab) were therefore measured in global mode; the table's (?<y>\d{4}) and (?>a*)b rows were driven the same way. The validation results and the new RegExp semantics they turn on do not depend on the flags, but I have not re-run any of them with all four checkboxes cleared in a real browser, so I am not claiming what they look like with no flags set.
How to re-run this
Everything above came from the shipped runRegex, loaded unchanged from the deployed file by the repo's fuzz harness:
node -e 'process.env.FUZZ_APP_ROOT="/tmp/fuzzroot";
const {loadApp}=require("./scripts/fuzz/load.js");
const A=loadApp("regexcalc",["runRegex"]).api;
A.__setInput("regexPattern","\\p{L}+");
A.__setInput("testInput","héllo world");
A.runRegex();
console.log(A.__readOutput("regexStatus"));
console.log(A.__readOutput("matchOutput"));'
# → Pattern Valid
# → No matches found in test string.
The __setInput("matchOutput", ...) trap: this handler writes results with innerHTML, which the harness stores under a separate key. Clearing the output element first by setting it to "" makes __readOutput return your blank instead of the write. Set the inputs and run; do not pre-clear the output.