Accessibility statement
This site is built to WCAG 2.2 Level AA
I'm a blind accessibility engineer, so this page is the one I care most about getting right. This statement names the standard to which the site conforms, explains how I checked, lists what I know is imperfect, and tells you how to reach me when something is wrong. Last reviewed: .
Conformance
meredithb104.github.io is intended to conform to the
Web Content Accessibility Guidelines (WCAG) 2.2 at Level AA,
and meets several AAA criteria where they were straightforward to meet: body text contrast of 7:1 or better in every
theme (1.4.6), 44-pixel targets (2.5.5), and no time limits or motion beyond short transitions that stop
entirely under prefers-reduced-motion (2.3.3).
What the site does
- Semantic HTML with one
h1per page, landmarks for header, navigation, main, and footer, and a skip link that lands on a focusable main. - A single visible focus indicator on every interactive element, 3 pixels wide with a 2-pixel offset, contrast-checked against every background it can appear on (2.4.7, 2.4.11, 2.4.13).
- The section menu shows About, Contact, Experience, and Skills, with the other six sections behind a "More information" button. Both it and the phone menu's button are the Commons UI MenuButton, used here as its framework-free custom element, with the same menu button pattern from the WAI-ARIA Authoring Practices:
aria-haspopup="menu",aria-expanded, andaria-controlsdescribe the button; opening moves focus onto the first item; Up and Down move between items, Home and End jump to the ends, and the list wraps; Escape closes it and returns focus to the button; a click outside or focus leaving closes it; and choosing a link closes it. The popup list carriesrole="menu"and its linksrole="menuitem", because JAWS hands the arrow keys to the page only inside a widget it recognizes; on the phone, the whole ten-row list is the menu and the More list is a group within it, and from 72em up, the top four are plain links on the bar and only the More list is a menu. Without JavaScript, the custom element renders nothing and the six links simply appear; inside the phone menu, the button is hidden and the list is flat, so that menu is one list of ten links. The current section is marked on its link and, when that link is inside More information, on the button too. - In-page navigation moves keyboard focus to the target section, and the fragment is then removed from the address, because a fragment that lingers pulls a screen reader's reading cursor back to its target whenever the page restyles. Below 72em, the navigation collapses behind a "Menu" button that opens a panel anchored beneath it (the same menu button pattern as above: focus lands on the first row, the arrow keys walk the rows, Escape closes it and returns focus; a click elsewhere or tabbing past the last row closes it). Every row in the panel is a full-width target at least 44 pixels tall. The header is not sticky at these widths, and without JavaScript, the full list simply appears. At 72em and above, the header is sticky only because the full navigation fits on one row, so it cannot obscure a focused element (2.4.11).
- Three themes (light, dark, high contrast) compiled from one token file; the build fails if any text/background pair drops below its required ratio. The palette is cake batter, white, monaco blue, yearbook blue, and royal navy. On the light theme, the navy is the text (15:1 on the cake-batter page), the monogram tile, the header rule, and the focus ring (2px outside every control; buttons are the lighter yearbook blue, so a ring is never lost against the thing it rings), the yearbook blue is links, buttons, and eyebrow text (7:1), and the monaco blue is the box for callouts and diagram stores with the navy on it. On the dark theme, the royal navy is the page, the monaco blue is links, buttons, the tile, and the rule, the cake batter is eyebrow text, and the focus ring is white (15:1). Errors on both themes use a plain red outside the palette, because an error color is functional, and every error also carries an icon and the word. Windows forced-colors mode is styled with system colors rather than overridden.
- Every state has a cue beyond color: Pass and Fail in the contrast checker, a check mark and a filled background on the active filter, and an underline on the current section (1.4.1).
- Nothing here is disabled while it is working, and no control requires dragging, a specific pointer, or a path-based gesture (2.5.1, 2.5.7).
- Content reflows to a 320-pixel-wide viewport with no horizontal scrolling, survives 200% zoom and user text-spacing overrides, and line length is capped at 68 characters (1.4.4, 1.4.10, 1.4.12, 1.4.8).
- Diagrams are inline SVG with a title and long description, plus a caption that says the same thing in prose. Each diagram has two drawings, a wide one and a stacked one for phones; only one is rendered at a time, so assistive technology meets exactly one image.
- Previous section, Next section, and Back to top sit in one small landmark (the "Page" navigation) after the main content, hidden until you scroll past the introduction. They are not hidden from assistive technology: a focusable control that is hidden from the accessibility tree is a silent tab stop, and the Tab key is shared by screen reader users and sighted keyboard users alike. Screen reader users have their own heading and landmark keys and can skip the landmark in one keystroke.
- The tabs and the carousel in the Lab follow the WAI-ARIA Authoring Practices: one tab stop per tablist with arrow keys, focusable panels, and a carousel that never rotates by itself. The tabs, like the menu buttons, the contrast checker's text fields, the live region, the buttons, and the skip link, are Commons UI components used as framework-free custom elements, so the pattern tested there is the pattern that runs here. The carousel's slides are not interactive, so focus stays on the Previous and Next buttons, and a polite live region announces only the slide's position and heading.
- Posts are written in Markdown and rendered to static HTML at build time. The build refuses a post whose images lack alt text, whose headings skip a level, or whose body contains a second
h1. Every post carries a dated byline, and the Atom feed carries the full text. - The résumé is a tagged PDF with a document title and language set, and every fact in it also appears as HTML in the Experience section, so the PDF is never the only route to the information.
- No cookies, analytics, or third-party scripts. The theme and typeface choices are saved in your browser's local storage and never leave it. The default typeface is Public Sans, served from this site; Atkinson Hyperlegible Next loads from this site only when chosen, and the System option uses no web font.
How it was tested
Every push to the repository runs the following before anything deploys:
- Token contrast build. 81 color pairs across three themes, checked with the WCAG 2.x relative-luminance formula. A failing pair stops the build.
- Unit and component tests with Vitest: keyboard behavior of the theme picker, the filter, and the contrast checker, plus the contrast math against the published WCAG example values.
- axe-core in a real browser with Playwright, using the WCAG 2.0, 2.1, and 2.2 A and AA rule sets and best-practice rules, on both pages and in all three themes. Zero violations is the pass condition.
- Rendered contrast at phone width in every theme: the focus ring measured against the color it actually borders for each kind of control, and each control's text and boundary in default, hover, pressed, and selected states.
- Keyboard walk with Playwright: the first Tab reaches the skip link, every interactive element is reachable in order, and the focused element is always inside the viewport.
- Reflow at 320 by 256 pixels: no horizontal scrollbar on either page.
Beyond the automated checks, I use the site the way I use everything else: with a screen reader and a braille display, keyboard only. Automated tools find roughly a third of accessibility problems; the rest require judgment, which is why the case studies describe behavior and not just markup.
Known limitations
-
The native color picker (
<input type="color">) in the contrast checker is a browser control, and its accessibility varies by browser. The adjacent hex text field does everything the picker does, and the two stay in sync. - The contrast checker's preview swatch shows sample text in exactly the two colors you enter. If you enter a failing pair, that sample fails by design; the verdict is in the table beside it, in words, and the swatch is hidden from assistive technology.
- The architecture diagrams are simplified. The long description and caption are the reliable source; the picture is a summary.
- Contrast is enforced for colors defined in the token file. If you override the theme with a browser extension or a user stylesheet, the site will respect your choice but cannot guarantee the result.
Report a problem
If anything on this site is hard to use with your assistive technology, please tell me. Email meredith.laurel@gmail.com, open an issue on the GitHub repository, or message me on LinkedIn. Say which page, what you were trying to do, and what browser and assistive technology you use, and I'll fix it and tell you when I have.
Criterion by criterion
Every Level A and AA success criterion in WCAG 2.2, with how this site meets it or why it does not apply. 39 criteria are supported and 16 do not apply. Reviewed .
| Criterion | Level | Status | How |
|---|---|---|---|
| 1.1.1 Non-text Content | A | Supports | Diagrams are SVG with a title and long description; icons are aria-hidden beside visible text; the monogram is aria-hidden inside a link named by its text; the two decorative hydrangeas are aria-hidden SVG with no name, outside the tab order, and absent at narrow widths, in print, and under forced colors. |
| 1.2.1 Audio-only and Video-only (Prerecorded) | A | Not applicable | No audio or video. |
| 1.2.2 Captions (Prerecorded) | A | Not applicable | No video. |
| 1.2.3 Audio Description or Media Alternative (Prerecorded) | A | Not applicable | No video. |
| 1.2.4 Captions (Live) | AA | Not applicable | No live media. |
| 1.2.5 Audio Description (Prerecorded) | AA | Not applicable | No video. |
| 1.3.1 Info and Relationships | A | Supports | Landmarks, one h1 per page with no skipped levels, lists, definition lists, tables with column headers, labelled form fields. Verified by axe and structural tests on every page. |
| 1.3.2 Meaningful Sequence | A | Supports | DOM order is reading order; no CSS reordering of content. The phone menu button is inserted before its list. |
| 1.3.3 Sensory Characteristics | A | Supports | Instructions never rely on shape, position, or color alone. |
| 1.3.4 Orientation | AA | Supports | No orientation lock; layouts reflow in either orientation. |
| 1.3.5 Identify Input Purpose | AA | Not applicable | The only inputs are hex colors and a theme choice; none collects personal data. |
| 1.4.1 Use of Color | A | Supports | Every state is also text, an icon, an underline, or a check mark: Pass and Fail, the pressed filter, the selected tab, the current section, error messages. |
| 1.4.2 Audio Control | A | Not applicable | No audio. |
| 1.4.3 Contrast (Minimum) | AA | Supports | Enforced at build for every declared pair (81 pairs, three themes) and re-measured as rendered in Chromium at phone width, including hover, pressed, and selected states. |
| 1.4.4 Resize Text | AA | Supports | All sizes in rem; tested at a 200% equivalent with no loss of content. |
| 1.4.5 Images of Text | AA | Supports | No images of text; the monogram is a logotype, which the criterion exempts. |
| 1.4.10 Reflow | AA | Supports | No horizontal scrolling at 320 CSS pixels, tested with a wide fallback font; data tables scroll in a labelled, focusable region as the criterion allows. |
| 1.4.11 Non-text Contrast | AA | Supports | Focus ring measured at 3:1 or better against the color it actually borders for every kind of control; control boundaries, state indicators, and the diagram store boxes measured at 3:1 or better against their surroundings. |
| 1.4.12 Text Spacing | AA | Supports | Tested with the criterion's spacing override applied; nothing clips or overflows. |
| 1.4.13 Content on Hover or Focus | AA | Not applicable | No tooltips or hover-revealed content. |
| 2.1.1 Keyboard | A | Supports | Every control is a native button, link, or input, or follows an ARIA Authoring Practices pattern with the documented keys. Tested by a full keyboard walk. |
| 2.1.2 No Keyboard Trap | A | Supports | No traps; the phone menu and the More menu close on Escape and return focus. |
| 2.1.4 Character Key Shortcuts | A | Not applicable | No single-character shortcuts. |
| 2.2.1 Timing Adjustable | A | Not applicable | No time limits. |
| 2.2.2 Pause, Stop, Hide | A | Supports | Nothing moves, blinks, or updates automatically; the carousel does not auto-rotate. |
| 2.3.1 Three Flashes or Below Threshold | A | Supports | Nothing flashes. |
| 2.4.1 Bypass Blocks | A | Supports | Skip link first on every page, landing on a focusable main. |
| 2.4.2 Page Titled | A | Supports | Every page has a descriptive title; post titles include the author. |
| 2.4.3 Focus Order | A | Supports | Focus order follows reading order; in-page links move focus to the target section. |
| 2.4.4 Link Purpose (In Context) | A | Supports | Repeated link text carries a visually hidden qualifier, so each link's purpose is clear from its own text. |
| 2.4.5 Multiple Ways | AA | Supports | Every page carries the same site navigation and footer links to every other page, and posts are also reachable from the archive and the feed. |
| 2.4.6 Headings and Labels | AA | Supports | Headings name their sections; form fields have visible labels and hints. |
| 2.4.7 Focus Visible | AA | Supports | A 3px ring on every focusable element; never removed. |
| 2.4.11 Focus Not Obscured (Minimum) | AA | Supports | The header is sticky only when the full navigation fits on one row; the keyboard walk asserts every focused element is unobscured. |
| 2.5.1 Pointer Gestures | A | Not applicable | No path-based or multipoint gestures. |
| 2.5.2 Pointer Cancellation | A | Supports | All actions fire on the up event (native click). |
| 2.5.3 Label in Name | A | Supports | Every accessible name contains its visible text (for example, 'Previous slide' for the button labelled Previous). |
| 2.5.4 Motion Actuation | A | Not applicable | No motion-operated functions. |
| 2.5.7 Dragging Movements | AA | Not applicable | Nothing is operated by dragging. |
| 2.5.8 Target Size (Minimum) | AA | Supports | Controls are 44px; the carousel picker uses the spacing exception (24px pitch) and is tested for it. |
| 3.1.1 Language of Page | A | Supports | lang="en" on every page. |
| 3.1.2 Language of Parts | AA | Supports | No passages in another language; loanwords such as résumé are English usage. |
| 3.2.1 On Focus | A | Supports | Focus never changes context. |
| 3.2.2 On Input | A | Supports | Changing the theme or a color updates the page in place; nothing navigates or moves focus unexpectedly. |
| 3.2.3 Consistent Navigation | AA | Supports | The same navigation, in the same order, on every page. |
| 3.2.4 Consistent Identification | AA | Supports | The same controls are named the same way on every page. |
| 3.2.6 Consistent Help | A | Supports | The Contact link is the last item of the navigation on every page. |
| 3.3.1 Error Identification | A | Supports | An invalid color is marked aria-invalid and described in text with an icon. |
| 3.3.2 Labels or Instructions | A | Supports | Every field has a visible label and a hint. |
| 3.3.3 Error Suggestion | AA | Supports | The error message shows the expected format. |
| 3.3.4 Error Prevention (Legal, Financial, Data) | AA | Not applicable | No such transactions. |
| 3.3.7 Redundant Entry | A | Not applicable | No multi-step process asks for the same information twice. |
| 3.3.8 Accessible Authentication (Minimum) | AA | Not applicable | No authentication. |
| 4.1.2 Name, Role, Value | A | Supports | Native elements where possible; ARIA patterns for tabs, the carousel, the filter, and the menu, verified by tests and axe. |
| 4.1.3 Status Messages | AA | Supports | Filter counts, carousel moves, and contrast results are announced through role="status" or polite live regions without moving focus. A theme or typeface choice is not announced separately: the chosen radio's own name and state say it, and the page holds its position on screen while it restyles. |
Technical details
The site is built from HTML, CSS, and TypeScript, compiled with Vite, with no framework runtime. It is tested in Chromium through Playwright, and I use it daily with a screen reader (mostly JAWS) and a braille display on Windows. The source, the tests, and this statement are in the public repository.