Tweaking Text Level Styles

This post is building on the post Short note on making your mark (more accessible) by Steve Faulkner at the Paciello Group blog. In that post, Steve answers a very specific question about the <mark> and making it more accessible for screen readers.

I saw some follow-up questions on the Twitters related to language and Windows High Contrast Mode, as well as other elements. I wrote this post to deal with four elements that impart text-level semantics and come with default styles that could use a little help.

I am not a copywriter nor a copy editor, so the wording and styles I am proposing may not be ideal for that audience (where I think these elements would see the most use). Rather, consider these places to start, places from which you can tweak them to fit your audience.

As Steve’s example shows, you can use CSS generated content to wrap the highlighted text in a spoken declaration at the start and end of the chosen text while also visually hiding it. Each example in this post leans on the following block of CSS:

mark::before, mark::after, del::before, del::after, ins::before, ins::after, s::before, s::after {
  clip-path: inset(100%);
  clip: rect(1px, 1px, 1px, 1px);
  height: 1px;
  width: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}

Now we can start to apply unique styles for each of the following elements (the links jump you deeper into this post):

The <mark> Element

The purpose of <mark> is to highlight a chunk of text to draw the reader’s attention, whether to note something in a quote or indicate a found word in a search term. The HTML 5.1 spec provides use cases for <mark>.

Default Styles

The browser default styles are probably good enough in most cases, but you may want to take this opportunity to ensure that it works with your site color scheme and maximizes contrast. Explicitly setting the styles helps enforce that.

mark {
  background-color: #ff0;
  color: #000;
}

Screen Readers

The generated content we want to use (which you may customize as you see fit for your audience):

mark::before {
  content: " [highlight start] ";
}

mark::after {
  content: " [highlight end] ";
}

Multilingual Content

A valid concern is that embedding content into the CSS violates the separation of concerns and can make maintenance more difficult. This is true. It also possible to use CSS to provide localized content instead, whether embedded in the page (which may already be wired to the back-end to pull the localized content) or just leaving it in your CSS.

*[lang=es] mark::before {
  content: " [resaltar inicio] ";
}

*[lang=es] mark::after {
  content: " [resaltar final] ";
}

Windows High Contrast Mode

Windows High Contrast Mode poses a unique challenge. Internet Explorer and Edge will both default to a yellow highlight, but the user’s high contrast theme may make that problematic to see. Instead, you can rely on system colors, which pull their values from the user’s theme.

@media screen and (-ms-high-contrast: active) {
  mark {
    color: HighlightText;
    background-color: Highlight;
  }
}

Print Styles

Not all printers will print in color, and even if they are able, not all users will allow a page to print in color. It is worth adding some other style besides just the background color, but a style that isn’t too disruptive and scales with the text.

@media print {
  mark {
    border: 1pt dotted #000;
  }
}

Update: 15 September 2020

NVDA is adding support for <mark> as of the 2020.3 beta release. As always, test with your users and browsers, and their configurations.

<mark> Update: 4 January 2024

Don’t rely on what the specs say. Always test. Which I have done today with plain vanilla <mark> (results for current releases of each):

JAWS/Chrome
Precedes with “mark”, follows with “mark”.
NVDA/Firefox
Precedes with “highlighted”, follows with “out of highlighted”.
Narrator/Edge
Precedes with a pause, follows with a pause.
Android/TalkBack/Chrome
Precedes with a pause, follows with “highlight”.
macOS/VoiceOver/Safari
Precedes with “highlighted”, follows with a pause.
iPadOS/VoiceOver/Safari
Nothing exposed in read-all. Otherwise precedes with a pause, follows with “highlighted”

The <del> Element

The <del> represents content that has been deleted from a page. It’s pretty straightforward and is often used in conjunction with <ins>, but you can read more about <del> in the HTML 5.1 spec.

Default Styles

Both <del> and <s> are assigned the same style in browsers — strikethrough. This may or not be worth defining, depending on your own rules.

del {
  text-decoration: line-through;
}

Screen Readers

The generated content we want to use (which you may customize as you see fit for your audience):

del::before {
  content: " [deletion start] ";
}

del::after {
  content: " [deletion end] ";
}

Multilingual Content

As with <mark> above, you can just use the lang attribute on the appropriate parent element as your selector to provide localized generated content.

Windows High Contrast Mode

Not a concern here as it does not rely on color.

Print Styles

Not a concern here as it does not rely on color nor background styles.

Update: 19 September 2018

NVDA has added support for <del> in Chrome. So, as always, test with your users and their configurations.

Update: 19 March 2020

JAWS 2020.2003.13 (March 2020) has added support for <del>. That link points to release note, but you may need to change the select control with no accessible name to JAWS 2020, as it defaults to the most recent release even with any anchor link.

Update: 8 May 2020

2020.2004.66 (April 2020) has rolled back support for <del>. To spare you dealing with the inaccessible menu, here is the text: In response to customer feedback, disabled the announcement of inserted and deleted text on web pages.

The <ins> Element

The <ins> represents content that has been added a page. It is often used in conjunction with <del>, but you can read more about <ins> in the HTML 5.1 spec.

Default Styles

The default style for <ins> is typically underline. Since underlines are generally reserved for links (except for fancy sites that like failing WCAG), this can be confusing for users. Our options are limited since we will be using ::before and ::after to support screen reader users and it seems silly to ask authors to add pointless <span>s. So let’s try a color-coded double bottom border (IE and Edge do not honor double on text-decoration). The color coding needs to have sufficient contrast and also convey some meaning. In this case, I went with green (for a white background).

ins {
  text-decoration: none;
  border-bottom: .2em double #080;
}

Screen Readers

The generated content we want to use (which you may customize as you see fit for your audience):

ins::before {
  content: " [insertion start] ";
}

ins::after {
  content: " [insertion end] ";
}

Multilingual Content

As with <mark> above, you can just use the lang attribute on the appropriate parent element as your selector to provide localized generated content.

Windows High Contrast Mode

Once you start tweaking colors, you need to consider WHCM. In this case, we only need to play with the border color. Since we know that the highlight color is already paired with text at the OS level, let’s lean on that for the border color:

@media screen and (-ms-high-contrast: active) {
  ins {
    border-bottom-color: Highlight;
  }
}

Print Styles

The green will print as dark gray for users without color printers, so you can probably leave it as is. You may want to tweak the border to use pt as the sizing unit.

@media print {
  ins {
    border-bottom-width: 3pt;
  }
}

Update: 19 September 2018

NVDA has added support for <ins> in Chrome. So, as always, test with your users and their configurations.

Update: 19 March 2020

JAWS 2020.2003.13 (March 2020) has added support for <ins>. That link points to release note, but you may need to change the select control with no accessible name to JAWS 2020, as it defaults to the most recent release even with any anchor link.

Update: 8 May 2020

2020.2004.66 (April 2020) has rolled back support for <ins>. To spare you dealing with the inaccessible menu, here is the text: In response to customer feedback, disabled the announcement of inserted and deleted text on web pages.

The <s> Element

The <s> represents content that is no longer relevant or accurate. A common use case is to indicate pre-sale prices on e-commerce sites. Read more about <s> in the HTML 5.1 spec.

Default Styles

Both <s> and <del> are assigned the same style in browsers — strikethrough. While that is fine by default for <del>, you may want to differentiate it a bit for <s> with some color. I chose red, though you can easily make the case that this is a better style for <del>. It’s up to you. Note that neither Internet Explorer nor Edge support colors on the text-decoration property, so those users will not see the color.

s {
  text-decoration-color: #f00;
}

Screen Readers

The generated content we want to use (which you may customize as you see fit for your audience):

s::before {
  content: " [start of stricken text] ";
}

s::after {
  content: " [end stricken text] ";
}

Multilingual Content

As with <mark> above, you can just use the lang attribute on the appropriate parent element as your selector to provide localized generated content.

Windows High Contrast Mode

Once you start tweaking colors, you need to consider WHCM. In this case, since neither IE nor Edge honor the color change, and since only IE and Edge honor WHCM (Firefox does to some extent, but WHCM users are generally in IE or Edge), you are currently safe ignoring it. If you want to be future proof for when Edge does support coloring text-decoration, then drop this in now:

s {
    text-decoration-color: Highlight;
  }

Print Styles

The red will print as dark gray for users without color printers, so you can probably leave it as is.

Wrapping It Up

I have all those examples stuffed into one CodePen, which I have also embedded here:

See the Pen Text Level Semantics by Adrian Roselli (@aardrian) on CodePen.

I also captured how the page works in different browsers and embedded them below.

In a Screen Reader

I have NVDA installed on this machine, so you get to hear it in NVDA.

This example uses NVDA and Firefox.

In Windows High Contrast Mode

Screen shots of the CodePen example in Internet Explorer and Edge with Windows High Contrast Mode active.

Screen shot in Internet Explorer while using Windows High Contrast Mode. Screen shot in Edge while using Windows High Contrast Mode.
Internet Explorer and then Edge.

When Printed

Screen shots of the print preview dialogs from Firefox and Edge. Firefox honors all the styles, while Edge ignores the background color on <mark> and the color on the strikethrough text.

Print preview dialog in Firefox. Print preview dialog in Edge.
Firefox and then Edge.

Update: May 2018 CodePen Challenge

I made a demo for the May 2018 week 1 CodePen challenge, HTML Buddies. The challenge focuses on <del> & <ins>, which I just happen to have covered above. The demo just takes what I have already written and focuses it for the challenge.

See the Pen HTML Buddies: del & ins by Adrian Roselli (@aardrian) on CodePen.

If the embed is a problem, visit the pen directly.

Update: 5 June 2023

I was so excited Steve did the heavy lift of updating what screen readers announce back in January (I even posted it on the socials) that I forgot to share it here: Screen Readers support for text level HTML semantics

So <mark> is better, <em> is not. The former is likely related to being needed for web-based document editing while the latter is likely related to authors emphasizing poorly and users being fine with not hearing all that.

Update: 4 January 2024

A made a demo with no CSS generated content (debug mode) so you can test support for the vanilla HTML elements more easily. I updated the <mark> entry above.

See the Pen Untitled by Adrian Roselli (@aardrian) on CodePen.

Update: 16 August 2024

I updated the pen just above and added versions of the elements with their corresponding ARIA roles and generic <span>s with the aforementioned ARIA roles (because someone is always going to throw ARIA at everything). I shared my findings about <del> and <s> in a thread on Mastodon. My takeaway:

Remember that how these elements are exposed to users aren’t necessarily bugs. Screen readers have to account for poor author practices and sometimes choose not to expose stuff (often at end user request).

I used:

7 Comments

Reply

Great work Adrian, really useful.

Reply

Very intelligent idea. Thank you for writing about it.

Reply

This is wonderfully comprehensive and shareable to a wide audience. I’ll definitely pass along to our front-end and content teams. Thank you!

Damian Sian; . Permalink
Reply

Do all screenreaders read the text in brackets with a good stop or wouldn’t it be better to add a colon to the start-text? Like: ” [highlight start:] “

In response to Jens Grochtdreis. Reply

Great question, and I have not tested all screen reader and browser combinations. I would be curious to hear what you get if you test it.

Reply

The language selectors will not work as intended. When you have

*[lang=en] mark::before { content: " [highlight start] ";}
*[lang=de] mark::before { content: " [Start der Markierung] ";}

in the stylesheet, applied to markup

<html lang="en">…
<p>In the English original: <q>There are four ways to apply different styles to different languages in a multilingual document using CSS. They are listed here <mark>in order of preference<mark>.</q></p>
<p>In the German translation: <q lang="de">Es gibt vier Möglichkeiten, um mit CSS verschiedene Stile für verschiedene Sprachen innerhalb eines mehrsprachigen Dokuments zuzuweisen. <mark>Nach der bevorzugten Verwendung geordnet</mark> sind dies: …</q></p>

then both selectors match the second mark element. The latter rule wins; the mark element would be announced in German—by chance. With reversed order of the rules in the stylesheet, it would be announced in English.

When you would want to have it in any case in the language of the page, you need to use html or :root instead of *.

Instead of using the [lang] attribute selector you’d be better off with the :lang() pseudo-class, see W3C i18n articleStyling using language attributes (from which the quotes where taken) for details.

The code should look like:

:root:lang(en) mark::before { content: " [highlight start] ";}
:root:lang(de) mark::before { content: " [Start der Markierung] ";}
In response to Gunnar Bittersmann. Reply

Gunnar, for some reason I thought I had included a disclaimer about my proposed selector for handling localization. Clearly I did not, and clearly it is not an ideal selector for many/most/all situations.

Thank you for the W3C reference and the German translation for mark::before.

Leave a Comment or Response