Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

CSS selectors are patterns that target HTML elements so you can apply styles to them. They range from simple element, class, and ID selectors to more powerful tools like combinators, attribute selectors, pseudo-classes, and pseudo-elements. Every CSS rule begins with a selector that tells the browser exactly which elements the following declaration block should style, which makes selectors the foundation of all styling and a core skill for both front-end developers and test automation engineers.
Below, we walk through every category of CSS selector with copy-ready code, explain how specificity resolves conflicts, and show how the same selectors double as locators in automation testing frameworks like Selenium and Playwright.
A CSS selector is the part of a CSS rule that points at the HTML elements you want to style. Every rule is made of two pieces: the selector and the declaration block. The selector comes first and decides which elements are matched; the declaration block, wrapped in curly braces, decides how they look. When the browser parses the stylesheet, it reads the selector, finds every matching element in the DOM, and applies the declarations to them.
/* selector */ /* declaration block */
p {
color: navy;
font-size: 16px;
}In the example above, p is the selector and everything inside the braces is the declaration block. The browser finds every paragraph element on the page and paints its text navy at 16 pixels. Selectors can be far more precise than a single tag name, letting you target elements by class, ID, attribute, relationship to other elements, state, or even a generated sub-part of an element.
CSS gives you several families of selectors. The list below summarizes them, and the sections that follow show the syntax for each.
The universal selector, written as an asterisk, matches every element in the document. It is most often used in CSS resets to strip default margins and padding so every browser starts from the same baseline.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}The type selector targets elements by their HTML tag name. It matches every instance of that tag, making it ideal for setting base styles such as default link colors or paragraph spacing.
a {
color: #0d6efd;
text-decoration: none;
}
li {
line-height: 1.6;
}The class selector begins with a period and matches every element carrying that class attribute. Classes are reusable across many elements, which is why they are the workhorse of component-based styling.
.btn {
padding: 10px 20px;
border-radius: 6px;
}
/* Combine a tag and a class for tighter targeting */
button.btn-primary {
background: #0d6efd;
color: #fff;
}The ID selector begins with a hash and targets the single element whose id attribute matches. Because an ID should be unique on a page, this selector is the most specific way to reach one element.
#main-header {
position: sticky;
top: 0;
background: #fff;
}Attribute selectors match elements based on the presence or value of an HTML attribute. They are perfect for styling form fields by type or links by destination, and they support partial-match operators.
/* Exact value */
input[type="text"] {
border: 1px solid #ccc;
}
/* Starts with */
a[href^="https"] {
color: green;
}
/* Contains */
a[href*="lambdatest"] {
font-weight: bold;
}
/* Ends with */
a[href$=".pdf"] {
color: crimson;
}A grouping selector applies one declaration block to several selectors at once, separated by commas. This keeps stylesheets DRY when multiple elements should share the same look.
h1, h2, h3 {
font-family: "Inter", sans-serif;
color: #1a1a1a;
}Combinators select elements based on their relationship in the DOM tree. There are four: the descendant combinator (a space), the child combinator (>), the adjacent sibling combinator (+), and the general sibling combinator (~).
/* Descendant: any li inside a nav */
nav li {
display: inline-block;
}
/* Child: only direct li children of ul */
ul > li {
margin-bottom: 8px;
}
/* Adjacent sibling: the p immediately after an h2 */
h2 + p {
margin-top: 4px;
}
/* General sibling: every p after an h2, same parent */
h2 ~ p {
color: #555;
}Pseudo-classes, written with a single colon, select elements in a particular state or structural position. They let you respond to user interaction and target elements by their place among siblings without adding extra classes.
/* Interaction state */
a:hover {
text-decoration: underline;
}
/* The first child of its parent */
li:first-child {
font-weight: bold;
}
/* Every odd row in a table */
tr:nth-child(odd) {
background: #f7f7f7;
}Pseudo-elements, written with a double colon, style a specific part of an element or inject generated content that does not exist in the HTML. The ::before and ::after pseudo-elements are commonly used for decorative icons and clearfix tricks.
.note::before {
content: "Note: ";
font-weight: bold;
}
blockquote::after {
content: " ";
}
/* Style only the first line of a paragraph */
p::first-line {
font-variant: small-caps;
}CSS has added powerful functional pseudo-classes that take other selectors as arguments. The :not() negation pseudo-class matches elements that do not match its selector list, :is() shortens long grouped selectors, and :has() — often called the "parent selector" — matches an element based on what it contains. These modern selectors remove a lot of extra classes and, in the case of :has(), solve a problem CSS could not express for years.
/* Negation: every button that is NOT disabled */
button:not(:disabled) {
cursor: pointer;
}
/* :is() collapses a long grouped selector */
:is(h1, h2, h3) a {
color: inherit;
}
/* :has() — style a card only if it contains an image */
.card:has(img) {
padding-top: 0;
}
/* Parent selector: an h1 immediately followed by a p */
h1:has(+ p) {
margin-bottom: 0;
}When two or more rules target the same element with conflicting declarations, the browser uses specificity to decide which one wins. Specificity is calculated as a four-part score based on the kinds of selectors used. A higher score beats a lower one; if scores tie, the rule that appears later in the stylesheet wins.
For example, #nav .link scores 0,1,1,0 and beats .link at 0,0,1,0, so the first rule wins even though the second is shorter. The universal selector and combinators add no specificity. The !important flag overrides normal specificity entirely, but it should be a last resort because it makes future overrides even harder.
CSS selectors are not just for styling. Automation frameworks like Selenium, Playwright, and Cypress use the very same selector syntax as locators to find elements during a test. Mastering selectors therefore pays off twice: once for clean stylesheets and again for stable, readable tests.
// Selenium (Java) using a CSS selector as a locator
WebElement loginBtn = driver.findElement(By.cssSelector(".login-btn"));
WebElement email = driver.findElement(By.cssSelector("input[name='email']"));
// Target the first item inside a list
WebElement firstItem = driver.findElement(
By.cssSelector("ul.menu > li:first-child"));The same patterns translate directly to JavaScript frameworks. CSS locators are generally faster and easier to read than XPath for most cases, and they map one-to-one onto the selectors developers already write in their stylesheets.
// Playwright (JavaScript) using CSS selectors
await page.click(".login-btn");
await page.fill("input[name='email']", "[email protected]");
// Attribute and pseudo-class locators work the same way
await page.click("a[href$='.pdf']");
await page.locator("tr:nth-child(2)").click();For a deeper dive into using these locators inside a test suite, see our complete guide to CSS selectors in Selenium, review how to locate elements on a web page and interact with them, and explore the Selenium Playground to practice on live pages.
CSS selectors are the patterns that connect your stylesheet to your markup, from the simple element, class, and ID selectors to attribute selectors, combinators, pseudo-classes, and pseudo-elements. Understanding how specificity resolves conflicts keeps your CSS predictable, and because the same syntax powers locators in Selenium and Playwright, strong selector skills make you faster at both styling and test automation. Keep selectors flat and readable, lean on classes over IDs, and validate your CSS-driven UIs across real browsers to ship with confidence.
The three most common are the type (element) selector, which targets by tag name like p; the class selector, written with a dot like .btn; and the ID selector, written with a hash like #header. CSS also adds attribute selectors, combinators, pseudo-classes, and pseudo-elements.
A class selector (.name) can match many elements and is reusable, while an ID selector (#name) should match a single unique element per page. IDs also carry higher specificity, so an ID rule overrides a conflicting class rule on the same element.
Specificity is scored as inline styles, then IDs, then classes, attributes, and pseudo-classes, then elements and pseudo-elements. The rule with the higher score wins. When two rules tie, the one declared later applies, and the !important flag overrides normal specificity.
A pseudo-class (single colon, like :hover or :nth-child) selects elements in a particular state or position. A pseudo-element (double colon, like ::before or ::after) styles a specific part of an element or injects content that is not in the HTML.
Yes. Selenium, Playwright, and Cypress all support CSS selectors as locators. You pass a selector string such as .login-btn or input[name='email'] to find elements, and CSS locators are usually faster and more readable than XPath.
The :has() pseudo-class matches an element based on what it contains, which is why it is often called the parent selector. For example, .card:has(img) styles only cards that hold an image, and h1:has(+ p) targets a heading followed by a paragraph. It solved a long-standing gap where CSS could not select an ancestor.
A selector matches elements by their type, class, ID, attribute, state, or part. A combinator is the symbol between two selectors that describes their relationship in the DOM, such as a space for descendants, > for direct children, + for an adjacent sibling, and ~ for general siblings. Combinators add no specificity of their own.
KaneAI - Testing Assistant
World’s first AI-Native E2E testing agent.

TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance