DEV Community

SEN LLC
SEN LLC

Posted on

A Regex Cheatsheet Where Every Entry Has Its Own Live Tester

A Regex Cheatsheet Where Every Entry Has Its Own Live Tester

Most regex cheatsheets show syntax but make you go to regex101 to actually test anything. In this one, every entry card has an editable pattern input, a test string textarea, and real-time match highlighting. 45 syntax entries across 9 categories plus 20 ready-made patterns (email, URL, IPv4, JP phone numbers, UUID, etc.).

Regex is the kind of thing you look up constantly and still get wrong. Is it \d or \D for non-digit? Does . match newlines? Is {3,} the same as {3} plus +? A cheatsheet that lets you test each construct immediately, on your own input, is much more useful than a static table.

🔗 Live demo: https://sen.ltd/portfolio/regex-cheatsheet/
📦 GitHub: https://github.com/sen-ltd/regex-cheatsheet

Screenshot

Features:

  • 45 syntax entries in 9 categories
  • 20 common patterns (email, URL, phone JP/intl, UUID, hex color, IPv4, etc.)
  • Live tester on every entry (edit pattern and test text)
  • Match highlighting with match count
  • Flag validation
  • One-click copy
  • Japanese / English UI
  • Zero dependencies, 47 tests

Safe regex compilation

User input is unpredictable. A syntactically invalid regex crashes new RegExp(), so all compilation goes through a wrapper:

export function safeCompile(pattern, flags = '') {
  try {
    return { regex: new RegExp(pattern, flags), error: null };
  } catch (e) {
    return { regex: null, error: e.message };
  }
}
Enter fullscreen mode Exit fullscreen mode

The UI checks result.error and shows a red message instead of crashing.

Finding and highlighting matches

export function findMatches(regex, text) {
  const matches = [];
  if (!regex) return matches;
  const flags = regex.flags.includes('g') ? regex.flags : regex.flags + 'g';
  const r = new RegExp(regex.source, flags);
  let m;
  while ((m = r.exec(text)) !== null) {
    matches.push({
      start: m.index,
      end: m.index + m[0].length,
      match: m[0],
      groups: m.slice(1),
    });
    if (m[0].length === 0) r.lastIndex++; // avoid infinite loop on zero-width matches
  }
  return matches;
}
Enter fullscreen mode Exit fullscreen mode

Two subtleties:

  1. The g flag is forced so exec advances. Without it, the loop runs forever on the first match.
  2. Zero-width matches (like ^ or \b) don't advance lastIndex automatically, so we bump it manually.

Highlighting as segments

To render matches, we split the text into alternating "match" and "non-match" segments:

export function highlightMatches(text, matches) {
  const segments = [];
  let cursor = 0;
  for (const m of matches) {
    if (cursor < m.start) {
      segments.push({ text: text.slice(cursor, m.start), isMatch: false });
    }
    segments.push({ text: m.match, isMatch: true });
    cursor = m.end;
  }
  if (cursor < text.length) {
    segments.push({ text: text.slice(cursor), isMatch: false });
  }
  return segments;
}
Enter fullscreen mode Exit fullscreen mode

The UI renders each segment as a span with optional highlight class. This gives exact visual placement of every match without needing to escape HTML manually.

Common patterns

20 patterns covering the cases developers look up weekly:

  • Email: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
  • JP Phone: ^0\d{1,4}-?\d{1,4}-?\d{4}$
  • IPv4: ^(\d{1,3}\.){3}\d{1,3}$
  • UUID: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
  • Hex color: ^#(?:[0-9a-fA-F]{3}){1,2}$
  • YYYY-MM-DD: ^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$

Each has an example string to try it on, so you can see it working before copying.

Series

This is entry #48 in my 100+ public portfolio series.

Top comments (0)