An abstract visual representation of modern CSS features including text highlighting, editorial indents, and mathematical typography in utilitybend's signature neon colors.

Between all the fancy features such as container style queries, contrast-color(), gap decorations, corners-shape... there are some features that became baseline available and we forgot about them. They are those little things that make life a bit easier. I wanted to search for the kinds of features that I don’t need on a regular basis, but am happy to know are available. This article is all about the CSS Custom Highlight API, text-indents and math.

I have to admit, keeping up with everything happening on the web has become a bit of a sport, and with all those new features landing, it’s more than a little jog. Between view transitions, scroll-driven animations, and anchor positioning, it’s easy to get distracted by the fancy stuff.

But sometimes, it’s the smaller, quieter additions that solve the most annoying, long-standing hacky trickeries in our daily work.

I was going through some recent browser updates and realized a few useful features that reached full baseline support without me noticing. I thought that these deserved a spotlight and since they’re features that I probably won’t use often, a little demo felt like the right thing to do. Using my blog as my own snippets lab usually turns out with my future self being grateful, so that’s kind of what I’m doing here.

If some of these went under your radar too, here are the features and demos explained.

1. CSS Custom Highlight API (::highlight())

I think I’ve built a search-in-page feature in the past. It has been some time though. I do remember it was sort of a pain so if you ever need this, apparently it’s become a lot easier.

Historically, the only way to do this was messy. You had to search the DOM, split text nodes, wrap the matches in something like <span class="highlight">, and inject them back into the page. And then, of course, you had to clean up those spans afterward so you didn’t leave a trail of dirty DOM nodes.

It was slow, felt a bit hacky, it caused layout reflows, not a good experience at all.

The Custom Highlight API changes this. It completely separates the highlighting style from the DOM structure. You create the styles in your CSS, and use JavaScript to define the text ranges you want to highlight. The browser does the rest, painting the highlight directly in the rendering phase. This means that you have no DOM mutations and no layout reflows. This sounds easy enough, but I find the tech behind it really interesting.

So I dived a bit deeper, and here is what the browser does under the hood (if I got my facts straight):

  • The Layout Phase is skipped entirely: The text block’s geometry, wrapping, and physical position are already computed and cached. The browser knows exactly where the text is on the screen, so it doesn’t spend a single microsecond recalculating layout.
  • The Paint Phase intercept: During the painting phase, the rendering engine iterates over the pre-calculated text boxes. As it draws the text, it queries the internal Highlight Registry.
  • Overlay painting: If a coordinate range intersects with a registered highlight, the rendering engine draws a background rectangle directly underneath the characters and paints the glyphs over it using the color specified in your ::highlight() CSS.

I think you could sort of say it’s like the browser has a post-it note with coordinates of where to highlight, rather than having to re-build the page just to color some text.

Here is how we set it up in the CSS using the ::highlight() pseudo-element:

::highlight(yellow-pen) {
  background-color: oklch(88% 0.17 95);
  color: oklch(28% 0.03 240);
}

::highlight(green-pen) {
  background-color: oklch(86% 0.18 145);
  color: oklch(25% 0.04 140);
}

::highlight(pink-pen) {
  background-color: oklch(82% 0.21 340);
  color: oklch(28% 0.05 340);
}

In JavaScript, instead of wrapping elements, we create standard text ranges. We identify the parent node and set the character start and end points:

const textElement = document.getElementById("demo-text");
const textNode = textElement.firstChild;

// Range 1: "Custom Highlight API" (characters 4 to 24)
const range1 = new Range();
range1.setStart(textNode, 4);
range1.setEnd(textNode, 24);

// Range 2: "style arbitrary ranges" (characters 40 to 63)
const range2 = new Range();
range2.setStart(textNode, 40);
range2.setEnd(textNode, 63);

Once we have the ranges, we register them by passing them into a new Highlight object and assigning it to the global CSS.highlights registry under the key names we defined in our stylesheet:

const yellowPen = new Highlight(range1);
const greenPen = new Highlight(range2);

CSS.highlights.set("yellow-pen", yellowPen);
CSS.highlights.set("green-pen", greenPen);

Browser support

This feature was already available in Chrome 105 (all the way back in September 2022), but it took a while for Safari (17.2) and Firefox (111) to get on board. You can still provide fallbacks if you need to, but it this is a really cool improvement for the web.

Here is a demo of the CSS Custom Highlight API in action, a bit more advanced than what I showed above, but the idea is the same:

2. Advanced text-indent Keywords (hanging and each-line)

The text-indent property has been in CSS since the absolute beginning (remember the logo behind an <h1> trick? Or am I being a dinosaur now?). For the longest time, it only accepted a length value. If you wanted to do more traditional, print-like editorial layouts, you had to “go fancy” on it.

Recently, two keywords joined the property and reached baseline support: hanging and each-line. These are newly available, but let’s be honest, they’re an easy progressive enhancement.

Hanging indents

A hanging indent is the opposite of a normal indent. It leaves the first line of a paragraph flush, but indents every subsequent line. You see this all the time in bibliographies, citations, or glossary indexes.

Historically, we hacked this on the web by applying padding to the left of the container and a negative text-indent on the first line.

Now, we can do it natively:

cite {
  text-indent: 3em hanging;
}

The browser handles the alignment math automatically, keeping the first line exactly where it belongs and nudging the rest of the lines in by 3em.

Indents for forced line breaks (each-line)

A normal text-indent only affects the very first line of a block container. If you have a forced line break using a <br> tag, the line immediately following it is treated as part of the same block, so it gets no indentation.

But if you are typesetting poetry, song lyrics, or script dialogue, you want that indentation to apply to every new line of text, even if it is within the same paragraph.

The each-line keyword forces the browser to apply the indentation to the start of the block and to any line immediately following a <br> break:

.poetry {
  text-indent: 2.5em each-line;
}

Keeping the reading rhythm

In my editorial typography demo (embedded a bit further), I also combined this with some traditional layout rules that I really love.

For example, in books, the opening paragraph of a chapter or section is usually flush (no indentation). Only the subsequent paragraphs get indented to signal a transition, and we drop the empty vertical margins between them to keep a nice reading rhythm:

/* No indent for the opening block */
/* Subsequent paragraphs get indented and no gap */

p {
  text-wrap: pretty;
  margin-block-end: 0;
  + p {
    text-indent: 2em;
  }
}

This simple adjustment gives your web copy a beautiful, book-like feel.

Browser support for these keywords became complete in March 2026 when Chrome 146 shipped them (Safari and Firefox had implemented them earlier).

Side note: Not only did I learn about these properties and play around with them, I’ve now found a way to use the word flush when not talking about poker or toilets… but I wanted to make sure it added up, as left and/or right alignment depends on writing modes. Text indenting is logical as in: it will indent from the start of the block, regardless of writing mode.

Here is a basic demo showing hanging indents and forced line breaks:

And here is a more enhanced demo combining these keywords with other typographic adjustments. Notice that the poetry part is not a blockquote with a padding, it uses text-indent with breaks:

3. Mathematical Typesetting (font-family: math & MathML)

Rendering math formulas on the web is something I’ve never done, but I did find this feature intriguing. In the past you had to rely on libraries like LaTeX or KaTeX, or export every single formula as an SVG. Not ideal to say the least, and it was terrible for accessibility because screen readers couldn’t parse the visual layouts of the equations. Reading that fact made me feel like it had to be added to my blog. Caring about users is important.

With the rollout of MathML across browsers, we now have a native, semantic way to write equations directly in HTML using tags like <math>, <mfrac>, and <msub>. It’s this whole micro-syntax for all you maths lovers out there (I’m not one of them, really…).

Now there is a way to style these formulas correctly, by using the math generic font-family keyword:

math {
  font-family: math;
  font-size: 1.3rem;
}

But what exactly does this do? The math keyword instructs the browser to look for system fonts that were designed with mathematical layout tables (like STIX or Latin Modern Math). These fonts contain the necessary instructions to render complex stacked fractions, stretched brackets, and integrals correctly.

Here is what a native MathML block looks like in the markup:

<math display="block">
  <msup>
    <mi>e</mi>
    <mrow>
      <mi>i</mi>
      <mi>π</mi>
    </mrow>
  </msup>
  <mo>+</mo>
  <mn>1</mn>
  <mo>=</mo>
  <mn>0</mn>
</math>

This renders natively in the browser without loading a single line of external JavaScript. More importantly, screen readers can read this aloud as a structured mathematical equation, making it accessible to everyone.

MathML Core and font-family: math reached wide browser support in 2023 with Chrome 109, and both are now baseline features that you can use safely. I wouldn’t call myself an expert in MathML just yet, but I did try to recreate the formulas and for the basic ones, the writing system is not that hard (the system, not the maths… I’m still bad at that).

Here is a demo of native MathML and mathematical fonts in action:

Nice little additions

I love seeing these CSS formatting issues fixed. The text-indent feature is right up my alley. A lot of things that used to require heavy scripts or graphics can now be handled natively by the browser. It makes our pages lighter, keeps the DOM cleaner, and makes the web a whole lot more accessible.

Have you started using the Highlight API or these new typographic properties in your own projects? Let me know, I’d love to see some extra examples.

 in css, javascript, accessibility