1. Why email is harder than the web
On a web page you have options. JavaScript can patch a control at runtime, an ARIA widget library gives you tested keyboard behaviour, and you broadly control the rendering environment.
Email has none of that. There is no JavaScript — every client strips it. There is no component library. And the markup a screen reader actually receives is not the markup you wrote: it is whatever survived the client's own stripping, which differs by client and which you cannot inspect from your desk. Outlook's Word engine alone rewrites a great deal of what you sent.
What is left is the part you fully control: the semantics and colours of the HTML itself. That is where all of the leverage is.
2. The contrast formula, worked
"Enough contrast" is not a judgement call — it is a computation, defined by WCAG, and worth seeing once so the thresholds stop feeling arbitrary.
Step one: linearise each channel. Screen colour values are gamma-encoded, so you cannot average the raw numbers. For each of R, G and B, take the 0–255 value, divide by 255, then:
v = channel / 255 c = v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ^ 2.4
Step two: weight them into a single luminance. The weights are not equal, because the eye is not equally sensitive to the three channels — green carries most of the perceived brightness:
L = 0.2126 × cR + 0.7152 × cG + 0.0722 × cBStep three: compare the two luminances. Lighter over darker, with 0.05 added to both sides so that pure black against pure black cannot divide by zero:
ratio = (L_lighter + 0.05) / (L_darker + 0.05)The result runs from 1:1 (identical colours) to 21:1 (pure black on pure white). Here is that arithmetic applied to pairs you will actually recognise:
| Foreground | Background | Ratio | Normal text (4.5:1) |
|---|---|---|---|
| #333333 | #ffffff | 12.63:1 | Passes |
| #1159da | #ffffff | 6.06:1 | Passes |
| #767676 | #ffffff | 4.54:1 | Passes, barely |
| #7c879b | #ffffff | 3.62:1 | Large text only |
| #999999 | #ffffff | 2.85:1 | Fails |
| #ffffff | #f3882c | 2.51:1 | Fails |
White text on a warm brand orange — the last row — is one of the most common CTA button treatments in email, and at 2.51:1 it is nowhere near AA. Orange is bright in the green channel, which is exactly the channel that dominates the luminance calculation, so it lands far closer to white than it looks. The usual fix is not to abandon the brand colour but to darken the button or set the label in near-black rather than white.
Two useful reference points to memorise: on white, #767676 is the lightest grey that passes for normal text, and #999999 — a very common "muted" grey — does not.
3. 4.5:1, 3:1 and what counts as large
WCAG AA sets two thresholds, and the line between them is defined in pixels, not vibes:
- Normal text needs 4.5:1. This is the default assumption for every block until proven otherwise.
- Large text needs 3:1. "Large" means 24px or above, or 18.66px or above when bold. Bigger, heavier letterforms carry more ink per glyph, so they stay legible at a lower ratio.
That 18.66px figure looks odd until you know where it comes from: it is 14pt expressed in pixels at the traditional 96dpi (14 × 4/3). WCAG's own wording is in points; email is written in pixels; the conversion is what you actually need.
It also means the size and weight of a block change the standard it is held to — which is why a checker has to resolve font size and boldness before it can judge colour at all, walking up from the element through its ancestors to find the nearest declared value for each.
4. When a checker should refuse to answer
This is the part most tools get wrong, and it is the reason to trust or distrust any contrast report you are given.
A contrast ratio needs two known colours. In an HTML email, plenty of text has no knowable pair — and the honest response is to say so, not to assume. Our engine skips and counts every one of these cases:
| Situation | Why no verdict is possible |
|---|---|
| Text over a background image | The effective backdrop is whatever pixels sit behind the glyphs. That is an image-analysis problem, not a CSS one. |
hsl(), var(--token), gradients | Not resolvable by parsing the markup — and a gradient has no single value to resolve to. |
Translucent rgba() or 8-digit hex | The rendered colour depends on everything composited underneath it. |
Hidden blocks (preheaders, mso-hide:all) | Nobody sees them. White-on-white there is the technique working correctly. |
So a report reads like "2 of 34 checkable text blocks below WCAG AA — worst 2.5:1 (needs 4.5:1); 6 skipped (background images / unresolvable colours)". Both halves matter. The first is a finding you can act on; the second is an honest boundary telling you where to use your own eyes.
A checker that guesses trains you to ignore it. Three false alarms and you stop reading the accessibility section entirely — at which point the real failure, the one sitting right next to them, ships too. Under-claiming is what makes the remaining claims worth acting on.
Where nothing at all is declared, there is still a sane default rather than a shrug: black text on the client's white canvas at roughly 14px — the strictest band, so the check errs toward flagging rather than excusing.
5. Alt text: presence is not quality
Almost every email QA tool checks whether alt exists. Far fewer check whether it is worth reading — and an alt attribute containing hero_banner_final_v2.png is arguably worse than none, because it passes the check while telling the reader nothing.
Three patterns are worth flagging automatically:
- Filenames. Anything ending
.png,.jpg,.gif,.webpor.svgis a leaked build artefact, not a description. - Placeholders.
image,img,photo,picture,graphic,icon,spacer,banner,header,untitled,screenshot— with or without a trailing number. These are what a template ships with when nobody replaced them. - Essays. Over 150 characters stops being a description and becomes an obstacle; a screen reader will read all of it before moving on. Aim under ~125.
The rule that produces good alt text in one sentence: write what the image says or does. "Save 20% — summer sale ends Sunday" beats "banner_final_v2.png", and beats "promotional banner image" too. Purely decorative images — spacers, rules, flourishes — get alt="", which tells a screen reader to skip them entirely. That empty string is a real instruction, not a missing value.
6. The images-off test
Here is the test that reframes the whole exercise: turn every image off and read what is left. Does the offer survive? The headline? The call to action?
This is not a hypothetical. Images are blocked by default in Outlook and in many corporate mail environments, so a meaningful share of your list sees the images-off version first — and a screen-reader user effectively always does. An email whose entire message lives inside a single sliced JPEG arrives, for those readers, as nothing at all.
It is measurable, which is why it is a check rather than advice. Roughly: images present with under about 120 characters of live text is effectively an image-only email — a failure. Two or more large images with under about 320 characters is thin enough to warn about.
Putting the headline, offer and CTA in real HTML text fixes accessibility, fixes images-off rendering, and removes a classic spam signal at the same time. Image-only emails score badly with filters precisely because that is what a certain kind of sender does to evade text analysis.
7. role="presentation" and why it matters
Email is built with tables — Outlook leaves no real alternative. But a screen reader has no way to know that your table is a layout scaffold rather than a spreadsheet. Encountering one, it announces a data table: row one, column one, cell contents; row one, column two; and onward through your nested layout.
For a typical email with a dozen nested layout tables, that turns a two-paragraph message into a recital of grid coordinates.
<!-- announced as a data table: "table, 3 rows, 2 columns…" --> <table width="600" cellpadding="0" cellspacing="0"> <!-- announced as nothing at all — the content just reads --> <table role="presentation" width="600" cellpadding="0" cellspacing="0">
One attribute, zero visual change, and it applies to every layout table — including the Outlook ghost tables tucked inside your <!--[if mso]> conditionals, which are easy to forget precisely because they are hidden in comments. If a table genuinely holds data — an order summary, a fixture list — leave the role off and mark it up properly with <th> instead.
8. Headings a screen reader can navigate
Screen-reader users navigate long content by jumping heading to heading, the way a sighted reader skims. An email built entirely from styled <td> elements offers nothing to jump between — the reader has to listen to the whole thing linearly to find out whether it is relevant.
Two things to get right:
- Use real heading tags. Wrap the headline in
<h1>and section titles in<h2>, styled inline as usual. Setmargin:0explicitly — clients add their own default margins to headings and will otherwise rearrange your spacing. - Do not skip levels.
h1thenh3reads as a missing section. Keep them sequential.
A short transactional email needs no headings at all. Anything with a few hundred characters of body copy and more than one section does.
9. "Click here" read out loud
Screen readers can list every link in a message so the user can pick one. Run that on a typical marketing email and you may hear: "click here, click here, read more, more, link, click here." The list is useless, and the user's fastest navigation route is gone.
The fix takes seconds: name the destination in the link text itself. "View your statement." "Browse the sale." "Book your seat." It reads better for everyone, and it is one of the rare accessibility improvements that also lifts click-through, because a specific label sets an expectation a vague one cannot.
10. aria-hidden, and its one legitimate use
aria-hidden="true" removes an element from the accessibility tree entirely. Sighted readers see it; screen-reader users do not. Put it on real content and you have quietly sent two different emails to two different audiences.
It does have a correct use in email, and it is a common one: a duplicated fallback. An Outlook ghost table that repeats content already present for other clients should be hidden from assistive technology, or the message gets announced twice. That is the pattern to look for when a checker flags it — is this text duplicated elsewhere in the email, or is it the only copy? Duplicated: correct. Only copy: a bug.
The checklist
- Body text at 4.5:1 minimum; large text (24px+, or 18.66px+ bold) at 3:1.
- Check your CTA buttons specifically — white on a bright brand colour is the usual failure.
- Alt text that says what the image says or does;
alt=""on decoration; nothing over ~125 characters. - The headline, offer and CTA in live HTML text, never only inside imagery.
role="presentation"on every layout table, ghost tables included.- Real
<h1>/<h2>headings, sequential, withmargin:0. - Link text that names its destination.
langon the<html>element, so the screen reader picks the right pronunciation.- Body copy at 14px or above; legal text no smaller than 12px.
aria-hiddenonly on genuine duplicates.
Run the contrast maths on your actual HTML.
Our free Email QA tool runs 40 pre-send checks, eight of them accessibility-specific — every one covered above: real WCAG contrast where both colours are declared, heading structure, alt-text quality, images-off resilience, aria-hidden misuse, layout-table semantics, ambiguous link text and tiny type. It shows you what it skipped and why, and each finding highlights the element it came from.
Automated checks catch the measurable failures — ratios, missing semantics, placeholder alt text. They cannot tell you whether your alt text is good, whether your reading order makes sense, or whether the email is comprehensible to someone using it under stress. Nothing replaces testing with a real screen reader, and a passing report is a floor, not a certificate.
Most creative processing happens locally in your browser. Files are uploaded only when you explicitly use sharing, transfer or team features.
Frequently asked questions
Does email accessibility actually matter, or is it a web-only concern?
What contrast ratio does an email need?
#767676 scrapes a pass at 4.54:1 while #999999 fails at 2.85:1 — a difference of two hex digits either side of the line.Why does your checker skip some of my text instead of judging it?
hsl(), a CSS variable, a gradient or a translucent rgba(), the real rendered colour is not knowable from the HTML alone. A checker that guesses in those cases produces confident, wrong warnings — so ours skips them, counts the skips, and tells you how many. You get a verdict on what is computable and an honest gap where it is not.Is white text on a hidden preheader going to be flagged?
display:none, font-size:1px, zero max-height, opacity:0, mso-hide:all — are excluded from the contrast check. White-on-white there is a technique, not a defect. Flagging it would train you to ignore the check, which is worse than not running it.What is the single highest-impact accessibility fix for an email?
Do these tools use credits or tokens?
Find the contrast failures before your subscribers do.
Free, in your browser, nothing uploaded — real WCAG maths on the HTML you are about to send. Or hand the build to the team that codes these for agencies every day.