CSS
Web Development
HTML
Programming
Coding

Is there a CSS selector for elements containing certain text?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

No, standard CSS does not have a selector that matches elements based on their text content. CSS selectors operate on the DOM structure (element names, classes, IDs, attributes, and relationships between elements), but they have no mechanism to inspect or match against text nodes. If you need to style elements based on what text they contain, you must either add semantic markup (classes or data attributes) or use JavaScript to inspect the text and apply classes dynamically.

Why CSS Cannot Select by Text

CSS was designed as a declarative styling language, not as a DOM query language. The CSS selector specification defines matching rules based on structural properties of elements:

CSS Can MatchCSS Cannot Match
Element type (div, p, span)Text content of an element
Class names (.warning)Substring of inner text
IDs (#header)Text of child text nodes
Attributes ([data-status])Computed text from pseudo-elements
Pseudo-classes (:hover, :focus, :checked)Text generated by JavaScript
Structural position (:first-child, :nth-of-type)Dynamic content changes
Parent-child relationships (article > p)Text across nested elements

This is a deliberate design boundary. Text-based matching would require the CSS engine to parse and compare string content during layout, which would be expensive and would blur the line between styling and application logic.

The Right Solution: Semantic Markup

If you need to style elements differently based on their meaning, encode that meaning in the markup. The most reliable approaches are classes and data attributes.

Using Classes

html
1<ul class="message-list">
2  <li class="message message-warning">Disk space low</li>
3  <li class="message message-success">Backup completed</li>
4  <li class="message message-error">Connection failed</li>
5</ul>
css
1.message-warning {
2  color: #b45309;
3  background-color: #fef3c7;
4}
5
6.message-success {
7  color: #15803d;
8  background-color: #dcfce7;
9}
10
11.message-error {
12  color: #dc2626;
13  background-color: #fee2e2;
14}

Using Data Attributes

When the styling condition maps to a value rather than a boolean category, data attributes are cleaner:

html
<div data-status="warning">Disk space low</div>
<div data-status="success">Backup completed</div>
<div data-status="error">Connection failed</div>
css
1[data-status="warning"] {
2  color: #b45309;
3  border-left: 4px solid #f59e0b;
4}
5
6[data-status="success"] {
7  color: #15803d;
8  border-left: 4px solid #22c55e;
9}
10
11[data-status="error"] {
12  color: #dc2626;
13  border-left: 4px solid #ef4444;
14}

Data attributes also support partial matching:

css
1/* Matches any element where data-status starts with "warn" */
2[data-status^="warn"] {
3  color: #b45309;
4}
5
6/* Matches any element where data-status contains "err" */
7[data-status*="err"] {
8  color: #dc2626;
9}
10
11/* Matches any element where data-status ends with "cess" */
12[data-status$="cess"] {
13  color: #15803d;
14}

These attribute selectors are CSS-native and work in all browsers. They match attribute values, not text content, which is the key distinction.

Using JavaScript When Text Content Matters

If you genuinely need to style based on text content and you cannot change the markup (third-party widgets, CMS output, legacy systems), JavaScript is the correct tool.

Vanilla JavaScript

javascript
1// Add a class based on text content
2document.querySelectorAll('.log-entry').forEach(entry => {
3  const text = entry.textContent;
4  if (text.includes('ERROR')) {
5    entry.classList.add('log-error');
6  } else if (text.includes('WARN')) {
7    entry.classList.add('log-warning');
8  }
9});
css
1.log-error {
2  color: #dc2626;
3  font-weight: bold;
4}
5
6.log-warning {
7  color: #b45309;
8}

Using MutationObserver for Dynamic Content

If the DOM changes after initial load (e.g., streaming log output), use a MutationObserver to apply classes as new elements appear:

javascript
1const observer = new MutationObserver(mutations => {
2  for (const mutation of mutations) {
3    for (const node of mutation.addedNodes) {
4      if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('log-entry')) {
5        if (node.textContent.includes('ERROR')) {
6          node.classList.add('log-error');
7        }
8      }
9    }
10  }
11});
12
13observer.observe(document.querySelector('.log-container'), {
14  childList: true,
15  subtree: true
16});

This keeps the text inspection in JavaScript where it belongs, while CSS handles only the visual styling.

jQuery's :contains() Is Not CSS

Some developers have seen :contains("text") syntax and assume it is part of CSS. It is not. The :contains() pseudo-class was proposed in early CSS3 drafts (Selectors Level 3) but was removed from the specification before browsers implemented it. It lives on in two places:

javascript
1// jQuery selector - this is jQuery's selector engine, not CSS
2$('p:contains("Warning")').addClass('warning');
3
4// Cypress testing framework - also not CSS
5cy.get('td:contains("Total")').should('have.class', 'amount');

Both jQuery and Cypress implement their own selector extensions beyond what CSS supports. Code using :contains() will not work in a CSS stylesheet or in document.querySelector().

javascript
// This does NOT work - :contains is not a valid CSS selector
document.querySelectorAll('p:contains("Warning")');
// Throws: SyntaxError: 'p:contains("Warning")' is not a valid selector

What About :has()?

The :has() selector (now supported in Chrome, Safari, and Firefox) is one of the most powerful additions to CSS. But it selects based on descendant elements, not text content.

css
1/* Select articles that contain an element with class error-badge */
2article:has(.error-badge) {
3  border: 2px solid red;
4}
5
6/* Select labels that have a required input as a sibling */
7label:has(+ input:required) {
8  font-weight: bold;
9}
10
11/* Select cards that contain an image */
12.card:has(img) {
13  padding: 0;
14}

:has() is structural. It can check for the presence of child elements, but it cannot inspect the text content of those elements. It does not solve the text-matching problem.

Framework-Specific Approaches

Modern component frameworks handle this at the rendering level rather than in CSS:

React

jsx
1function StatusMessage({ text, status }) {
2  return (
3    <p className={`message message-${status}`}>
4      {text}
5    </p>
6  );
7}

Vue

html
1<template>
2  <p :class="['message', `message-${status}`]">
3    {{ text }}
4  </p>
5</template>

In both cases, the class assignment happens during rendering based on component state, not by inspecting rendered text. This is the correct architectural boundary: components decide what classes to apply, and CSS decides what those classes look like.

Common Pitfalls

Confusing jQuery selectors or testing framework locators with standard CSS is the most frequent mistake. :contains() works in jQuery and Cypress but is not part of the CSS specification and is not supported by browsers in stylesheets or querySelector.

Trying to use attribute selectors ([title*="warning"]) as a workaround for text content matching only works if the information is duplicated in an attribute. This approach is fragile and adds redundant data to the DOM.

Writing complex JavaScript to scan and classify text when the real fix is adding a class in the component that renders the markup adds unnecessary runtime cost. If you control the rendering, add semantic classes at render time. JavaScript text scanning should only be used when the markup comes from an external source you cannot modify.

Forgetting about localization makes text-based styling rules break when the application supports multiple languages. A rule that looks for the English word "Warning" fails for users seeing "Advertencia" or "Avertissement". Semantic classes and data attributes are language-independent.

Over-relying on :has() as a general replacement for text matching leads to brittle selectors. :has() is powerful for structural queries, but it still cannot match text content. If your styling condition is truly text-based, JavaScript is the right tool.

Summary

  • Standard CSS has no selector for matching elements by their text content.
  • Use classes or data attributes to encode meaning in the markup so CSS can target it.
  • Use JavaScript to inspect text content and add classes dynamically when you cannot control the markup.
  • jQuery's :contains() is a jQuery extension, not a standard CSS selector.
  • ':has() selects based on descendant elements, not text nodes.'
  • In component frameworks, assign semantic classes at render time rather than scanning rendered text.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.