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
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.