Modifying Specific Letters with CSS and JavaScript

Avatar of Bret Cameron
Bret Cameron on

Changing specific characters can be a challenge in CSS. Often, we’re forced to implement our desired changes one-by-one in HTML, perhaps using the span element. But, in a few specific cases, a CSS-focused solution may still be possible. In this article, we’ll start by looking at some CSS-first approaches to changing characters, before considering a scenario where we need to turn to JavaScript.

CSS

Right now, CSS doesn’t excel at targeting specific characters without making alterations to the HTML. However, there are a few scenarios where CSS could be the go-to.

@font-face

The @font-face rule is regularly used to create custom fonts, but its unicode-range property can also allow us to target specific characters. 

For example, imagine our site often contains ampersands in its headings. Instead of using the heading font, we want something a tad more flamboyant. We can look up the unicode value of an ampersand (U+0026) and use unicode-range to target this specific character.

@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300');

h1, h2, h3, h4, h5, h6 {
  font-family:  'Ampersand', Montserrat, sans-serif;
}

@font-face {
  font-family: 'Ampersand';
  src: local('Times New Roman');
  unicode-range: U+0026;
}

Try this with the following HTML to see it in action:

<h1>Jane Austen Novels</h1>
<h2>Pride & Prejudice</h2>
<h2>Sense & Sensibility</h2>

::first-letter

The ::first-letter pseudo-element was primarily designed with drop caps in mind and it is supported by all major browsers.

p::first-letter {
  font-size: 125%;
  font-weight: bold;
}

Of course, this is only useful in a relatively limited number of scenarios. There have been several calls for an  ::nth-letter pseudo-element (including here on CSS-Tricks) but, right now, that’s just a pipe dream!

::after

Using the ::after pseudo-element and content property, we can achieve a similar effect for the final character — so long as that character is always the same. For example, here’s how we could add a jazzy, italicized exclamation point after every h2 element:

h2::after {
  content: '\0021';
  color: red;
  font-style: italic;
}

font-variant-alternates

Finally, there’s the font-variant-alternates property. This is only supported by Firefox, so it’s not recommended for production, but it may be worth knowing about for really specific scenarios: if a font happens to contain alternate glyphs, we can use this property with the character-variant() function to select a preferred glyph for a character of our choice.

JavaScript

Turning to JavaScript doesn’t need to come at a cost to performance, especially if we run HTML-altering functions at build time. The most common use case is probably to find and replace specific characters in our HTML with a span element. For simplicity’s sake, I’ll begin with an example on the client-side, and after that we’ll look into running this at build with webpack.

Find and replace at runtime

Let’s imagine that, whenever we have the text “LOGO” in a header on our site, we want to add a special style to the first “O” character only, by wrapping it in a span element with the class .special-o.

const headings = document.querySelectorAll("h1, h2, h3, h4, h5, h6");

for (const heading of headings) {
  heading.innerHTML = heading.innerHTML
    .replace(/\bLOGO\b/g, 'L<span class="special-o">O</span>GO');
}

In the JavaScript above, we’re performing a find-and-replace on every heading tag. 

Our regular expression uses the metacharacter \b to ensure that LOGO is always a word — rather than an element of a larger word. For example, we don’t want to match the plural  “LOGOS.” Right now, it would be impossible to do this with CSS, not least because we only want to target the first “O” in the sequence.

The same principle applies if we want to replace the “O” — or even the whole word “LOGO” — with an image. 

Find and replace at build

There are plenty of build tools out there, but as webpack is so popular, we’ll use that for our example — and luckily, there’s a plugin for what we need called string-replace-loader. For those new to webpack, a loader is used to preprocess files. Here, we can perform a find-and-replace on specific files as part of our build. 

First, we need to install the plugin:

npm install --save-dev string-replace-loader

Then, inside webpack.config.js add:

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.html$/i,
        loader: 'string-replace-loader',
        options: {
          search: '/\bLOGO\b/g',
          replace: 'L<span class="special-o">O</span>GO',
        }
      }
    ]
  }
}

By changing the test property value, we could target JSX, TSX, PUG, Handlebars or any other templating file format:

/\.html$/i # HTML
/\.[jt]sx$/i # JSX or TSX
/\.pug$/i # PUG
/\.handlebars$/i # Handlebars

The advantage of this approach is that no unnecessary JavaScript will run in our client’s browser. 

Final note

Finally, if you’re comfortable creating and editing fonts and would rather avoid CSS or JavaScript, a custom font could be a solution for many of the scenarios set out above. There are plenty of free font-editing tools such as Font Forge or Birdfont for those who want to try this more design-focused approach.