Tweaking Text Level Styles, Reprised
In 2017 I wrote Tweaking Text Level Styles (terrible name in retrospect) and I made regular updates over the years. Stop reading it. Remove it from your bookmarks. Unlink it from your posts. Print it onto paper and then burn it.
The conclusions and techniques in that post were based on the state of the art when I wrote it. If you have been treating that post as the current ideal approach then I failed to convey that the post, and all its embedded demos and code samples, are really just a place to start your own testing.
So now I am being explicit — this post is a starting point. You need to take it from here.
I include the corresponding ARIA roles because sometimes people do terrible things they later regret. ARIA role does not bring any styles with them, so I add the browser defaults from their HTML counterparts.
Jump to the Wrap-up at the end of the post if you just want guidance on what to do.
Demo
Use this for your own testing (visit it directly). Or make a better one.
Testing kit:
- Firefox 137
- Chrome 135
- Edge 135
- Safari 18.4
- NVDA 2024.4.2
- JAWS 2024.2411.16
- Narrator Win11 24H2
- VoiceOver / macOS 15.4
- Orca / Ubuntu 24.10
- TalkBack 15.2 / Android 15
- VoiceOver / iPadOS 18.4
- Contrast Themes in Win11 24H2
- Print preview in each desktop browser
The <mark>
Element
Use <mark>
to indicate a run of text as some form of reference. Get more detail from the WHATWG HTML spec for <mark>
.
Default Styles
The browser default styles are fine. They lean on CSS keywords, so they’ll adapt for forced-colors mode. You probably should not change them unless you have a genuine contrast issue with your own styles.
If somebody has tricked you into using the ARIA mark
role, then you will need to replicate the browser’s default styles:
[role="mark"] {
background-color: Mark;
color: MarkText;
}
Print Styles
Not every system can print (background) color and not every user wants to. You may want to add a custom print style. An outline is probably not too disruptive and it can scale with the text. I don’t set a text color because my testing showed it wasn’t necessary when the background color wasn’t being printed.
@media print {
mark {
border: 1pt dotted;
}
}
If someone is still forcing you to use the ARIA role, blink twice for help and add a [role="mark"]
selector. I won’t do it for you.
Screen Reader Output
This represents the spoken output from a few screen readers using default settings. You may experience different results depending on your screen reader voice, TTS engine, language pack, custom dictionary, settings, or if you use a Braille display.
- NVDA / Firefox
- Precedes with “highlighted”, follows with “out of highlighted”.
- Applies for both read-all and virtual cursor
- JAWS / Chrome
- Precedes with “mark” in a raised pitch, follows with “end mark” in a raised pitch.
- Applies for both read-all and virtual cursor
- Narrator / Edge
- No announcement, but precedes with a pause, follows with a pause.
- Applies for both read-all and virtual cursor
- VoiceOver / macOS / Safari
- When using read-all, it ignores all text in the paragraph up to the marked-up text, then announces as the virtual cursor does.
- With the virtual cursor, announces “highlighted” at the start, follows with a pause.
- With the virtual cursor, when the text wraps it announces “highlighted” at the start of each new line.
- Orca / Firefox
- Precedes with “highlight start”, follows with “highlight end”.
- Applies for both read-all and virtual cursor
- TalkBack / Chrome
- Pauses at the start of the node, follows the node with “highlight”.
- Applies for both read-all and virtual cursor
- VoiceOver / iPadOS / Safari
- Nothing exposed using read-all.
- Using the virtual cursor, follows with “highlighted”.
The <del>
Element
Use <del>
to indicate a run of text has been deleted or removed or stricken. Get more detail from the WHATWG HTML spec for <del>
.
Default Styles
Both <del>
and <s>
are assigned the same style in browsers. It takes the color of the text, so contrast should never be an issue.
If somebody is holding your favorite mug hostage and making you use the ARIA deletion
role, then you will need to replicate the browser’s default styles:
[role="deletion"] {
text-decoration: line-through;
}
Enhanced Styles
The default thickness of that strike-through line might be too thin for your tastes. Before changing the color or position, instead try it a bit thicker.
del {
text-decoration-thickness: .15em;
}
If someone is still forcing you to use the ARIA role, signal for help by quietly tapping a Fibonacci sequence and add a [role="deletion"]
selector. I won’t do it for you.
Screen Reader Output
This represents the spoken output from a few screen readers using default settings. You may experience different results depending on your screen reader voice, TTS engine, language pack, custom dictionary, settings, or if you use a Braille display.
- NVDA / Firefox
- Precedes with “deleted”, follows with nothing.
- Applies for both read-all and virtual cursor
- JAWS / Chrome
- Precedes with “deletion” in a raised pitch, follows with “end deletion” in a raised pitch.
- Applies for both read-all and virtual cursor
- Narrator / Edge
- No announcement, but precedes with a pause, follows with a pause.
- Applies for both read-all and virtual cursor
- VoiceOver / macOS / Safari
- When using read-all, it ignores all text in the paragraph up to the marked-up text, then announces as the virtual cursor does.
- With the virtual cursor, announces “deletion” at the start, follows with a pause.
- With the virtual cursor, when the text wraps it announces “deletion” at the start of each new line.
- Orca / Firefox
- Precedes with “deletion start”, follows with “deletion end”.
- Applies for both read-all and virtual cursor
- TalkBack / Chrome
- Pauses at the start of the node, follows the node with “deletion”.
- Applies for both read-all and virtual cursor
- VoiceOver / iPadOS / Safari
- Nothing exposed using read-all.
- Using the virtual cursor, opens with “deletion”.
The <ins>
Element
Use <ins>
to indicate a run of text has been inserted. Get more detail from the WHATWG HTML spec for <ins>
.
Default Styles
The default style for <ins>
is an underline. It takes the color of the text, so contrast should never be an issue.
If somebody is sneaking into your code at night and swapping it with the ARIA insertion
role, then you will need to replicate the browser’s default styles:
[role="insertion"] {
text-decoration: underline;
}
Enhanced Styles
Since underlines are generally reserved for links (except for fancy sites that are trying to fail WCAG), this could be confusing for users if the context isn’t clear. Instead of changing the color or position, try a style variation. Like wavy.
ins { text-decoration-style: wavy; }
If someone is still forcing you to use the ARIA role, and the police won’t respond, add a [role="insertion"]
selector. I won’t do it for you.
Screen Reader Output
This represents the spoken output from a few screen readers using default settings. You may experience different results depending on your screen reader voice, TTS engine, language pack, custom dictionary, settings, or if you use a Braille display.
- NVDA / Firefox
- Precedes with “inserted”, follows with nothing.
- Applies for both read-all and virtual cursor
- JAWS / Chrome
- Precedes with “insertion” in a raised pitch, follows with “end insertion” in a raised pitch.
- Applies for both read-all and virtual cursor
- Narrator / Edge
- No announcement, but precedes with a pause, follows with a pause.
- Applies for both read-all and virtual cursor
- VoiceOver / macOS / Safari
- When using read-all, it ignores all text in the paragraph up to the marked-up text, then announces as the virtual cursor does.
- With the virtual cursor, announces “insertion” at the start, follows with a pause.
- With the virtual cursor, when the text wraps it announces “insertion” at the start of each new line.
- Orca / Firefox
- Precedes with “insertion start”, follows with “insertion end”.
- Applies for both read-all and virtual cursor
- TalkBack / Chrome
- Pauses at the start of the node, follows the node with “insertion”.
- Applies for both read-all and virtual cursor
- VoiceOver / iPadOS / Safari
- Nothing exposed using read-all.
- Using the virtual cursor, opens with “insertion”.
The <s>
Element
Use <s>
to indicate a run of text is no longer accurate (but really this means stricken). Get more detail from the WHATWG HTML spec for <s>
.
Default Styles
Both <del>
and <s>
are assigned the same style in browsers. It takes the color of the text, so contrast should never be an issue.
If for some reason you hate HTML and prefer ARIA (or, I dunno, React) and use the ARIA deletion
role, then you will need to replicate the browser’s default styles:
[role="deletion"] {
text-decoration: line-through;
}
Enhanced Styles
The default thickness of that strike-through line might be too thin for your tastes. Before changing the color or position, instead try it a bit thicker.
del {
text-decoration-thickness: .15em;
}
If someone is still forcing you to use the ARIA role, it’s too late for you, but before you expire add a [role="deletion"]
selector. I won’t do it for you.
Screen Reader Output
This represents the spoken output from a few screen readers using default settings. You may experience different results depending on your screen reader voice, TTS engine, language pack, custom dictionary, settings, or if you use a Braille display.
- NVDA / Firefox
- Precedes with “deleted”, follows with nothing.
- Applies for both read-all and virtual cursor
- JAWS / Chrome
- Precedes with “deletion” in a raised pitch, follows with “end deletion” in a raised pitch.
- Applies for both read-all and virtual cursor
- Narrator / Edge
- No announcement, but precedes with a pause, follows with a pause.
- Applies for both read-all and virtual cursor
- VoiceOver / macOS / Safari
- When using read-all, it ignores all text in the paragraph up to the marked-up text, then announces as the virtual cursor does.
- With the virtual cursor, announces “deletion” at the start, follows with a pause.
- With the virtual cursor, when the text wraps it announces “deletion” at the start of each new line.
- Orca / Firefox
- Precedes with “deletion start”, follows with “deletion end”.
- Applies for both read-all and virtual cursor
- TalkBack / Chrome
- Pauses at the start of the node, follows the node with “deletion”.
- Applies for both read-all and virtual cursor
- VoiceOver / iPadOS / Safari
- Nothing exposed using read-all.
- Using the virtual cursor, opens with “deletion”.
Wrap-up
Here are the key takeaways:
- Use the HTML elements instead of ARIA because you get the user agent styles for free.
- You don’t need to adjust the default styles, but if you do it anyway be mindful of contrast, sizing units, forced-colors mode, print styles, and language.
- Do not make any changes for screen readers — do not use CSS generated content and do not use
aria-roledescription
.- Narrator only has 0.7% market share as the primary desktop screen reader according to the Screen Reader User Survey #10 Results (which is not necessarily the real number). Its users already experience plenty of other gaps, so maybe don’t degrade the experience for all other screen reader users by adding noise.
- VoiceOver on macOS has a significant bug, but it’s not insurmountable by users because they can use the virtual cursor. At 9.7% usage (it’s the only screen reader on macOS), I suggest against trying to code workarounds that degrade the experience for all other screen reader users.
- I reported this macOS VoiceOver bug in January 2025 as #286267 AX: VoiceOver does not announce text preceding some phrasing elements. It looks like the bug has gotten worse since it now impacts the elements
<del>
,<ins>
, and<s>
when they have no redundant role.
- This is not just a departure from my 2017 post, but a refutation of nearly everything I said there. But the good news is that old post is still a swell history of where we were and what bugs were fixed.
Ok bye.
8 Comments
Please be aware: in some language, such as zh-hant-TW, the wavy underline is used to mark book titles. Please refer to https://language.moe.gov.tw/001/Upload/FILES/SITE_CONTENT/M0001/HAU/h12.htm (content in zh-hant-TW).
In response to .And that right there is exactly why I say to be mindful of language in the second bullet under Wrap-up. Thanks!
Some results from the previous VoiceOver versions:
macOS 14.6.1 (Safari 17.6): Same support and behavior with
<mark>
. No support for<del>
or<ins>
, howeverrole="deletion"
androle="insertion"
are supported, with the same reading behavior as<mark>
. No support for<s>
.iOS 17.7: Same support and behavior with
<mark>
,<ins>
and<del>
. No support for<s>
.
In response to .Thanks, SiblingPastry! For those reading along, James had helped validate some of my tests and, while running vaguely older kit, was able to gather other results. I refused to include it and asked him to leave it as a comment instead. Something something dark side, something something ego.
Glad to see improvements from the testing Steve Faulkner did in 2023.
Adding more VoiceOver results, Safari 18.4 on macOS 14.7.4 (Sonoma) is the same as macOS 15.4 (Sequoia). Safari 18 is available for macOS 15, 14, and 13 (Ventura) which run on Mac hardware from 2017 and newer (or older, with unofficial patches).
I made a table of the test results.
In response to .Indeed, and better than the two 2024 updates I wrote after including Steve’s 2023 update in my last post. All taken together, there has been slow but positive movement for a while and that trend is hopefully clear. I appreciate the additional older macOS support notes.
Are there any differences if you add the appropriate ARIA roles to the appropriate HTML text level elements, e.g.
<del>
?
Sometimes support for an ARIA role is better than for the HTML element that maps to it, aso there’s no harm in adding the role to the corresponding element, though it should be redundant.
Great pot though, as someone battling rustration around lack of screen reader support for text level elements since 2010 it is good to see significant progress.
In response to .Are there any differences if you add the appropriate ARIA roles to the appropriate HTML text level elements […]?
Nope. Not in my tests with the kit I outlined. I would have noted it.
However, four comments above yours James gives results from his own Safari 17.x testing and includes how the roles impacted them.
Leave a Comment or Response