Skip to main content
hank-builds.com

HTML Tag regex

Matches paired HTML tags with content.

/<([a-z][a-z0-9]*)\b[^>]*>.*?</\1>/gi

What it matches

An opening tag, its attributes, the content, and a matching closing tag. The backreference \1 is the point: it captures the tag name and requires the same name to close, so <div>…</div> matches but <div>…</span> does not. The lazy .*? stops at the first valid close rather than the last.

Railroad diagram

Read left to right. Every path through the diagram is a string the pattern accepts.

"<" Group [a-z] [a-z0-9] \b [^>] ">" . "<" \/ \1 ">"

Drag to pan. Ctrl+scroll to zoom.

Breakdown

gFind all matches instead of stopping after the first
iLetters match both upper and lower case
<Literal "<"
[a-z]Match one of: Literal "a"–Literal "z"
[a-z0-9]*Match one of: Literal "a"–Literal "z", Literal "0"–Literal "9", zero or more
\bWord boundary
[^>]*Match not one of: Literal ">", zero or more
>Literal ">"
.*?Any character, zero or more (lazy)
<Literal "<"
\/Literal "/"
\1Backreference to group #1
>Literal ">"

Test cases

Matches

  • <div class="test">Hello</div>
  • <p>World</p>
  • <span id="x">y</span>

Does not match

  • <br />
  • <div>unclosed
  • plain text

Caveats

Fine for a quick scan, wrong for parsing. Nesting defeats it: the lazy quantifier closes <div><div>a</div></div> at the inner tag. It cannot see comments or CDATA, and content spanning a newline needs the s flag. HTML is not a regular language; for anything real, use a parser.

More web patterns

Test this against your own input in the regex tester, browse the full pattern library, or check the syntax cheat sheet.