Selector Cookbook
Patterns for finding stable CSS selectors, the DevTools workflow that makes it fast, and the specific anti-patterns that produce fragile experiments.
The single most common reason a CSS variant doesn’t work is a bad selector. The element you wanted to change isn’t matched, or the selector matches too broadly and changes things you didn’t intend. This guide is the cookbook for getting selectors right.
The DevTools Workflow
Before writing any selector, do this:
- Open the page in Chrome or Firefox.
- Right-click the element → Inspect (
F12). - The DOM panel highlights the element. Read its
classandid. - Switch to the Console tab and test:
document.querySelectorAll(".your-selector") - The result should match exactly what you want — no more, no less.
This 30-second loop catches 90% of selector bugs before you ever write the variant.
Selector Priority — What to Reach for First
In order of preference:
1. ID selectors
#add-to-cart { ... }
IDs are unique on a well-formed page. If the element has an ID, use it. The selector is short, fast, and unambiguous.
2. Data attributes
[data-testid="add-to-cart"] { ... }
[data-product-cta] { ... }
Data attributes are often added by developers specifically for testing or analytics. They’re stable across redesigns because they’re not tied to visual styling — when CSS classes get refactored, data attributes usually survive.
3. Descriptive BEM-style classes
.product-form__cta { ... }
.product-hero__primary-cta { ... }
Class names that describe what the element is (BEM-style: .block__element--modifier) tend to be stable. They survive style refactors because the structural relationship outlasts any specific visual treatment.
4. Tag + class compounds
button.btn-primary { ... }
form[action="/cart/add"] button[type="submit"] { ... }
When a class alone is too generic, combine it with the tag or other attributes for specificity.
Selectors to Avoid
These work — until they break.
Position-based
.products > div:nth-child(3) { ... } /* DON'T */
ul li:first-child { ... } /* DON'T */
These break the moment the DOM is reordered. Theme updates, CMS edits, or even sorting plugins can shift child positions.
Generic single-token classes
.button { ... } /* DON'T */
.text { ... } /* DON'T */
.container { ... } /* DON'T */
These match dozens of elements across the page. You’ll change things you didn’t mean to, and your variant looks broken in unpredictable places.
Auto-generated class names
.css-1a2b3c4 { ... } /* DON'T */
.MuiButton-root-129 { ... } /* DON'T */
.sc-eclbeb-3 { ... } /* DON'T */
These come from CSS-in-JS frameworks (Emotion, styled-components) or build tools that hash class names. They regenerate on every build, so a selector that works today fails tomorrow.
Tag-only selectors
button { ... } /* DON'T */
h1 { ... } /* DON'T */
a { ... } /* DON'T */
These match every instance of the tag on the page. Almost always broader than you want.
Recipes by Element Type
Quick patterns for the elements most often tested.
Add-to-cart button (Shopify)
.btn-primary,
.product-form__buttons button[type="submit"],
form[action*="/cart/add"] button[type="submit"],
[data-testid="add-to-cart"] {
/* your variant here */
}
The comma-separated list is defensive — different Shopify themes use different conventions. Hit several plausible selectors and one will land.
Add-to-cart button (HTML/generic)
.add-to-cart,
.product__cta,
button[data-action="add-to-cart"] {
/* your variant */
}
Inspect the actual element to find the right selector for your specific site.
Product hero headline
.product__title,
h1.product-hero__heading,
[data-product-title] {
/* variant */
}
Hero image
.product__media img,
.product-hero__image img,
[data-product-image] {
/* variant */
}
Promo banner / announcement bar
.announcement-bar,
.promo-banner,
[data-section-type="announcement"] {
/* variant — often just `display: none !important` to test removing it */
}
Newsletter signup form
form[action*="/contact"],
.newsletter-form,
[data-newsletter-form] {
/* variant */
}
Specificity Escalation
When a selector matches but the styles don’t apply, theme styles are winning the cascade. Escalate in this order:
Add !important
.btn-primary { background: orange !important; }
Almost always sufficient. !important overrides any non-important rule regardless of specificity. Use it freely in variant CSS — variant CSS is short-lived and tightly scoped, exactly when !important is appropriate.
Add a parent selector
.product-form .btn-primary { background: orange !important; }
Increasing the selector’s depth increases specificity. Useful when other elsewhere on the page also use .btn-primary and you only want to change this one.
Repeat the class name
.btn.btn { background: orange !important; }
.btn.btn.btn { background: orange !important; }
Each repetition adds class-level specificity. Reach for this only when even !important is being beaten by an inline style="..." attribute.
Switch to JS
If the theme sets style="..." on an element via JavaScript at runtime, no amount of CSS specificity wins. Use a JS variant to remove or override the inline style directly.
Multi-Selector Robustness
A defensive habit: when you’re not sure which selector your theme actually uses, list several plausible ones:
.btn-primary,
.btn--primary,
.button-primary,
[data-testid="primary-cta"] {
background: orange !important;
}
This costs nothing if the extra selectors don’t match. It saves you from a broken variant if the theme’s class naming is inconsistent or you’ve guessed wrong.
Testing Selectors Against Multiple Pages
A selector that works on the home page might not work on a product page or a collection page. If your URL targeting hits multiple pages, sanity-check the selector on each:
- Visit the home page → DevTools → run
document.querySelectorAll(".your-selector"). - Visit a product page → repeat.
- Visit a collection page → repeat.
If the count differs unexpectedly, either narrow the targeting or use multiple selectors.
Selectors and Responsive Design
Some elements have different markup at different breakpoints:
<!-- Desktop -->
<button class="btn-primary desktop-only">Buy Now</button>
<!-- Mobile (rendered separately) -->
<button class="btn-primary mobile-cta">Buy Now</button>
A selector targeting .btn-primary would hit both, which is usually what you want. If you want to test only on desktop or only on mobile, wrap the variant CSS in a @media query — see CSS Variants.
Advanced: Attribute Selectors
For elements without convenient classes, attribute selectors can rescue you:
/* Match form submit button regardless of class */
form button[type="submit"] { ... }
/* Match "buy now" buttons by content */
button:has(span:contains("Buy Now")) { ... } /* :has() and :contains() — modern browsers */
/* Match link by href */
a[href="/checkout"] { ... }
/* Match input by name */
input[name="email"] { ... }
Attribute selectors are stable when classes aren’t, but they can be slow on very large pages. Use them when class selectors fail.
When to Give Up on CSS
Sometimes the page is hostile to CSS-only changes. Signs:
- The element you want to change has no stable selector at all (auto-generated classes, no IDs, no data attributes, no parents you can anchor to).
- The element is inside a Shadow DOM (CSS doesn’t pierce Shadow boundaries).
- The element is rendered after page load by JavaScript and your CSS applies before it exists.
- The text content is what you want to change (CSS can’t change text).
For these cases, switch to a JS variant — JavaScript can manipulate any element regardless of selector hostility, can wait for late-rendering elements, and can change text content directly.
Common Mistakes
- Forgetting to test the selector in the console. It costs 10 seconds and saves hours of confusion.
- Trusting Shopify theme docs blindly. Different themes use different class naming. Always inspect the actual element.
- Using too many
!importants as a default. They’re appropriate in variant CSS but overuse means you’ve stopped thinking about specificity. - Writing one mega-selector instead of several specific ones. A single rule with 12 comma-separated selectors is hard to read; usually better to split.
Next Steps
- Apply the selectors in your CSS variants: CSS Variants.
- When CSS isn’t enough, switch to JavaScript: JavaScript Variants.
- Verify variants render correctly across pages: Screenshots and Preview.
Ready to start testing?
Install Split Test Pro and run your first experiment today.