Pressroom email output: Accessibility Conformance Report
WCAG edition, based on VPAT® 2.5 (Rev, WCAG 2.2).
Name of product and version
Pressroom newsletter email output: the HTML email and its text/plain
part that Pressroom renders for an edition, and the same stored HTML when it
is served as a web page (the public archive edition page /archive/{code},
the web view /v/{token} and a shared preview /p/{token}).
Version: main at 526b67bf (2026-10-09).
Report date
2026-10-09. Last reviewed 2026-10-09.
Product description
Pressroom is a multi-tenant newsletter editorial platform. Editors assemble an edition from articles, and Pressroom compiles it through MJML into one email document. Each tenant renders through a shell. This report covers the two shell families that ship today:
- D10 shell (
denver-perfect-10,src/lib/newsletter/templates/tenants/denver-perfect-10/), the only shell where the validator's blocking rules are enforced at generation (ENFORCED_SHELLSinsrc/lib/newsletter/validate/validator.ts); - generic shells (
src/lib/newsletter/templates/snippets.tsandtemplates/shells/*), where the same validator report is attached and logged but does not stop generation unlessNEWSLETTER_VALIDATION_ENFORCE=allis set.
Where the two differ, the remark says so.
Contact information
Report accessibility problems through the route on the public statement page
/accessibility (the deployment's HELP_URL, or the newsletter's contact
address on a newsletter's own host). Issues are tracked in the Pressroom
repository (Gitea chris/pressroom).
Notes
- Self-authored. Nobody outside the project has audited this output. A third-party audit is planned only if a procurement process asks for one (owner decision #1352 D-4).
- Content is the publisher's. Pressroom controls the template, the markup it generates and the checks it runs. Editors supply headlines, body text, images and alt text. Several criteria therefore read "Partially Supports": the product checks for the failure and blocks sign-off on it, but an editor can override a finding with a reason and ship.
- Email clients vary. The reader's client (Outlook, Gmail, Apple Mail and others) decides how much of the HTML and CSS is honoured, and it owns keyboard focus, zoom and the reading order of its own interface. Remarks describe what Pressroom emits; they do not certify any client.
- No real-client or assistive-technology testing. Every client verdict below comes from headless Chromium renders under per-client transforms and the caniemail dataset (2026-09-13 audit). No screen reader was used on the email output.
- The staff (editorial) web UI is not covered here. It was audited on 2026-09-13 against WCAG 2.2 AA, did not conform, and is being remediated; a separate UI report will follow once that work lands (#1128, #1129, #1190).
- Cross-reference (2026-10-26, not a re-version of this report): the staff UI
and the reader pages are rated in
app-acr.md(#2142).
Evaluation methods used
validate-newsletter(src/lib/newsletter/validate/validator.ts, CLIvalidate/cli.ts). Sixteen rules over the compiled HTML. The accessibility rules are 3.1 text contrast, 3.2 semantic headings, 3.3 layout tables carryrole="presentation", 3.4 descriptive link text, 3.5 image alt text (missing, empty withoutaria-hidden, placeholder, duplicate) and 3.6 numeric image widths. All six areblocking; on the D10 shell a failure stops generation.- QA linters (
src/lib/newsletter/qa/), run on every render and shown on Review & QA. Accessibility findings:a11y.alt-*,a11y.heading-order,a11y.vague-link-text,a11y.dark-mode-contrast(computed backgrounds under the shipped dark CSS,dark-css.ts),wcag.1-4-3.contrast,wcag.3-1-1.lang,wcag.2-4-2.title,wcag.1-4-4.font-size(under 12px),wcag.2-4-6.no-headings,wcag.1-3-1.multiple-h1,wcag.1-3-1.table-nesting,wcag.2-5-8.target-size, andrendering.mobile-min-width(360px min-content estimate). Open warnings and fails block the edition's sign-off until an editor resolves them or overrides them with a reason (countOpenQaGate,edition-qa.ts). - Renderer behaviour read from source: generic link labels get an
aria-labelthat starts with the visible text (link-a11y.ts), alt-text rules shared by renderer and editor (alt-text.ts), a generatedtext/plainpart (plain-text.ts), and a sanitizer that drops scripts, iframes, objects and forms (sanitize.ts). - Email client rendering audit, 2026-09-13
(
11-audit/email-client-rendering-audit-2026-09-13.md, crops in11-audit/rendering-audit-2026-09/). Findings F1 (dark mode blanked article text in Apple Mail, 69 of 119 text runs failing AA), F2 (Outlook buttons), F3 (360px overflow) and F4 (:not()dark palette) were fixed in PR #449 on the same day, and the three QA rules it recommended were added. No re-measurement of the 69/119 figure in real clients has been done since. - UI accessibility audit, 2026-09-13
(
11-audit/ui-accessibility-audit-2026-09-13.mdon branchworktree-audit-ui-accessibility). Out of scope for this report; cited only for the staff-UI status above.
Applicable standards and guidelines
| Standard / guideline | Included in report |
|---|---|
| WCAG 2.2 Level A | Yes |
| WCAG 2.2 Level AA | Yes |
| WCAG 2.2 Level AAA | No |
Conformance target: WCAG 2.2 Level AA. The output does not yet meet it; see the remarks marked Partially Supports and Does Not Support.
Terms
- Supports: the functionality of the output has at least one method that meets the criterion without known defects, or meets it with an equivalent facilitation.
- Partially Supports: some functionality of the output does not meet the criterion.
- Does Not Support: the majority of the functionality does not meet the criterion.
- Not Applicable: the criterion is not relevant to the output.
Summary
| A | AA | |
|---|---|---|
| Supports | 13 | 5 |
| Partially Supports | 6 | 7 |
| Does Not Support | 0 | 0 |
| Not Applicable | 13 | 12 |
One result differs by shell: 1.3.1 (headings enforced on D10, reported on generic). That row counts once, under its weaker result.
WCAG 2.2 report
Table 1: Success Criteria, Level A
| Criteria | Conformance level | Remarks and explanations |
|---|---|---|
| 1.1.1 Non-text Content | Partially Supports | Every <img> must carry alt; decorative images use alt="" with aria-hidden="true" (validator 3.5, QA a11y.alt-missing fail). Placeholder ("image", file names) and duplicate alt text are reported (a11y.alt-redundant, a11y.alt-duplicate, warn). The rules check that alt text exists and is not obviously empty of meaning; they cannot judge whether an editor's alt describes the image, and a warning can be overridden. |
| 1.2.1 Audio-only and Video-only (Prerecorded) | Not Applicable | The output embeds no audio or video; the sanitizer removes iframes and objects. A link to media hosted elsewhere is out of scope. |
| 1.2.2 Captions (Prerecorded) | Not Applicable | As 1.2.1. |
| 1.2.3 Audio Description or Media Alternative (Prerecorded) | Not Applicable | As 1.2.1. |
| 1.3.1 Info and Relationships | Partially Supports | Headings are real h1–h3 elements in both shell families (generic shells since #677); validator 3.2 requires an h1 masthead and at least one heading, and QA reports skipped levels, more than one h1, and invalid table nesting (wcag.1-3-1.table-nesting, fail). Layout tables carry role="presentation" (validator 3.3). Enforced at generation on the D10 shell only; on generic shells the same failures are reported and block sign-off but not generation. Lists, quotes and emphasis inside article bodies come from editor content and are not checked. |
| 1.3.2 Meaningful Sequence | Supports | Source order is reading order: single-column sections, and multi-column rows (card grid, brief grid) stack in DOM order below 480px. Presentation tables keep screen readers from announcing grid structure. |
| 1.3.3 Sensory Characteristics | Supports | Template copy does not rely on shape, colour or position ("click the red button"). Editor copy is not checked. |
| 1.4.1 Use of Color | Partially Supports | Footer and source-credit links are underlined, and the generic shell's head CSS underlines links. Article links that rely on the editor's body markup, and D10 card and resource rows, are distinguished by layout and button shape rather than underline, and no rule checks link distinguishability. |
| 1.4.2 Audio Control | Not Applicable | No audio. |
| 2.1.1 Keyboard | Supports | The only interactive elements are links (poll answers are signed links, not forms; the sanitizer drops forms and scripts), which every client makes keyboard-operable. |
| 2.1.2 No Keyboard Trap | Supports | No scripted or embedded content that could hold focus. |
| 2.1.4 Character Key Shortcuts | Not Applicable | The output defines no keyboard shortcuts. |
| 2.2.1 Timing Adjustable | Not Applicable | No time limits. |
| 2.2.2 Pause, Stop, Hide | Partially Supports | Templates contain no moving or auto-updating content. An editor-uploaded animated GIF ships as-is; nothing detects animation or limits it to five seconds. |
| 2.3.1 Three Flashes or Below Threshold | Partially Supports | Templates do not flash. Editor-uploaded animated images are not analysed for flashing. |
| 2.4.1 Bypass Blocks | Supports | Each edition is one document with no navigation repeated across pages. Semantic headings (validator 3.2) let screen-reader users move between sections. |
| 2.4.2 Page Titled | Supports | MJML writes <title> from <mj-title> (the edition subject); QA wcag.2-4-2.title reports a missing or empty title. Relevant to the web view and archive page; email clients show the subject line instead. |
| 2.4.3 Focus Order | Supports | Links are in DOM order, which matches visual order (see 1.3.2); no tabindex is emitted. |
| 2.4.4 Link Purpose (In Context) | Partially Supports | Bare generic link text ("click here", "read more", "learn more" and similar) without an aria-label fails validator 3.4 and QA a11y.vague-link-text (fail), and the renderer adds aria-label="Learn more: <headline>" to template-generated generic labels. Limits: the check is a fixed list of 14 generic phrases, so other vague wording passes; the QA finding can be overridden at sign-off; and the validator rule stops generation only on the D10 shell. Whether editor-written link text describes its destination is not judged. |
| 2.5.1 Pointer Gestures | Not Applicable | No path-based or multipoint gestures. |
| 2.5.2 Pointer Cancellation | Supports | Only native links, which activate on the up-event. |
| 2.5.3 Label in Name | Supports | Generated aria-labels begin with the visible link text (link-a11y.ts), so a voice command that names the visible label still matches. |
| 2.5.4 Motion Actuation | Not Applicable | No motion-triggered functionality. |
| 3.1.1 Language of Page | Supports | Every shell writes <html lang="en">: the D10 shell, and since #1509 the generic shells (newsletter, single-content, poll) and the block builder, which put lang="en" on the <mjml> root. A saved template whose root has no lang gets en at render. QA wcag.3-1-1.lang warns when <html> has no lang, an empty one or und (what MJML writes when the root has none). Language is fixed to English; no tenant or newsletter language setting exists. Basis for the rating: all current tenants publish in English; a non-English edition would not meet 3.1.1 until a language setting exists. |
| 3.2.1 On Focus | Supports | Nothing changes on focus. |
| 3.2.2 On Input | Not Applicable | No form inputs. |
| 3.2.6 Consistent Help | Not Applicable | Single document, not a set of pages. |
| 3.3.1 Error Identification | Not Applicable | No form inputs. |
| 3.3.2 Labels or Instructions | Not Applicable | No form inputs. |
| 3.3.7 Redundant Entry | Not Applicable | No multi-step process. |
| 4.1.1 Parsing | Supports | Removed from WCAG 2.2 (always satisfied per the 2.2 errata for 2.0 and 2.1). Listed for VPAT completeness. |
| 4.1.2 Name, Role, Value | Supports | Links have accessible names (text or aria-label, see 2.4.4); images have alt; layout tables are role="presentation"; decorative images and icons are aria-hidden. The preheader is the preview text MJML writes from <mj-preview>, hidden with display:none, so it is not exposed in the email body. |
Table 2: Success Criteria, Level AA
| Criteria | Conformance level | Remarks and explanations |
|---|---|---|
| 1.2.4 Captions (Live) | Not Applicable | No live media. |
| 1.2.5 Audio Description (Prerecorded) | Not Applicable | No video. |
| 1.3.4 Orientation | Supports | Fluid layout; nothing locks orientation. |
| 1.3.5 Identify Input Purpose | Not Applicable | No inputs. |
| 1.4.3 Contrast (Minimum) | Partially Supports | Light mode: validator 3.1 walks inherited colours and fails text below 4.5:1 (3:1 large); QA wcag.1-4-3.contrast reports the same. Dark mode: since PR #449 the dark palette uses positive surface classes rather than :not(), and QA a11y.dark-mode-contrast measures text against computed backgrounds under the shipped dark CSS. Limits: the dark-mode check is a warning, not a validator rule; clients that partially invert colours (Gmail apps) cannot be modelled and are unverified; text inside images is not measured. |
| 1.4.4 Resize Text | Supports | Text is real text in fluid tables, so client zoom scales it. QA wcag.1-4-4.font-size reports inline sizes under 12px. |
| 1.4.5 Images of Text | Partially Supports | Template text is HTML text; the masthead is a logotype image with alt text, which the criterion exempts. Editor-uploaded graphics that contain text (event flyers, for example) are not detected. |
| 1.4.10 Reflow | Partially Supports | Columns stack below 480px, and QA rendering.mobile-min-width estimates min-content width against a 360px viewport (the 2026-09 audit found and fixed a 373px overflow). Nothing measures the 320px width the criterion uses, and wide editor content (tables, long URLs) is not checked. |
| 1.4.11 Non-text Contrast | Partially Supports | Buttons are filled cells with text that passes 1.4.3, but no rule measures a button's or icon's contrast against its surroundings, and focus indicators are drawn by the reader's client. |
| 1.4.12 Text Spacing | Partially Supports | Not tested. Templates set line-height inline and avoid fixed-height text containers, but whether user spacing overrides apply is up to the client, and nothing verifies that text survives them. |
| 1.4.13 Content on Hover or Focus | Not Applicable | No hover or focus-triggered content. |
| 2.4.5 Multiple Ways | Not Applicable | Single document. (The public archive, a separate web page, lists past editions.) |
| 2.4.6 Headings and Labels | Supports | Section and article titles are headings with descriptive text; QA wcag.2-4-6.no-headings reports long body text with no h1–h3. No form labels exist. |
| 2.4.7 Focus Visible | Supports | Templates do not suppress link outlines (outline: none is applied only to images), so the client's focus indicator shows. Its appearance is the client's. |
| 2.4.11 Focus Not Obscured (Minimum) | Supports | No sticky or overlaid content. |
| 2.5.7 Dragging Movements | Not Applicable | No dragging. |
| 2.5.8 Target Size (Minimum) | Partially Supports | D10 buttons and resource rows set min-height of 24px or 44px with cell padding that Outlook honours. QA wcag.2-5-8.target-size reports stand-alone links under 24px tall, but as a warning that can be overridden. Links inside sentences are exempt and are not flagged. |
| 3.1.2 Language of Parts | Partially Supports | No mechanism marks a passage in another language (lang on an element); a bilingual edition would not identify its parts. Not an issue for English-only editions. |
| 3.2.3 Consistent Navigation | Not Applicable | Single document. |
| 3.2.4 Consistent Identification | Not Applicable | Single document. Across editions, the template keeps the same labels for the same functions (Unsubscribe, Update preferences, View in browser). |
| 3.3.3 Error Suggestion | Not Applicable | No inputs. |
| 3.3.4 Error Prevention (Legal, Financial, Data) | Not Applicable | No transactions. Unsubscribe and preferences are separate web pages, out of scope. |
| 3.3.8 Accessible Authentication (Minimum) | Not Applicable | No authentication. |
| 4.1.3 Status Messages | Not Applicable | The output has no status messages; nothing updates after load. |
Known limitations
- Enforcement differs by shell. Validator failures stop generation only on the D10 shell. Other shells report them and require sign-off, but an editor can override a finding with a reason.
- Language is fixed to English (3.1.1). Every shell declares
lang="en"(generic shells since #1509); there is no tenant or newsletter language setting to declare another. All current tenants publish in English; a non-English edition would not meet 3.1.1 until a language setting exists. - Editor-supplied content is checked, not guaranteed. Alt text quality, images of text, animated GIFs and flashing, and in-body link styling depend on the editor.
- No real-client or screen-reader testing. Client verdicts are simulations (headless Chromium with per-client transforms); Gmail's partial dark-mode inversion is unverified.
- Not measured: 320px reflow, text-spacing overrides, non-text contrast, and language of parts.
- The staff UI is out of scope and does not yet conform (2026-09-13 audit); a separate report will follow.
Legal disclaimer
This report is self-authored by the Pressroom project. It describes the output as of the version and date above, from the evidence listed under Evaluation methods. It is not a third-party certification.