A GTM click trigger the next deploy will not break
Why a copied CSS selector stops matching, which attributes are worth anchoring to, and the Tailwind class that silently makes a selector invalid.
Most GTM work is the same small task on repeat: something on the page has to become an event. You open DevTools, right-click the button, Copy selector, paste it into a Click trigger, publish. Three weeks later the trigger stops firing and nobody notices until the monthly report looks thin.
Why copied selectors rot
- They are positional. "Copy selector" gives you a path like
div:nth-child(3) > div:nth-child(2) > button. One added wrapper anywhere above the button and it matches nothing. - The class names are generated. CSS modules, styled-components and similar tools produce
names like
Button_root__x7f2q, and the hash changes when the file changes. - So are the IDs. Frameworks emit
ember1234, an eight-character hex run, or a plain number — all regenerated on the next render.
What to anchor on, in order
- An ID, if it is a real one. An ID is the shortest possible selector and cannot be ambiguous. Reject it if it starts with a digit, contains a run of eight hex characters, or carries a framework prefix — those are generated, not authored.
- An attribute that exists to be targeted.
data-gtm,data-testid,name,aria-label. Someone put these there on purpose; a redesign changes the styling and leaves them alone. One caveat: if the value contains{or a quote, you are looking at an un-rendered template, and it will not be the same tomorrow. - Classes, sparingly. Two or three meaningful ones, never the state classes —
hover,active,focus,valid,invalidcome and go while the user interacts with the page, so a selector built on them matches only sometimes.
The Tailwind trap
Tailwind class names contain characters that mean something in CSS. hover:bg-blue-500 has a
colon; md:w-1/2 has a colon and a slash. Written into a selector unescaped, the result is not a
selector that fails to match — it is invalid CSS, and GTM's Click trigger simply never fires. There is no
error anywhere.
Escaped, it is .hover\:bg-blue-500, and it works. This is the single most common reason a
hand-written selector does nothing on a modern site.
Stop as soon as it is unique
The best selector is the shortest one that matches exactly one element on the page. Every ancestor you add
past that point is another element that can be restructured. Checking the match count as you build — and
stopping the moment it reaches one — produces something far more durable than a full path from
body.
The element you clicked is not the element GTM sees
You click a button; the browser reports the deepest element under the cursor, which is usually the
<span> holding the label. GTM's Click Element is that span. A trigger written against the
button therefore fires only when someone happens to click the padding around the text.
The fix is to climb to the nearest genuinely clickable ancestor — an <a>, a
<button>, or something with role="button" — and anchor there. GTM's
Click Element variable supports matches CSS selector, which tests the clicked
element against the selector, and a descendant selector covers the children automatically.
Tag Master builds the selector for you: click the element in the page and it climbs to the clickable parent, prefers the stable attributes over generated classes, escapes what needs escaping, and stops at the shortest unique path — then hands you the trigger condition and a variable to copy.
Getting the value out
A click trigger usually needs a number as well: the price on the card you clicked. That means a Custom
JavaScript variable that finds the price element relative to the clicked one and parses the text — which is
where locale bites. 1.648,25 TL and $1,648.25 both have to become
1648.25, and a naive parseFloat turns the first into 1. Strip
everything that is not a digit or a separator, then decide which separator is the decimal one by looking at
what follows it.
Verify before you publish
- Click the element and confirm exactly one match on the page.
- Click a sibling — a different product card, the second button in the row — and confirm it does not match, unless you meant it to.
- Click the text inside the element, not just its edge. This is the test that catches the deepest-element problem.
- Reload and repeat. Anything that only matched before the reload was matching generated markup.
Related guides
- Google Tag Gateway: checking that it actually works
- When GA4 says the traffic came from somewhere else
- Tracking inside an embedded widget
Try it on your own site
Tag Master is free, needs no account, and collects no data.
Add to Chrome — Free