PatternRegex

Match an HTML tag

Find opening and closing HTML tags in text — for stripping, counting or spotting markup — and the famous reason regex must never parse HTML.

Last updated

Patterng
<\/?[a-zA-Z][\w-]*(?:\s[^<>]*)?>
Open in Regex Tester & Builder

Matches

  • <div>
  • </p>
  • <img src="logo.png">
  • <custom-element>

Doesn’t match

  • <>
  • <1div>
  • a < b

Why it’s written this way

An optional slash covers closing tags, the name must start with a letter (so a stray < in "a < b" can't start a match), and the optional attribute chunk requires a leading space then anything that isn't an angle bracket — which is what keeps the match from running past the tag's own >.

The [\w-]* name body accepts custom elements like <my-widget> for free.

Edge cases to know

  • →This finds tag-shaped substrings; it does not parse HTML. Nesting, comments, CDATA and attribute values containing > all defeat any regex — for real parsing use a DOM parser.
  • →Attribute values holding an angle bracket (title="a>b") end the match early at that >.
  • →It matches tags inside code samples and escaped text too — strip or segment first if that matters.

Part by part

<\/?
An opening angle bracket with an optional slash, so the pattern matches both start tags and end tags in one expression.
[a-zA-Z]
A required first letter. Tag names cannot begin with a digit, and requiring a letter is what stops this matching a less-than sign used as arithmetic.
[\w-]*
The rest of the tag name: word characters and hyphens, which covers custom elements like my-widget as well as standard tags.
(?:\s[^<>]*)?
The optional attribute block. It requires whitespace before any attributes, then accepts anything that is not an angle bracket.

Common mistakes

  • <.*>

    The greedy dot matches everything from the first angle bracket to the last one on the line, so a paragraph containing two tags collapses into a single match that swallows the text between them.

    <\/?[a-zA-Z][\w-]*(?:\s[^<>]*)?>
  • <[^>]*>

    Closer, and still wrong in a specific way: it matches a bare < followed by anything, so the less-than sign in "if a < b then" starts a match that runs to the next > anywhere in the document.

  • <\/?[a-zA-Z][\w-]*(?:\s.*)?>

    Using .* rather than [^<>]* inside the attribute block reintroduces greediness, so an attribute value containing an angle bracket lets the match run past the end of its own tag.

Questions

Can I use this to strip HTML from a string?

For trusted, well-formed markup you control, it is usually adequate. For anything user-supplied it is not: an attribute value containing a quoted angle bracket, or a deliberately malformed tag, will defeat it. Use a parser when the input is untrusted.

Why is parsing HTML with regex so widely discouraged?

Because HTML nests arbitrarily and regular expressions cannot count. A pattern can find individual tags, which is what this one does, but it cannot reliably match a tag to its closing partner or understand the tree that results.

Does it match comments and doctype declarations?

No. Both begin with <! rather than a letter, so the required first letter excludes them — which is usually what you want when you are looking for elements rather than markup furniture.

Does it handle attribute values containing a > character?

No, and this is its sharpest limit. The attribute block stops at the first angle bracket, so an attribute like title="a > b" truncates the match mid-tag. It is the case that most often argues for a real parser.

Related in Patterns