Email production

Email accessibility: the checks that matter, with real contrast maths

Most accessibility advice for email stops at "use enough contrast" and "write good alt text". This is the arithmetic underneath the first one, the specific tests that catch the second, and the cases where an honest checker should refuse to give you a verdict at all.

17 August 2026 11 min read Backed by the engine in our Email QA tool

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 × cB

Step 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:

Computed with the formula above. The 0.7152 green weight is why the orange button fails so badly despite looking bright.
ForegroundBackgroundRatioNormal text (4.5:1)
#333333#ffffff12.63:1Passes
#1159da#ffffff6.06:1Passes
#767676#ffffff4.54:1Passes, barely
#7c879b#ffffff3.62:1Large text only
#999999#ffffff2.85:1Fails
#ffffff#f3882c2.51:1Fails
The row that catches everyone

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:

Each of these makes the true rendered colour unknowable from the HTML alone. Guessing here produces confident, wrong warnings.
SituationWhy no verdict is possible
Text over a background imageThe effective backdrop is whatever pixels sit behind the glyphs. That is an image-analysis problem, not a CSS one.
hsl(), var(--token), gradientsNot resolvable by parsing the markup — and a gradient has no single value to resolve to.
Translucent rgba() or 8-digit hexThe 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.

Why this is worth insisting on

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, .webp or .svg is 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.

Three problems, one fix

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:

  1. Use real heading tags. Wrap the headline in <h1> and section titles in <h2>, styled inline as usual. Set margin:0 explicitly — clients add their own default margins to headings and will otherwise rearrange your spacing.
  2. Do not skip levels. h1 then h3 reads 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.

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

  1. Body text at 4.5:1 minimum; large text (24px+, or 18.66px+ bold) at 3:1.
  2. Check your CTA buttons specifically — white on a bright brand colour is the usual failure.
  3. Alt text that says what the image says or does; alt="" on decoration; nothing over ~125 characters.
  4. The headline, offer and CTA in live HTML text, never only inside imagery.
  5. role="presentation" on every layout table, ghost tables included.
  6. Real <h1>/<h2> headings, sequential, with margin:0.
  7. Link text that names its destination.
  8. lang on the <html> element, so the screen reader picks the right pronunciation.
  9. Body copy at 14px or above; legal text no smaller than 12px.
  10. aria-hidden only on genuine duplicates.
Check yours in the browser

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.

Run the email QA
What this is honest about

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?
It matters more in email, because email has fewer escape hatches. There is no JavaScript to patch things at runtime, no ARIA widget library, and the markup a screen reader receives is whatever survived the client's own stripping. Roughly one in six people has a disability of some kind, and a great many more read email one-handed on a phone in bad light — which is the same problem as low contrast, arriving by a different route.
What contrast ratio does an email need?
The WCAG AA thresholds: 4.5:1 for normal text and 3:1 for large text, where "large" means 24px or larger, or 18.66px and bold. The ratio is computed from relative luminance, not from how different two colours look. On white, #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?
Because it can only be honest about colours that are actually declared. If text sits on a background image, or the colour is written as 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?
No, and deliberately so. Hidden preheader blocks — 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?
Put the message in real HTML text rather than inside imagery. It fixes several problems at once: screen readers cannot read pixels, images are blocked by default in Outlook and many corporate clients, and image-only emails are a classic spam signal. An email whose offer only exists inside a JPEG is invisible to a meaningful share of the people you sent it to.
Do these tools use credits or tokens?
No — never. Every tool is free to use with sample or daily limits, and PRO is a flat $25/mo or $199/yr for unlimited use of every tool: no credit packs, no top-ups, no per-use fees. See tools pricing.

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.