RegExp v and d Flags

A character class like [a-z] has always been a flat bag of characters. You can list ranges, you can negate the whole thing with ^, and that is about the end of it. There is no way to say “letters, but not vowels” or “Greek script, but only the letters” without spelling out every excluded character by hand. And once you do get a match, the result tells you what matched but is stingy about where — you get the matched text, one lonely .index for the whole thing, and nothing about each capture group.

Two flags close those gaps. The v flag turns character classes into a small algebra: union, intersection, difference, and even sets of whole strings. The d flag attaches precise [start, end] positions to every part of a match. They solve unrelated problems, but both are recent, both are worth reaching for, and both are safe to use in 2026.

/…/v/…/dset algebra in classes[\p{L}&&[a-z]][\p{Letter}–\q{no}]ES2024 · replaces upositions for every groupmatch.indices[0]match.indices.groupsES2022 · widely available
Two independent regex flags. v reshapes what a character class can express; d enriches what a successful match reports back.

The v flag: character classes that do algebra

The v flag is a successor to the older u (Unicode) flag. Both make a regex Unicode-aware — matching by code point rather than by UTF-16 code unit, and enabling \p{…} property escapes. But v goes further. It reinterprets the inside of […] as a proper set that you can combine with other sets.

The catch, stated up front: u and v are mutually exclusive. They interpret the same source in incompatible ways, so a regex carrying both throws a SyntaxError immediately.

new RegExp("[a-z]", "uv"); // SyntaxError: Invalid flags supplied to RegExp constructor

You check for the v flag the same way you check any flag — through the boolean accessor on the regex:

const re = /[\p{Letter}]/v;
re.unicodeSets; // true
re.flags;       // "v"

Everything a u-mode regex could do, a v-mode regex still does. The new powers all live inside the square brackets.

Union: the familiar behaviour, now spelled out

Listing things inside a class has always meant “any of these” — that is union. Under v the idea is the same, but the syntax is looser and reads better: you can put ranges, property escapes, nested classes, and (new) string literals side by side.

/[\p{Emoji}\p{ASCII}0-9]/v; // emoji OR ASCII OR a digit

Nothing surprising yet. The interesting part is that a class is now a value you can subtract from and intersect with.

Intersection with &&

A&&B inside a class matches a character only if it belongs to both A and B. This is exactly the Venn-diagram overlap you would draw for two circles.

// letters that are also lowercase ASCII
/[\p{Letter}&&[a-z]]/v.test("k"); // true
/[\p{Letter}&&[a-z]]/v.test("K"); // false — uppercase isn't in [a-z]
/[\p{Letter}&&[a-z]]/v.test("5"); // false — not a letter

// ASCII whitespace only, not the exotic Unicode spaces
const asciiSpace = /[\p{White_Space}&&\p{ASCII}]/v;
\p{Letter}[a-z]K Ω é5 #a k zbothmatches only the shaded overlap
Intersection [\p{Letter} && [a-z]] keeps only the elements in the overlap: characters that satisfy both sets at once.

If that overlap picture reminds you of Set.prototype.intersection, it should — it is the same operation, one level down at the character-class layer. The Set methods do it for runtime collections; v does it for the alphabet a pattern accepts.

Difference with --

A--B matches characters in A that are not in B. This is the “letters, but not vowels” case that used to force you to enumerate consonants.

// any letter except the five ASCII vowels
const consonantish = /[\p{Letter}--[aeiou]]/v;
consonantish.test("b"); // true
consonantish.test("a"); // false

// every Unicode decimal digit that isn't a plain ASCII 0-9
/[\p{Decimal_Number}--[0-9]]/v.test("٤"); // true (Arabic-Indic four)
/[\p{Decimal_Number}--[0-9]]/v.test("4"); // false
\p{Letter}b c d f gh k m za e io uremovedthe violet area minus the punched-out hole
Difference [\p{Letter} -- [aeiou]] subtracts the second set from the first: everything in Letter minus the vowels punched out.

Type a single character below and watch three v-mode classes decide whether it belongs. Each row is one set expression; edit the code to invent your own intersection or difference.

interactiveTest one character against v-mode set algebra

String literals with \q{…}

Here is the genuinely new capability. Until now a character class matched exactly one character. The \q{…} escape lets a class contain whole strings as elements, separated by |.

/[\q{a|bc|def}]/v; // matches the string "a", or "bc", or "def"

On its own that looks like a clumsy way to write (?:a|bc|def). The payoff is that these multi-character elements now participate in the set algebra. You can subtract a specific emoji sequence, or intersect a property set against a list of allowed strings.

// national-flag emoji, but not the flag of one specific region
const flags = /[\p{RGI_Emoji_Flag_Sequence}--\q{🇦🇶}]/v; // everything except Antarctica

\q{…} is where v stops being “a nicer u” and becomes something u simply cannot express. A u-mode class is a set of code points; a v-mode class is a set of strings, most of which happen to be one character long.

The demo below anchors a \q{…} set so the whole input has to be a single element of it. Notice that "do" fails even though it is a prefix of "dog" — a string element is all-or-nothing, and the difference operator lets you punch one word back out.

interactiveWhole-string membership with \q{…}

Why v is emoji-safe

Many emoji are not single code points. A flag is two regional-indicator symbols; 👨‍👩‍👧 is several people glyphs stitched together with zero-width joiners. Under u, a bracket class treats each of those underlying code points as a separate class member, which quietly breaks negation and intersection around emoji. Under v, the property \p{RGI_Emoji} matches the recommended-for-interchange emoji as whole units, sequences included, because the class can hold multi-code-point strings.

👨‍👩‍👧one emoji, on screen👨ZWJ👩ZWJ👧/[\p{Emoji}]/u→ matches one code point at a time (splits it)/\p{RGI_Emoji}/v→ matches the whole sequence as one element
A ZWJ emoji sequence is several code points. A u-mode class sees the pieces; a v-mode \p{RGI_Emoji} matches the sequence as one element.

Escaping is stricter under v

Because &, -, and a few other characters now carry meaning inside a class, v mode is fussier about literals. A doubled punctuation like &&, !!, ##, or ~~ inside a class is reserved and must be escaped if you mean it literally. The upside is fewer silent surprises; the cost is that a pattern copied from a u regex might need a backslash or two.

/[a&&b]/v; // SyntaxError — && is the intersection operator
/[a\&\&b]/v.test("&"); // true — escaped, so a literal ampersand

The d flag: exact positions for every group

Switch problems. A match under a normal regex hands back the matched text and a single .index marking where the whole match started. If you want to know where a specific capture group landed — to highlight it, to splice around it, to map it back to a source location — you are on your own, usually re-searching the string by hand.

The d flag makes the engine record that for you. It adds an indices property to the match array, and hasIndices reports whether the flag is on.

const re = /(\d{4})-(\d{2})/d;
const m = re.exec("ship 2026-07 now");

m[0];          // "2026-07"      — the full match
m.index;       // 5             — where the full match starts
m.indices[0];  // [5, 12]       — [start, end] of the full match
m.indices[1];  // [5, 9]        — group 1, the year
m.indices[2];  // [10, 12]      — group 2, the month
re.hasIndices; // true

Each entry in indices is a two-element array [start, end], where end is exclusive — the same half-open convention as String.prototype.slice. So str.slice(...m.indices[1]) gives you exactly that group’s text, no arithmetic required. indices[0] is the whole match; indices[1] onward line up with m[1], m[2], … one-to-one.

ship 2026-07 now05101112indices[0] = [5, 12]indices[1] = [5, 9]indices[2] = [10, 12]end index is exclusive — slice(start, end) returns the group text
With the d flag, /(\d{4})-(\d{2})/d over 'ship 2026-07 now' reports a [start, end] pair for the whole match and for each capture group.

Because every string method that matches internally calls exec, the flag flows through them too. String.prototype.matchAll with a /d regex yields match objects that each carry their own indices, which is what makes it practical for scanning a whole document.

Named groups land on indices.groups

If your pattern uses named capture groups, indices grows a groups object that mirrors match.groups, but with position pairs instead of text.

const re = /(?<year>\d{4})-(?<month>\d{2})/d;
const m = re.exec("2026-07");

m.groups.year;          // "2026"
m.indices.groups.year;  // [0, 4]
m.indices.groups.month; // [5, 7]
Reading indices.groups step by step
1/4
Variables
hasIndices=true
The d flag is on, so any match from this regex will carry an indices property.

Unmatched optional groups report undefined

Positions only exist for groups that actually participated. An optional group that did not match gives undefined in indices, exactly as it gives undefined in the match array — so guard before spreading.

const re = /(a)|(b)/d;
const m = re.exec("a");

m.indices[1]; // [0, 1]   — the (a) branch matched
m.indices[2]; // undefined — the (b) branch never ran

A realistic use: precise highlighting

The classic reason to reach for d is turning a match into styled output without re-scanning. Say you want to wrap the month portion of every date in a <mark>. With indices you know its exact span, so you can rebuild the string in one pass.

function highlightMonths(text) {
  const re = /(?<year>\d{4})-(?<month>\d{2})/dg;
  let out = "";
  let cursor = 0;

  for (const m of text.matchAll(re)) {
    const [start, end] = m.indices.groups.month;
    out += text.slice(cursor, start);          // text before the month
    out += `<mark>${text.slice(start, end)}</mark>`;
    cursor = end;
  }
  return out + text.slice(cursor);
}

highlightMonths("from 2026-07 to 2027-01");
// "from 2026-<mark>07</mark> to 2027-<mark>01</mark>"

No second search, no counting characters, no fragile assumptions about where inside the match the group sits. The engine already knew; the d flag just let it tell you. This is the pattern behind syntax highlighters, linters that underline a sub-token, and any tool that maps a match back to a location in source.

Here that same function runs live. Type any text containing YYYY-MM dates and only the month digits get wrapped, using indices.groups.month to find the exact span.

interactiveHighlight just the month, using indices.groups

Support and when to use them

Both flags are shipped and standardized — no polyfill, no transpile step for the language features themselves (though a build tool can still down-level v-flag syntax if you target ancient engines).

ChromeFirefoxSafari/dES2022908815widely available/vES202411211617Baseline (2023)min version shown · d since 2021, v since 2023
Shipping status as of 2026. The d flag has been broadly deployable for years; the v flag is a couple of years younger but now clears the widely-available bar.

The d flag (ES2022) landed in Chrome 90 and Firefox 88 in April 2021 and in Safari 15 that September, so it has been comfortably deployable for years. The v flag (ES2024) arrived with Chrome 112 in spring 2023, Firefox 116 that August, and Safari 17 in September 2023 — Baseline “newly available” from that point, and by 2026 it has aged past the widely-available threshold. Use v freely in evergreen and current-mobile targets; if you must support a browser older than late 2023, keep a u-flag fallback or transpile.

Summary

  • v (unicodeSets, ES2024) upgrades u: character classes become sets you can combine. It is mutually exclusive with u — using both throws.
  • Inside a v class: && is intersection, -- is difference, adjacency is union, and \q{…} introduces multi-character string elements separated by |.
  • Mix operators only across nesting levels: [[\p{L}&&[a-z]]--[aeiou]], one operator per bracket level. Doubled punctuation like && must be escaped to be literal.
  • \p{RGI_Emoji} under v matches emoji sequences as whole units, which u-mode classes cannot do because they see individual code points.
  • d (hasIndices, ES2022) adds match.indices: indices[0] is the whole match’s [start, end], and indices[1..] line up with the capture groups. end is exclusive, so slice(...pair) gives the text.
  • Named groups appear on indices.groups, keyed like match.groups. Groups that did not participate are undefined — guard before spreading into slice.
  • Both flags are standardized and shipped: d has been widely available since 2021, v since 2023. Reach for v as the default in new code.