Question

CSS Selectors for Text Content: What CSS Can and Cannot Match

csscss-selectors

Question

Given this table, is there a standard CSS selector that matches every <td> element whose text content is exactly male?

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Identity</th>
      <th>Age</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Peter</td>
      <td>male</td>
      <td>34</td>
    </tr>
    <tr>
      <td>Susanne</td>
      <td>female</td>
      <td>12</td>
    </tr>
  </tbody>
</table>

Short Answer

You will learn why standard CSS selectors cannot inspect an element's visible text, why this limitation exists, and how to design HTML so that CSS or JavaScript can reliably target the intended table cells.

Concept

Standard CSS has no selector that matches an element based on its text content. In particular, CSS cannot express “select every <td> whose displayed text is male.”

CSS selectors work primarily with document structure and element metadata, such as:

  • element names: td
  • classes: .identity
  • IDs: #user-row
  • attributes: [data-identity="male"]
  • states: :hover, :checked
  • relationships: tr > td, tr + tr

The text male in this example is a text node, not an HTML attribute or an element that CSS can query. CSS can style a selected element's text, but it cannot normally use the text as the condition for selecting that element.

This matters because visible labels are often changed by translation, formatting, or user-entered data. Stable semantic data—classes and data-* attributes—gives styles and scripts a dependable way to identify elements.

Some libraries provide nonstandard extensions. For example, jQuery historically supports :contains("male"), but it is not valid standard CSS and should not be used as a CSS rule selector. It also performs substring matching, so :contains("male") would match both male and female.

Mental Model

Think of CSS as a filing system that can read a folder's label and tags, but not the sentence written inside a document.

  • The element name, class, ID, and attributes are the folder label and tags.
  • The visible text inside <td>male</td> is the document's contents.

If you need to find a folder reliably, put a tag on it first:

<td data-identity="male">male</td>

Now CSS and JavaScript can find the cell using its tag instead of trying to read its visible text.

Take Quiz

Syntax and Examples

Use a class or a data-* attribute when the value has meaning that code may need to target.

<td class="identity identity--male">male</td>
<td class="identity identity--female">female</td>
.identity--male {
  background-color: #dbeafe;
  font-weight: 700;
}

A scalable alternative stores the underlying value in a data attribute:

<td class="identity" data-identity="male">male</td>
<td class="identity" data-identity="female">female</td>
.identity[data-identity="male"] {
  background-color: #dbeafe;
}

The attribute selector matches the exact attribute value. It does not depend on the displayed text, so the text could later be translated without changing the selector:

<td class="identity" data-identity="male">man</td>

The same CSS selector still works.

Step by Step Execution

Consider this markup and rule:

<td class="identity" data-identity="male">male</td>
.identity[data-identity="male"] {
  color: navy;
}

CSS evaluates the selector from its conditions:

  1. .identity checks whether the <td> has the identity class. It does.
  2. [data-identity="male"] checks whether that same element has a data-identity attribute with the exact value male. It does.
  3. Because both conditions match, CSS applies color: navy to the <td>.
  4. The browser renders its text, male, in navy.

Now compare a different cell:

<td class="identity" data-identity="female">female</td>

It passes the class check but fails the exact attribute-value check, so the rule is not applied.

Real World Use Cases

Classes and data attributes are useful whenever an element has a category, status, or state.

  • Order tables: <td data-status="overdue">Overdue</td> can receive a warning style.
  • User roles: rows with data-role="admin" can display a role badge.
  • Inventory: data-stock="out" can highlight unavailable products.
  • Form validation: .field--invalid can show an error border.
  • Feature flags: [data-plan="pro"] can expose plan-specific visual treatments.
  • Localized apps: a value such as data-status="pending" remains stable while its displayed label changes between languages.

Use semantic HTML where appropriate. If identity is actual user data, the browser does not automatically need a special CSS selector for it; add a class or data attribute only when styling or behavior needs it.

Take Quiz

Real Codebase Usage

In real projects, developers usually avoid deriving logic from displayed text. Instead, templates render both a machine-readable value and a human-readable label.

<tr data-user-id="42">
  <td>Peter</td>
  <td class="identity" data-identity="male">Male</td>
  <td>34</td>
</tr>

Styling state

.identity[data-identity="male"] {
  color: #1d4ed8;
}

JavaScript behavior

const maleCells = document.querySelectorAll(
  'td.identity[data-identity="male"]'
);

maleCells.forEach((cell) => {
  cell.classList.add('is-highlighted');
});

Prefer a row-level attribute for row-level behavior

If the whole row represents a person, putting the value on the row can be clearer:

<tr data-identity="male">
  <td>Peter</td>
  <td>Male</td>
  <td>34</td>
</tr>
tr[data-identity="male"] {
  background: #eff6ff;
}

This pattern supports validation, filtering, test automation, and rendering from API data without coupling code to presentation text.

Common Mistakes

Expecting a standard :contains() selector

This is not standard CSS:

/* Not valid in standard CSS */
td:contains("male") {
  color: blue;
}

Do not place it in a stylesheet and expect browsers to support it. Use a class or attribute instead.

Using a substring match for an exact value

Even in jQuery, this selector has a logic problem:

$("td:contains('male')")

female contains the letters male, so it would match both cells. Store an exact value in an attribute and use [data-identity="male"].

Confusing :has() with text matching

Modern CSS :has() can select an element based on a matching descendant element, not a text node:

/* Selects rows that contain an element with this class. */
tr:has(.identity--male) {
  background: #eff6ff;
}

It does not make this possible:

/* Not supported: text is not a CSS selector. */
tr:has("male") {
  background: #eff6ff;
}

Selecting by column position when meaning is the real condition

tr > td:nth-child(2) {
  color: blue;
}

This selects every second cell, not only male identities. It is appropriate for column-wide styling, but not for selecting one data value.

Using visible text as a JavaScript data source

cell.textContent === "male" can be acceptable for a small, one-off script, but it is fragile when labels change, whitespace is introduced, or the interface is translated. Prefer a data-* value when you control the HTML.

Comparisons

ApproachCan match visible text?Standard CSS?Best use
tdNoYesSelect every table cell
.identity--maleNo; uses class metadataYesFixed visual category or state
[data-identity="male"]No; uses attribute metadataYesExact semantic value, styling, and scripts
tr:has(.identity--male)No; checks descendant elementsYes in modern browsersStyle a parent based on child structure/state
td:nth-child(2)No; uses positionYesStyle an entire known column
td:contains("male")YesNojQuery-only legacy behavior; avoid for CSS
JavaScript textContentYesNot CSSOne-off processing of existing markup

data-* attributes are usually the best choice when the value comes from application data. Classes are often best when the value describes a styling state, such as .is-error or .is-selected.

Take Quiz

Cheat Sheet

/* All cells */
td { }

/* Cells with a class */
td.identity--male { }

/* Cells with an exact data value */
td[data-identity="male"] { }

/* Rows containing a matching child element */
tr:has(td[data-identity="male"]) { }

/* The second column in each row */
tr > td:nth-child(2) { }
  • Standard CSS cannot select an element by its text content.
  • :contains() is not a standard CSS pseudo-class.
  • Attribute equality, [name="value"], is an exact attribute match.
  • Attribute substring selectors exist, such as [data-code*="abc"], but they inspect attributes—not displayed text.
  • Use data-* for machine-readable values and classes for reusable styling states.
  • Use :has() to react to descendant elements, not descendant text.

FAQ

Can CSS select an element based on its inner text?

No. Standard CSS cannot select elements based on their text nodes or textContent.

Is :contains() a valid CSS selector?

No. It is not part of standard browser CSS. It has existed as a jQuery-specific selector extension.

Why would :contains("male") be unsafe anyway?

It is a substring match. The text female contains male, so both could match.

Can I use :has() to find a cell containing text?

No. :has() checks for a matching descendant element selector, such as .identity--male; it does not accept text as a selector.

What is the best HTML solution?

Add a meaningful class or an exact data-* attribute, for example <td data-identity="male">male</td>.

Can JavaScript select cells by their text?

Yes. You can inspect element.textContent, but use a data attribute when you control the markup and need robust behavior.

Should I use nth-child(2) for the identity column?

Use it only when styling every cell in that column. It cannot distinguish male from female.

Do data attributes affect what users see?

No. data-* attributes store extra information in the HTML; the displayed text remains whatever you place between the tags.

Take Quiz

Mini Project

Description

Build a small people table that highlights entries with a selected identity value. The project demonstrates the robust approach: store a stable value in a data-* attribute and let CSS and JavaScript target that value instead of parsing visible text.

Goal

Create a table where a button highlights only rows whose data-identity value is male.

Requirements

Use a semantic HTML table with headings.|Add a data-identity attribute to each person's row.|Create at least one male row and one female row.|Add a button that toggles highlighting for male rows.|Do not select rows by comparing their visible text.

Take Quiz

Keep learning

Related questions

View more