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.
In Windows High Contrast Mode
Screen shots of the CodePen example in Internet Explorer and Edge with Windows High Contrast Mode active.
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.
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:
- Use
<del>
over<s>
because VoiceOver / iPadOS is less useful with<s>
and that means one fewer less-ideal pairing. - VoiceOver / macOS and Narrator do not convey
<del>
nor<s>
with anything other than a leading & trailing pause using default settings. - Adding the
deletion
role will cause VoiceOver / macOS to pre-pend the announcement with “deletion,” but it has no effect in the Braille emulator. - Because inserting hidden text makes for really verbose experiences for non-macOS / non-Narrator, probably don’t do it.
- As always, test with your specific users.
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:
- Firefox 129 / NVDA 2024.2
- Chrome 127 / JAWS 2024
- Edge 127 / Narrator Win11 23H2
- Safari 17.5 / VoiceOver / macOS 14.5
- Firefox 128 / TalkBack 14.2 / Android 14
- Chrome 127 / TalkBack 14.2 / Android 14
- Safari 17.5 / VoiceOver / iPadOS 17.5.1
7 Comments
Great work Adrian, really useful.
Very intelligent idea. Thank you for writing about it.
This is wonderfully comprehensive and shareable to a wide audience. I’ll definitely pass along to our front-end and content teams. Thank you!
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 .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.
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; themark
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, 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