Highlight Pseudo-Elements: What They Are and Why Style Them

With Chrome 144, the ::search-text pseudo-element is now part of the highlight family. It targets the text matched by the browser’s find-in-page feature (typically triggered with Ctrl/Command + F). The default style is a yellow background for matches, with the current match shown in orange via ::search-text:current — but these defaults are customizable.

The family of highlight pseudo-elements includes ::selection, ::target-text, ::spelling-error, ::grammar-error, ::highlight(), and now ::search-text. The <mark> element also functions similarly. All of them except ::highlight() come with default browser styling.

Why Customize Highlights at All?

The main motivation is accessibility and usability. Default highlight colors may not provide enough color contrast against text or background, or they may blend into the page’s design. For instance, the standard yellow for find-in-page matches can be hard to see on certain backgrounds.

A robust approach to guaranteeing visual distinction is using relative color syntax to invert the container’s background color for the highlight background:

body {
  --background: #38003c;
  background: var(--background);

  mark,
  ::selection,
  ::target-text,
  ::search-text {
    /* Match color to background */
    color: var(--background);

    /* Convert to RGB then subtract channel value from channel maximum (255) */
    background: rgb(from var(--background) calc(255 - r) calc(255 - g) calc(255 - b));
  }
}

This snippet works by converting the background color to RGB, then subtracting each channel’s value from 255 to produce an inverted color. The result is set as the highlight’s background, ensuring it stands out against virtually any backdrop. While the inverted text color won’t always guarantee accessible contrast, it’s generally a solid starting point — though contrast should always be verified with a dedicated tool.

Keeping Highlights Distinct From Each Other

Different highlight types should also look visibly different from one another, especially since they can overlap — for example, a user may select text that is also a find-in-page match. Assigning slightly varied inverted colors to each type helps communicate what each highlight represents:

body {
  --background: #38003c;
  background: var(--background);

  mark,
  ::selection,
  ::target-text,
  ::search-text {
    color: var(--background);
  }

  mark {
    /* Invert all channels */
    background: rgb(from var(--background) calc(255 - r) calc(255 - g) calc(255 - b) / 70%);
  }

  ::selection {
    /* Invert all channels but R */
    background: rgb(from var(--background) r calc(255 - g) calc(255 - b) / 70%);
  }

  ::target-text {
    /* Invert all channels but G */
    background: rgb(from var(--background) calc(255 - r) g calc(255 - b) / 70%);
  }

  ::search-text {
    /* Invert all channels but B */
    background: rgb(from var(--background) calc(255 - r) calc(255 - g) b / 70%);
    
    &:current {
      /* Invert all channels but B, but without transparency */
      background: rgb(from var(--background) calc(255 - r) calc(255 - g) b / 100%);
    }
  }
}

In that snippet, <mark> retains its default look, while ::selection, ::target-text, and ::search-text each keep one color channel unmodified from the inversion logic. The resulting colors are distinct yet still have an inverted character. Adding an alpha channel at 70% (and 100% for ::search-text:current) lets overlapping highlights blend and show where each begins or ends.

The ::spelling-error and ::grammar-error pseudo-elements are left out of this scheme. They already carry their own well-understood visual cues — typically red and green underlines, respectively — which mostly appear in editable areas like a <textarea>.

Because <mark>, ::selection, ::target-text, and ::search-text can appear anywhere and can stack on top of each other, keeping them visually discernible is important. Be aware that even fully inverted colors can fail contrast checks (the inverse of #808080 is itself, for example), so always test real combinations. Once contrast-color() ships on all engines, this may become easier to automate.

Since there is no way to reliably unstyle highlights or undo their visual impact, it’s worth planning these carefully — you get one chance to make them both accessible and unmistakable.