CogniPrep has been shipping a long content release: 28 assessment providers' worth of practice tests, 26 tests in the final wave alone, and the public pages that go with them. Before the branch merged, six auditors opened every new test screen and every new public page at three widths: 320x568, 390x844 and 1280x800.
320 pixels is not a device anybody buys in 2026. It is the width where every layout decision that was going to fail has already failed, and it is cheap to check. This post is the method, the findings, and the two console snippets I will run before commissioning that kind of audit again.
The method
One auditor per group of five providers, with a fixed per-screen checklist rather than "have a look at it". For each test: both instruction pages, the first item, a middle item scrolled to its bottom, any confirmation screen, and the completion screen with real text in it. For each public page: the provider hub, the guides, the blog posts, the employer pages and the format pages, in dark mode where the page forces dark, with light mode spot checks.
Two parts of that are worth stealing.
Scroll to the bottom of a middle item, not just the top. Almost every genuine finding came from the bottom of a screen: an action bar sitting over the last row, a button strip pushed under the browser chrome, a label running outside its own button. The top of a page looks fine at 320px because the top of a page is a heading.
Audit with real text, not placeholder text. Several findings were sentences, not pixels: a value that did not fit its tile, a 27 character title that could not wrap, a card whose single long word overflowed its column by 9 pixels. Lorem ipsum wraps politely. Product copy does not.
Reaching the states you cannot click to
The honest problem with this kind of audit is that some screens are not reachable from a signed-in account. Our first pass came back with six premium tests never opened, because the auditor's account could not unlock them and the permission check refused to render.
What worked was a temporary preview route that mounted the real component tree over mocked API responses, so the auditor saw the real UI with no entitlement and no server state. Two rules made it safe: the routes were never committed, and nothing in the real tree was modified to accommodate them. If a preview route needs a change inside the component to work, the audit is no longer auditing what ships.
The limits got written down too. Audio items ran silently in a headless browser, so "does the sound play" was explicitly out of scope rather than silently unchecked. An audit with a recorded gap is worth more than one that implies full coverage.
What the findings had in common
Across all six groups, nearly everything reduced to four shapes:
-
A table. Four and five column tables at 320px either clip, scroll words in half, or push a whole column off screen. Our standard fix is now: keep the table from
mdup, and render a card per row below it, with each cell labelled. - A fixed strip over content. Action bars, review rails and summary strips that work at 1280 cover the last item at 320, or get covered themselves by the phone keyboard when an input is focused.
- A label that is wider than its control. "Start, the clock starts now" does not fit a 320px button. The fix is shorter copy, not smaller text.
- Something off centre or scrolled. A screen that opens scrolled down, or a centred layout that is centred in a container wider than the viewport.
None of those are exotic. All of them are invisible at 1280.
The part I am proudest of: what we did not fix
The audit produced a "left alone, on purpose" list as long as the fix list, and every entry has a reason:
- The primary button's 3.49:1 contrast against its background is the site's existing design token. Changing it is a brand decision, not an audit finding, so it goes to the owner rather than into a patch.
- Breadcrumb links at the top of 52 hub pages are 18 pixels tall. They meet the spacing exception in WCAG 2.5.8, and the markup is inline in all 52 pages, so enlarging them is a deliberate site-wide pass if we want one, not a drive-by edit in a release branch.
- Chart axis labels can spill a few pixels outside the SVG viewBox. The SVG is
overflow-visible, so nothing is clipped and nothing is wrong.
An audit that reports everything it noticed is a worse artefact than one that separates defects from preferences. The second kind can be acted on.
The two snippets
Here is what I will run first next time, before anybody opens a browser by hand. Paste into the DevTools console with the viewport at 320:
// 1. Does the page itself scroll sideways?
document.documentElement.scrollWidth > window.innerWidth;
// 2. What sticks out, and is it inside a declared scroller?
[...document.querySelectorAll('*')]
.filter((el) => el.getBoundingClientRect().right > window.innerWidth + 1)
.map((el) => {
let p = el, scroller = null;
while (p && p !== document.body) {
const ox = getComputedStyle(p).overflowX;
if (ox === 'auto' || ox === 'scroll') { scroller = p.className; break; }
p = p.parentElement;
}
return { tag: el.tagName, inScroller: !!scroller, scroller };
});
The second one is the useful one, because "something extends past the viewport" is not a bug by itself. A table inside an overflow-x-auto container is supposed to extend past the viewport. A heading is not.
See it on the live pages
Set your viewport to 320 wide and open these:
-
The FAA ATSA hub. Scroll to the score history. At 320 you get a card per row with Bands and Source labelled. Resize to 800 wide and the same data is a three column table. In the console,
document.querySelectorAll('table').lengthis 1 at both widths, and at 320 that table'soffsetParentisnull. -
The CritiCall hub. Snippet 1 returns
false, so the page does not scroll sideways. Snippet 2 returns 83 elements, and every one of them is insidebg-card overflow-x-auto rounded-lg, which is the cut score table doing exactly what it was told to do. - The PELLETB hub and a format page, where snippet 2 returns an empty array.
If you only take one thing: run snippet 2 on your own product at 320 right now. The output is usually short, and every entry in it is either a bug or a comment you should have written.
Top comments (0)