Is an “AI” company offering to provide spoken versions of your pages for users? Is an overlay company promising to make your content more accessible by its overlay speaking it? Is some other vendor pitching you on some kind of thing that reads your web pages aloud to users?
You don’t need it.
Or, rather, your users may not need it. Their browser already offers it. At no cost to you. Nor them.
These features may not appear for pages that are not well-formed (lack a main region, are nothing but div-soup, etc.). If they don’t appear for your content, that’s a signal that you may have work to do on the underlying HTML. Any fixes you make would likely result in a better experience for everyone.
Instructions for common browsers:
In most cases, users can choose the reading speed and voice, pause or restart it, and even jump back and forth through paragraphs. This is more control than third-party products offer.
If an “AI” company offered on-demand or pre-recorded audio, they’re leveling up our overall destruction of the environment.
If an overlay vendor offered it through its overlay, you’re adding legal risk, trusting them to not be liars, letting them track your users, and assuming it will work. Because, as I’ve documented extensively, overlays don’t work (sometimes at all but usually as advertised).
Any other vendor is probably a mix of data harvester, crypto miner, scam platform, or government panopticon node.
Users who most benefit from a page being spoken to them know about browsers’ built-in features. Those users may rely on extensions for even more control. They are unlikely to benefit from (or care about) these third-party offerings.
If you have good research, not provided by the vendor, that your specific users are asking for this specific feature then sure. Go for it. But also track its use and understand you’re taking on the vendor’s risks as your own — for those users whose blockers don’t block it.
]]>The current state of affairs in the three rendering engines…
Firefox on Android supports scaling a web page’s text if you adjust the system font size. It doesn’t seem to care if you use px units. You don’t need to do anything special; it just works.
Safari will not scale a web page’s text with the OS settings unless the author adds some CSS. This code points to the system font properties (such as size):
body {
font: -apple-system-body;
/* other styles, including font-family */
}
This feature query checks for support while trying to limit it to touch devices only, then sets the font size:
@supports (font: -apple-system-body) and (not (-webkit-touch-callout: default)) {
:root {
font-size: 100%;
}
}
The rest of your styles have to use relative units or this breaks down.
Google has opted not to follow Firefox’s lead and support OS text sizes by default. Google has also opted not to mimic Safari and make platform-specific features. Instead, Google has decided to use its weight in the CSSWG to mint a new value for the <meta> element:
<meta name="text-scale" content="scale">
For this to work, however, the author will need to “opt-in” by not setting a base font size in your CSS:
body {
/* font-size: yeah, no */
}
You actually can set one, but it should be a percentage. You then need to avoid fixed units (such as px) throughout your site. If this seems familiar, it’s the core thesis of my aptly-named post The Ultimate Ideal Bestest Base Font Size That Everyone Is Keeping a Secret, Especially Chet.
Read Josh Tumath’s post Try text scaling support in Chrome Canary for more details.
Let’s combine those into one set of code that will work in Safari and now Chrome. Firefox was always on point, so no effort needed there.
Add this HTML to the <head> of your page (it’s the CSSWG code):
<meta name="text-scale" content="scale">
Add this to the CSS of your page (it’s Apple’s code):
body {
font: -apple-system-body;
/* other styles, including font-family */
}
@supports (font: -apple-system-body) and (not (-webkit-touch-callout: default)) {
:root {
font-size: 100%;
}
}
Done.
I made a demo. It has images and a table. Visit the debug version in your mobile browser.
See the Pen Untitled by Adrian Roselli (@aardrian) on CodePen.
Seriously, visit the debug version in your mobile browser. I’m not dealing with desktop here.
With your Android device, install Chrome Canary from the Google Play store. If you are using an Android device with another app store, I leave it to you to sort that out. Then, using chrome://flags, enable Experimental Web Platform Features in Chrome Canary.
In Android, go to Settings → Accessibility → Display size & text; in the Size options section, go to Font size and make it as large as possible.
In iDevices, go to Settings → Accessibility → Display & Text Size → Larger Text, toggle Larger Accessibility Sizes, then drag that slider all the way to the to max.
These are before (small text) and after (largest text) images of Frankenstyle’s Demo. Your experience may differ. If you’re reading this from the future, it may differ a lot.
None of these required a browser reload. It may take a moment, so be patient.
Things that may better inform those words and pictures above:
env(preferred-text-scale) (not addressed in this post)<meta> element<meta> might better belong in WHATWG HTML, I could find no corresponding issue with WHATWG.
In 2012 I vented about TypeButter using <kern style="letter-spacing: -0.01em;"> for each letter. In 2020 I noted the AWWWards site wrapped every letter in a <div> for animation, which screen readers presented letter by letter. In 2022, it was BeeLine Reader using <span>s to achieve gradients across a word.
In 2026 I am finally writing about it because GSAP has its SplitText plug-in asserting screen reader support that doesn’t stand up to use. I appreciate the embedded video explains how it should work, and even includes an unnamed screen reader demo, but GSAP assumes authors will use SplitText in a very specific way and fails to note potential problems. That’s a bummer because now I have to be the buzzkill.
This is the animation demo on the SplitText page (or go to the debug view if you want to test it):
See the Pen SplitText Demo by GSAP (@GreenSock) on CodePen.
I also made a fork, just in case GSAP changes the code later (which would mess with future testing).
The simplest way to test this is to fire up a screen reader and try navigating the page with whatever method you prefer. You may get different results than I did, but that’s the fun of testing! Though I did get confirmation from one, then another, then a third screen reader user.
If you press the “Characters” button, this is the HTML output of the first line of text (which is in its own <div>):
<div aria-hidden="true" style="position: relative; display: block; text-align: center;">
<div aria-hidden="true" style="position: relative; display: inline-block;">
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">B</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">r</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">e</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">a</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">k</div>
</div>
<div aria-hidden="true" style="position: relative; display: inline-block;">
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">a</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">p</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">a</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">r</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">t</div>
</div>
<div aria-hidden="true" style="position: relative; display: inline-block;">
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">H</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">T</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">M</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">L</div>
</div>
<div aria-hidden="true" style="position: relative; display: inline-block;">
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">t</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">e</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">x</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">t</div>
</div>
<div aria-hidden="true" style="position: relative; display: inline-block;">
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">i</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">n</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">t</div>
<div aria-hidden="true" style="position: relative; display: inline-block; translate: none; rotate: none; scale: none; opacity: 1; transform: translate(0px);">o</div>
</div>
</div>
Five words.
The demo uses a <div>, which in the HTML AAM maps to the generic role. The problem here is that the generic role does not allow itself to be named by the author — which means aria-label is prohibited on it.
The example code GSAP offers, however, shows headings instead (which allow aria-label). It makes no mention of the restrictions on which roles allow aria-label. An author might think nothing of using aria-label on a <div>. Kind of like the embedded GSAP demo.
When aria-label is used on an element that allows it, the GSAP page makes no mention of other risks. I’ve repeatedly said aria-label may not auto-translate for users. It also doesn’t talk about WCAG SC 2.5.3 Label in Name risks when aria-label is applied to any control.
Kind of a nitpick, but the GSAP page links to a CSS-Tricks post that itself simply links to Scott’s Inclusively Hidden post. Scott’s post got an update in 2023. CSS-Tricks’ post did not. I consider that a disservice to both Scott and readers.
I hate making these videos. It takes so much time. But it’s evidence. Or proof. Or something.
I was going to make videos for VoiceOver iPadOS with Safari, TalkBack with Chrome, and TalkBack with Firefox, but I got tired. If you’ve been reading my blog long enough, then you should know the commands to give it a try.
Similarly, you can pop open the Braille viewers on the desktop and see how those perform.
I’ve reduced these to a binary yes/no. I also added mobile results, for which I made no videos.
| Pairing | Works? |
|---|---|
| NVDA / Firefox | yes |
| JAWS / Chrome | no |
| Narrator / Edge | no |
| VO macOS / Safari | no |
| Orca / Firefox | no |
| TalkBack / Chrome | yes |
| TalkBack / Firefox | no |
| VO iPadOS / Safari | no |
This post is a warning to authors. I’ve also filed an issue with GSAP asking the SplitText page to clarify the risks and limitations: #642 Screen Readers do not expose SplitText
I don’t expect them to be able to jump on the bug report immediately, and they will almost definitely want to perform further testing. So until they can tackle it, I strongly recommend avoiding SplitText.
If you need to split words into their constituent letters in order to adjust kerning, give them gradients, animate them, or whatever, well, no you don’t. Find another method.
If the GSAP people who guaranteed their approach works with screen readers got it wrong, it seems likely you will too. Unless you have all the screen reader, browser, platform, and TTS variations along with the screen reader navigation skills needed to perform ongoing and robust testing.
Which you don’t.
Turn your terminal intois completed letter by letter first with
an interface designer,which is deleted letter by letter and replaced with
a frontend wizard,and then replaced by
an accessibility expert.The browser dev tools show it’s all in a
<span> with an aria-label.
The makers of Tailwind, dissatisfied with pushing verbose class names on human authors only, have decided to target LLMs with ui.sh (pronounced “wish”). While this hilarious, non-parody home page doesn’t make the mistake of wrapping every letter in its own element, it does make the mistake of using aria-label.
I’ve decided it warrants a mention here.
The good news is this will help guarantee the need for human accessibility practitioners over poorly-trained LLMs.
Jeffrey Yasskin says the W3C TAG was looking at a proposal for <canvas> to split a JavaScript string into glyphs to render them in the canvas, with the TAG asking for the proposal to squeeze those “letter” into the DOM. I say “was” because as of yesterday the TAG closed it as “unsatisfied.” Read the TAG’s (via Matt) reasoning for why. Also, Jeffrey added more context on Masto.
Are the below codes equivalent if we consider all the aspects? (a11y, semantic, something else maybe?)
If not, what is missing (or should be changed) in the second code
![]()
I have my canned response that aria-label auto-translation is inconsistent.
But the something else maybe
question is what reminded me that this construct has caused issues outside of WCAG concerns. In particular, the only assistive technologies (AT) that consume ARIA are screen readers and, to a far lesser extent, voice control. That latter part only because browsers assemble the accessible names, not AT. There’s plenty more AT that never touches ARIA.
I knew there were issues, but couldn’t rattle them off from the top of my head. So I built some examples and poked them with other accessibility features of browsers.
I am not testing screen readers nor voice control.
aria-label value of any link.aria-hidden, regardless of other attributes.aria-label value of any link.aria-hidden, regardless of other attributes.aria-hidden.
aria-hidden.
aria-label value of any link.aria-hidden links, though that changed when I got more content on the page.
As Amelia Bellamy-Royds points out, the text-to-speech features seem to respect the aria-hidden but ignore the aria-label — and by extension the accessible name the browser provides in the accessibility tree.
Three key takeaways here (yes, I know your use case is special):
aria-label on links;aria-hidden within links;I look forward to you, dear reader, trying other approaches and letting me know where these fall down (or what I got wrong). I don’t need to know where they are supported. Just the gaps.
These are the tests I used to generate the results you just read (originally as a Codepen). You can ignore this part unless you want to run your own tests. If you want to find other ways these approaches might break for users, then please do so.
I used the Korean words 샌드위치 for “sandwich” and 망치 for “hammer” (the values of aria-label).
The links with aria-label all fail WCAG SC 2.5.3 Label in Name, but it’s intentional so I can quickly tell if the aria-label is exposed. Similarly, the links with aria-hidden and no aria-label fail 4.1.2 Name, Role, Value, but again I wanted to see how they performed.
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Sandwich">
Sandwich
</a><a href="proxy.php?url=https://ko.wikipedia.org/wiki/%EC%83%8C%EB%93%9C%EC%9C%84%EC%B9%98" hreflang="ko" lang="ko">
샌드위치
</a>aria-label: Sandwich
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Sandwich" aria-label="hammer">
Sandwich
</a>aria-label: 샌드위치
<a href="proxy.php?url=https://ko.wikipedia.org/wiki/%EC%83%8C%EB%93%9C%EC%9C%84%EC%B9%98" hreflang="ko" lang="ko" aria-label="망치">
샌드위치
</a><span>:
Sandwich
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Sandwich">
<span>
<span>S</span><span>a</span><span>n</span><span>d</span><span>w</span><span>i</span><span>c</span><span>h</span>
</span>
</a><span>:
샌드위치
<a href="proxy.php?url=https://ko.wikipedia.org/wiki/%EC%83%8C%EB%93%9C%EC%9C%84%EC%B9%98" hreflang="ko" lang="ko">
<span>
<span>샌</span><span>드</span><span>위</span><span>치</span>
</span>
</a>aria-label and each letter of visible text in its own <span>:
Sandwich
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Sandwich" aria-label="hammer">
<span>
<span>S</span><span>a</span><span>n</span><span>d</span><span>w</span><span>i</span><span>c</span><span>h</span>
</span>
</a>aria-label and each letter of visible text in its own <span>:
샌드위치
<a href="proxy.php?url=https://ko.wikipedia.org/wiki/%EC%83%8C%EB%93%9C%EC%9C%84%EC%B9%98" hreflang="ko" lang="ko" aria-label="망치">
<span>
<span>샌</span><span>드</span><span>위</span><span>치</span>
</span>
</a>aria-hidden on visible text with each letter of visible text in its own <span>:
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Sandwich">
<span aria-hidden="true">
<span>S</span><span>a</span><span>n</span><span>d</span><span>w</span><span>i</span><span>c</span><span>h</span>
</span>
</a>aria-hidden on visible text with each letter of visible text in its own <span>:
<a href="proxy.php?url=https://ko.wikipedia.org/wiki/%EC%83%8C%EB%93%9C%EC%9C%84%EC%B9%98" hreflang="ko" lang="ko">
<span aria-hidden="true">
<span>샌</span><span>드</span><span>위</span><span>치</span>
</span>
</a>aria-label and aria-hidden on visible text with each letter of visible text in its own <span>:
<a href="proxy.php?url=https://en.wikipedia.org/wiki/Sandwich" aria-label="hammer">
<span aria-hidden="true">
<span>S</span><span>a</span><span>n</span><span>d</span><span>w</span><span>i</span><span>c</span><span>h</span>
</span>
</a>aria-label and aria-hidden on visible text with each letter of visible text in its own <span>:
<a href="proxy.php?url=https://ko.wikipedia.org/wiki/%EC%83%8C%EB%93%9C%EC%9C%84%EC%B9%98" hreflang="ko" lang="ko" aria-label="망치">
<span aria-hidden="true">
<span>샌</span><span>드</span><span>위</span><span>치</span>
</span>
</a>Don’t be shy about making your own variations and leaving your results in the comments.
You know what? Just don’t split words into letters. It’s not just a problem with links, even if it’s more excitingly wrong with links.
]]>
If you’re here because your live region isn’t working as you expect, then your expectation is wrong, you did something wrong, or you found a fun bug. Read Pat’s Why are my live regions not working? instead of this post.
If you want a quick review of current support and simple test cases with testing steps, then keep reading. Otherwise, I dunno, close this tab?
I made this demo in mid-2024 to demonstrate a problem. It now covers five kinds of live regions:
aria-live="polite";aria-live="assertive";role="alert";<output> elementaria-describedby whose value changes, triggering an event.That last one is a bit of a curve ball because it isn’t a live region, but it can functionally behave like one. Many authors are unaware of this, and for those who are aware, many think it’s a bug. The example uses a button, text input, and select menu. The button fires on activation, the two fields on change. Give the live region time to clear before moving to the next control. When testing, be sure you are hearing the de facto live region and not that you moved focus to the subsequent control with a description.
The demo also shows how these live regions work when hidden using any of three methods:
hidden;aria-hidden;display:none.As always, I have a debug view without the Codepen cruft so you can test more easily.
See the Pen Live Regions, Hidden and Not by Adrian Roselli (@aardrian) on CodePen.
The detailed results that follow the table go into specific versions and browsers used. Failure for a screen reader to support a feature properly may be a function of the browser, not the screen reader (especially with dynamic descriptions). Other bits:
| Screen Reader / Browser | polite, Audio | polite, Braille | assertive, Audio | assertive, Braille | alert Role, Audio | alert Role, Braille | alert Role, Audio “alert” | alert Role, Braille “alert” | <output>, Audio | <output>, Braille | aria-describedby, Audio | aria-describedby, Braille | Hidden Regions Hidden |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| NVDA / Firefox | polite | assertive | polite | assertive | assertive | buggy | yes | buggy | polite | assertive | no | no | yes |
| JAWS / Chrome | polite | assertive | polite | assertive | polite | assertive | no | no | polite | assertive | polite? | assertive? | yes |
| Narrator / Edge | polite | assertive | polite | assertive | polite | assertive | no | no | polite | assertive | no | no | yes |
| VoiceOver macOS / Safari | buggy | buggy | assertive | assertive | assertive | assertive | no | no | buggy | buggy | no | no | yes |
| Orca / Firefox | polite | polite | buggy | buggy | polite | no | no | yes | |||||
| TalkBack / Chrome | polite | polite | polite | no | polite | no | no | yes | |||||
| VoiceOver iDevice / Safari | polite | assertive | assertive | no | polite | no | no | yes |
For the read-all testing on macOS, I would activate it (Ctrl + Opt + A), then when a button had focus I would hit Enter. I could visually confirm the live region was populated and I had the Braille emulator running. NVDA’s read-all (Caps Lock + A) and JAWS’ read-all (Caps Lock + ↓) does not leave programmatic focus on controls while reading so the tactic does not work there.
I’m not a daily screen reader user and I used the Braille viewers built into each screen reader, so they may not reflect actual hardware exposure to users.
alert role does not pre-pend announcement with alert.
alert role does not pre-pend announcement with alert.
alert role does not pre-pend announcement with alert.
alert role treated as assertive (interrupts announcement but goes back and re-announces content it interrupted).alert role pre-pends announcement with alert.
alert role message appeared in the Braille emulator.<output> live regions treated as polite (announces at first break, which is not at end of sentence).alert role treated as assertive (interrupts announcement but goes back and re-announces content it interrupted).alert role pre-pends announcement with alert.
alert role was expsed in the Braille emulator only as “alert”, without the message..<output> live regions treated as polite (announces at first break, which is not at end of sentence).alert role treated as assertive (interrupts announcement but goes back and re-announces content it interrupted).alert role pre-pends announcement with alert.
alert role showed alertin Braille emulator, but not the rest of the message.
alert role was expsed in the Braille emulator only as “alert”, without the message..<output> is not announced at all. Likely a Narrator bug since it is not announced in Chrome or Firefox either. Though Chrome and Firefox both show it as a status region in Microsoft Accessibility Insights, Edge does not seem to expose it. Chatter on the socials suggests it might be a UIA bug.alert role does not pre-pend announcement with alert.
alert role does not pre-pend announcement with alert.
aria-live="polite" is not announced during read-all (Ctrl + Opt + A), but acts assertive in Braille displays and some content is lost.aria-live="assertive" treated as assertive (interrupts announcement, stops reading).alert role treated as assertive (interrupts announcement, stops reading).alert role does not pre-pend announcement with alert.
<output> is not announced during read-all (Ctrl + Opt + A), but acts assertive in Braille displays and some content is lost.aria-live="polite" is not announced during read-all (Ctrl + Opt + A), but acts assertive in Braille displays and some content is lost.aria-live="assertive" treated as assertive (interrupts announcement, stops reading).alert role treated as assertive (interrupts announcement, stops reading).alert role does not pre-pend announcement with alert.
<output> announces twice and appears in Braille emulator twice.alert role not announced at all.alert role not announced at all.alert role does not pre-pend announcement with alert.
alert role does not pre-pend announcement with alert.
aria-live="assertive" and alert role treated as assertive (interrupts announcement but goes back and re-announces content it interrupted).alert role does not pre-pend announcement with alert.
aria-live="assertive" and alert role treated as assertive (interrupts announcement but does not go back and re-announces content it interrupted).alert role does not pre-pend announcement with alert.
This reflects support as noted for the latest releases in the Detailed Results section. If you find something amiss, leave a comment. It’s possible I got something wrong during transcription or testing. Or just, you know, life.
]]>
It’s a fascinating thread given all the international participation. It’s also outside the scope of this post.
Instead, this post is to tell you not to stress about what keys a screen reader uses. This is because screen readers have pass-through commands, which tell the screen reader to ignore the next key or combo and pass it through to the application (whether an installed application or the web browser running your application).
Windows desktop screen readers have explicit pass-through commands:
VoiceOver on macOS does as well, and I’m being very careful to qualify this as macOS, not iOS nor iPadOS:
VoiceOver on macOS differs from Windows screen readers by requiring the modifier keys to execute most commands. But if you enable Quick Nav to allow single key use, you may need to turn it off sometimes (which is not a pass-through):
Orca generally relies on a modifier key except in structural navigation, which you may need to disable (and is not the same as a pass-through):
In each case, the screen reader modifier key (NVDA or VO, for example) represents the Caps Lock or Insert (depending on preferences and keyboard layout), and for VoiceOver are the Ctrl + Opt keys combined.
This, of course, assumes the screen reader audience for your application knows they exist. Not all screen reader users are expert screen reader users (imagine the same breakdown as with the general public and their computers). But this is stuff you would include in documentation and training (because you have documentation, right?) while also telling your developers and QA folks so they don’t file unnecessary bugs.
James Scholes told me that VoiceOver on macOS does indeed have a pass-through command. I updated the post accordingly. I didn’t do my usual strikes to show the changes, mostly because it was getting unwieldy to read (I moved the Orca section to after macOS).
]]>
I’ve helped evaluate ACRs for clients looking to purchase a product but who want guarantees that the product is accessible. The existence of an ACR doesn’t mean the product is accessible or even conformant, nor does it mean the ACR itself is accurate. However, it’s often the first step to identifying how a company presents the accessibility of its offerings. I often remind my clients looking to acquire a product that “ACR or GTFO” is only a starting point.
This post outlines, at a very high level, some of the steps I take when evaluating an ACR and, by extension, a vendor. You may have other approaches. You should comment with your own. Unless you feel it’s some sort of trade secret. Which is weird.
This list is not exhaustive and not always practical. It matches the front matter provided in the template.
This list addresses the content of the report itself.
Way back in 2011, Karl Groves wrote Why a Third Party Should Prepare Your VPAT and I think it’s useful for evaluating ACRs. In 2023 he also wrote A quick guide to reviewing a VPAT ACR, which is far longer than this post. Probably read it after this one for a more detail. There is also WebAIM’s process for vetting ACRs as another reference point, from early 2024.
]]>Cases through the end of 2025 where I have used generated images which were not part of the core subject of the post (as a critique or evaluation):
You can see where my fascination and experimentation dropped off. My use of generated images was never acceptable. Not because I was depriving designers of work, but because the work I generated was based on theft. I did it anyway. Some of the images were meant to demonstrate failures of the technology, but not all. In time I hope to replace nearly all generated images (keeping the ones where I am evaluating the output).
Cases through the end of 2025 where I have written about ‘AI’ as the core subject of a post:
I think it’s fair to say this blog has not turned into a pro- or anti-‘AI’ collection of posts. Generally I only touch on it when it directly impacts my work.
All this together is me coming clean where I have cheated (images) and also committing to you (and me) that this has always been and will continue to be a site written by a human (still me). On top of that, this site is not following the fad of trying to chase the trend and write about every iteration of ‘AI’.
Similarly, this site has no ads, does not gather nor sell your information, and has no third-party trackers. That said, I am exploring adding some kind of analytics to the site — but not Google.
Like most writers, my content has also been stolen. I am not actively poisoning LLMs, but I rather want to.
When reading content on other sites, if I sense an article or post has been generated, drafted, edited, or otherwise touched by an LLM, I close the site and add it to a block list. I should note that I have been much less strict about generated images when reading content on other sites. Yes, I still have to reconcile that. I am still torn about cases of LLM-generated image alternatives, but if it’s from an accessibility practitioner and has clearly not been reviewed, then possible block. I accept auto-generated closed captions because I recognize those are the only captions I will generally get.
I look forward to a 2026 crafted by humans, even if it means I have to block everything else.
]]>Broadly, when someone says something is “accessible” that’s a hopeful statement that is based on some best efforts. Of course, there are bad actors who assert something is accessible because they just want the clicks or don’t invest much effort. Others assert their pet features are to “make something accessible” but are unable to explain how. Some even promise the technology to do it — even formerly legitimate practitioners.
There are many people who think making a web site that passes WCAG 2.2 at Level AA makes it accessible. I’ve documented many cases where that’s not true, as have others, some for years now.
If you think passing WCAG at Level AAA will be more accessible, then I know plenty of people who’d like to have a conversation with SC 1.4.6: Contrast (Enhanced) for encoding eye pain.
I keep trying to stress (with authors, clients, spec writers, GitHub randos, LLM aficionados, …) that accessibility is about people. It is not a strictly technical problem to be solved with code.
Because people have varying needs across disparate contexts from assorted expectations with unequal skill levels using almost random technologies, never mind current moods and real-life distractions, to suggest one thing will be accessible for everyone in all those circumstances is pure hubris. Or lack of empathy. Maybe a mix.
I’m not suggesting that claiming something is “accessible” is an overtly bad act. I am saying, however, that maybe you should explain what accessibility features it has, and let that guide people. It’s more honest to them and you.
This is why we keep working at accessibility. To shrink those cases. To move halfway to the wall, over and over, until we cannot slide a sheet of paper between our nose and the cold vinyl siding of reality. That’s why we keep up the work. That’s why we continue to try. To make things better for yet more people, even if we can’t cover everyone.
Stop beating yourself up and be wary of those who maybe aren’t.
]]>
Web developers around the world have for years given a nod to Saturnalia solstice Isaac Newton’s birthday Yule wassailing mummering end of Gregorian calendar year Christmas with advent calendars covering web-related topics. As a result, you may (should) recognize some of the ones listed below.
Every year I miss a few on December 1, so add a comment or hit me up on the Fediverse if you have more. I delayed posting again this year because I am a bit tired of people who just copy the entire thing and share their version on the socials. Also, this content originally appeared on AdrianRoselli.com and if you aren’t reading it there then the site you are on is a thieving site which should be held in the most contemptible disdain. Happy holidays!
I have not included advent calendars that are single blog posts, image-only efforts with inaccessible images, or delivered only via email. It would be a terrible gift from me to you if you sign up for spam or end up taking advice from organizations that are clearly bad at advice. Maybe next year I’ll ban LinkedIn posts. I also don’t evaluate the quality of the calendars (hard to do when most have barely opened their first day).
I am happy to see more of the calendar creators are available on the fediverse. I look forward to it being all of them. I resent those who are still on Twitter, so if you have a better social link for any of them, please let me know.
HTMHell Advent Calendar is Manuel Matuzović’s doubling-down on the theology of HTML. And Christianity. One of those. Anyway, each day will reveal an article, talk, or tool that focuses on HTML.
Advent of Code provides a small programming puzzle each of twelve days (that’s a change this year). They are stand-alone, but supposedly have a general theme. They also use different technologies so there is some variety as well. This year, in honor of 10 years, Eric Wastl made a music video for it.
24 Jours de Web is back as an advent calendar for web folk. Written in French, it is clearly primarily targeted at French speakers, but a round of Google Translate will open it up to far more readers (like me).
Performance Calendar hails this as the speed geek’s favorite time of the year, ostensibly because of the tips it has been offering each December since 2009. It isn’t just server optimizations you’ll find here, so don’t shy away because you’re not a system admin.
JVM Advent is posting a technical article from various authors related to Java each day.
Accessibility (アクセシビリティ) Advent Calendar 2024 is in Japanese, and thanks to the powers of Google Translate, I can tell you it covers a variety of accessibility issues, including web: Webのアクセシビリティを含む、様々なアクセシビリティについてのアドベントカレンダーです。
). If you know Japanese, I welcome any corrections. It has been running since 2013.
Selfhtml Advent is from Germany’s oldest (since 1995) and largest web design Community. The advent calendar will present tips and examples from its contributors. As of 1 December, there doesn’t appear to be content, however.
Kodekalender is a Norwegian code-specific calendar. Each day solve a code puzzle and be entered in a drawing (you should check the rules).
Bekk.christmas collects a few topics per day in one calendar. In previous years I broke them all out, but with some of the domains not resolving it seems safest to link to the parent calendar. Posts will cover JavaScript, UX, security, and machine learning.
24 Days in Umbraco is dedicated to the Umbraco CMS. This calendar has been running since 2012.
24 Pull Requests is less an advent calendar than it is an effort to mobilize developers. The goal is to get developers to send a pull request every day in December (up to Christmas), thereby supporting your favorite open source projects. I’m not sure it’s active in the sense that it posts, but you can still do your own contributions of course.
Perl Weekly Challenge Advent has been running since 2021. Each day it takes a response from a previous weekly challenge and re-posts it on the site.
Perl Advent Calendar goes all the way back to 2000 (and back then looked a bit more like a traditional advent calendar, too) and has been dispensing tips for Perl developers ever since.
C# Advent Calendar (sharp, not hashtag) is revealing two posts per day, including on December 25. That’s 50 posts over the course of the month.
Raku Advent Calendar (Raku is Perl6) has a new daily post for your Perl/Raku needs.
Festive Tech Calendar is a YouTube channel with videos ranging from a half hour to over an hour.
IndieWeb Gift Calendar is an annual group effort starting in 2017 to gift (ship) one or more IndieWeb-related thing(s) each day of December that others can use to improve their IndieWeb experience.
The Tactile Times Advent Calendar and Countdown is self-described as the first fully-accessible online advent calendar aimed specifically at young braillists (as far as they know). Its content is not web/dev-related, but it is good insight into what “3 blind children” can build for their “newspaper for young braillists”.
CSS Advent Calendar offers a brief note on a property or feature, with links to MDN and a related video. He’s also posting a very brief video on Twitter for each day.
F# Advent Calendar in English collects folks who write posts or make videos about solving something in F#. The English version dates back to 2014 and there is a Japanese version back to 2010. It also collects folks who solve Advent of Code using F#.
adventJS presents programming challenges to be solved using JavaScript, TypeScript and / or Python (though that one appears to still be in beta). It has been running since 2020 and appears to be available in Spanish and Portuguese.
Advent 2025 – 24 days of accessibility plans to highlight a section (or more) of the Web Content Accessibility Guidelines (WCAG) each day.
Advent of A11Y will offer a tip every day until Christmas how to make your web content more accessible. It promises each tip will be quick to read and easy to apply. Thanks to Jens in the comments for this one.
Advent of Svelte has no coding challenges, focusing instead on 24 days of Svelte knowledge, tips, and insights. Started in 2023, it used to focus on Svelte coding challenges. But in this economy‽
Debug December presents 24 coding challenges framed as bugs for you to solve. There’s also a raffle on days 3, 6, 12, 18 and 24. Thanks to T on Bluesky for the heads-up.
Advent(ure) in System Seeing provides a daily activity to help you practice how you might perceive or understand a system so you can better design it. Collected, these become a journal of prompts for your system design and architecture work.
Inclusive Design 24 (#id24) is an online conference that stuffs 24 talks into 24 consecutive hours. Thanks to a fluke of timekeeping, you can make your own advent calendar from #id24 videos for any year. For example, head over to the 2025 #id24 schedule and watch the one that corresponds to the day of the month. Free content! No sign-in needed!
Each day until Christmas Heydon is posting on Mastodon a new shirt design in a series he calls “monochrome pride:” celebrating pride but without all the bright colors.
Damien Guard is returning with his Masto-only 8×8 Font Advent Calendar. The hashtag is #AdventOfFonts, so you can follow on your preferred Masto client / server.
I started tracking these in 2010. Since then some have come and gone. For the ones not returning, in many cases the content is still out there. Take a look and maybe you’ll find an older article that is useful today.
]]>