Advanced Patterns advanced dynamic react css-in-js

Dynamic IDs and Generated Classes

React, Angular, Vue and CSS-in-JS generate ids and class names that change on every build. Learn to spot them, and the partial-match and anchor strategies that survive them.

Dynamic IDs and Generated Classes

Modern front ends generate attributes. Emotion and styled-components produce classes like css-1x2y3z. Angular Material numbers its inputs mat-input-3. React Select emits react-select-2-input. Some builds hash everything: _button_4f2ba_7. A locator that copies one of these values works today and fails after the next deploy.

Recognising a generated value

Treat a value as unstable if it has any of these traits:

TraitExamples
Hex or base-36 hashcss-1x2y3z, sc-bdVaJa, _root_8f3a1_2
Trailing countermat-input-3, react-select-2-input, radix-:r1:
Random-looking tokenid="yui_3_17_2_1_1650000000000_123"
Framework prefix plus numberember123, ext-gen42, ui-id-7
Utility-only classesflex items-center px-4 py-2 (Tailwind)

Utility classes are stable but not meaningful: px-4 is on hundreds of elements. They fail the uniqueness test rather than the stability test.

Strategy 1: use a different attribute

Before writing a clever XPath, look for something the framework did not generate:

//input[@name='email']                name is set by the developer
//button[@aria-label='Close dialog']  accessibility labels are stable
//*[@data-testid='submit']            test ids exist precisely for this
//input[@type='password']             semantic attributes
//a[@href='/account']                 routes change rarely

Nine times out of ten there is a stable attribute nearby.

Strategy 2: match the stable part

When the attribute mixes a stable stem with a generated suffix, match only the stem:

<input id="mat-input-3" placeholder="Email">
<div class="css-1x2y3z-Control">
<button id="btn-save-8f3a">Save</button>
//input[starts-with(@id, 'mat-input-')]       still not unique, combine with more
//div[contains(@class, '-Control')]           library suffix is stable
//button[starts-with(@id, 'btn-save')]        stem is stable

Combine with context to reach uniqueness:

//label[normalize-space()='Email']/following::input[starts-with(@id, 'mat-input-')][1]
//div[contains(@class, '-Control')][.//input[@name='country']]

Strategy 3: anchor to something stable and walk

Generated markup usually still has human-authored text or structure somewhere. Anchor on that and navigate:

//h3[normalize-space()='Shipping address']/following::input[@name='postcode'][1]
//label[contains(., 'Remember me')]/preceding-sibling::input[@type='checkbox']
//*[@data-testid='cart-row'][.//span[normalize-space()='USB-C Hub']]//button[contains(., 'Remove')]

See Anchoring to Stable Context for the full technique.

Strategy 4: ignore the attribute entirely

Sometimes the right answer is to match on role and text and let the generated attributes fall away:

//button[normalize-space()='Save']
//*[@role='tab'][normalize-space()='Billing']
//li[@role='option'][normalize-space()='Germany']

Roles come from ARIA and are stable across styling changes. This is also the direction Playwright and Testing Library push you in.

Avoiding the trap: “Copy XPath” in DevTools

Chrome’s Copy XPath produces something like //*[@id="mat-input-3"] or /html/body/div[2]/div/main/form/div[3]/input. Both are fragile: the first depends on a counter, the second on layout. Treat the copied value as a starting point for locating the element, then write your own.

Class matching that does not over-match

contains(@class, 'btn') also matches btn-danger, btn-outline and subtn. When the class name matters, match the token:

//button[contains(concat(' ', normalize-space(@class), ' '), ' btn ')]

When to ask for test ids

If you find yourself writing starts-with and following:: chains for the same component in several tests, that is the signal to ask the front-end team for a data-testid. One attribute replaces a dozen fragile locators, and most frameworks strip test ids from production bundles if that is a concern.

Try It Yourself

Open in Playground →

The Dynamic IDs sample uses generated ids and classes. Click elements to see which suggestions are rated stable.

Next Steps

  1. Anchoring to Stable Context - Walking from stable landmarks
  2. Forms and Labels - Finding inputs through their labels
  3. Locator Strategy in 2026 - Where XPath fits alongside role and test-id locators