Eugene Fedorenko is Writing, Reading, and Traveling

Outlining text with CSS and SVG

A few weeks ago, I worked on a website refresh and wanted to outline text in a few titles. This is not the effect I’d normally use, but in this case, the titles were on top of a patterned background, and a thick outline in the same color as a background helped with the readability:

Before vs. after outlining text with a background color
Before vs. after outlining text with a background color

It took me a while to believe that there is no good way to do this with pure CSS. Unlike box-shadow, a text-shadow property doesn’t have the fourth “spread” value. Experimental property text-stroke sounded like a good fit until I realized that the stroke is always aligned inside the text:

CSS property text-stroke
CSS property text-stroke

After some experimentation, I solved this with a mix of SVG, CSS, and JS. Some parts weren’t very obvious, so I decided to document my solution in case it helps someone else. First, let’s walk through markup:

<h1>
  <svg viewBox="0 0 1000 90" xmlns="http://www.w3.org/2000/svg">
    <text x="500" text-anchor="middle" dominant-baseline="hanging" fill="white" stroke-width="22" paint-order="stroke">
      Product Manager
    </text>
  </svg>
</h1>

SVG acts as a wrapper on a title text, just to provide more styling properties:

  • paint-order="stroke" keeps the stroke behind the text. This is very important! SVG still only supports center stroke alignment, but this is a nice way to imitate an “outside” stroke alignment.
  • Because half of the stroke is hidden behind the text, the value of stroke-width is twice bigger than it should be.
  • x="500" (middle of viewBox) and text-anchor="middle" align text to the horizontal center of the block.
  • SVG’s height is set to match the line-height in CSS, and text is placed at omitted y="0". dominant-baseline="hanging" renders text from the top instead of from the baseline, so it fills the whole block. Without this property, the text would be rendered outside of the box.
  • Stroke color is specified in CSS using stroke property.
  • The font is inherited from h1 and specified in CSS as well.

All of that together adds a nice stoke to the text (made darker for the demo):

Text outlined in SVG

The only problem is that SVG has a fixed size (see viewBox) and doesn’t scale well. I could easily scale it as an image, but I’d rather keep the font size consistent with other titles and media queries in the projects. Wish I could avoid this, but I had to add a few lines of JS to update the viewBox value based on the size of the element:

function resizeTitle (el) {
  var width = el.clientWidth;
  var height = el.clientHeight;
  var text = el.querySelector('text');
  el.setAttribute('viewBox', `0 0 ${width} ${height}`);

  if (text !== null) {
    text.setAttribute('x', (width / 2));
  }
}

function svgTitles () {
  var svgs = document.querySelectorAll('svg');

  if (svgs.length > 0) {
    svgs.forEach(el => resizeTitle(el));
  }
}

window.addEventListener('DOMContentLoaded', svgTitles);
window.addEventListener('resize', svgTitles);

That’s it! The text now scales based on media queries just as any other title in the project, only with nice outlines. Considering the outlines are the same color as backgrounds, it’s hard to even say that they are there, but the text is much easier to read. This implementation can be seen live in Wildbit’s Good & Bad section or as a CodePen.