An audit is a snapshot of a system nobody is holding in place

Accessibility fails at the point it stops being checked, which on most projects is the week after the report was delivered.

Vishal Chiniwar Co-founder and CTO 7 August 2026

Share
What a build can check against what a person must judgeFour mechanically checkable accessibility failures on the left, and two that require human judgement on the right.Missing altHeading orderNo accessible nameContrast below AACHECKED ON EVERY BUILDKeyboard passneeds a personIs the alt text usefulNEEDS JUDGEMENT

Where the state goes

A site passes an audit. Six months later it does not. Nothing dramatic happened. A new section was added using a div where a button belonged, a colour was adjusted for a campaign and dropped below the contrast threshold, a modal was introduced that traps focus incorrectly, and an image went up without alt text because the person uploading it was in a hurry.

Each of those was a small decision made by a competent person who did not have accessibility in front of them at that moment. Collectively they undo a report that cost real money.

This is the same decay pattern that governs everything else on a website, and it has the same cause. A property that requires continuous effort was established once, by a process that ended, and nothing was left behind to hold it.

So the useful question is not what does an audit find. It is what would have to exist for the audit result to still be true in a year without anybody remembering to care.

Most of it is decided in markup

The first structural fact is that accessibility is overwhelmingly a consequence of element choice, which means it is decided by whoever writes the markup, at the moment they write it.

A button element is focusable, activates on enter and space, announces its role, and works with every assistive technology without any further work. A div with a click handler does none of that and requires five additions to approximate it, all of which can be got subtly wrong.

The same applies throughout. A list of items in a ul announces its length. A heading at the right level lets somebody navigate the page by structure. A label associated with an input means a screen reader user knows what they are typing into. A nav element with a name means it can be skipped.

Which means the highest return accessibility intervention available is not an audit and not a tool. It is developers choosing the right element, and that is a review and habit question rather than a testing one.

The cheapest accessibility work is choosing the element that already does it, and the most expensive is rebuilding what that element would have given you.

Focus order is DOM order, which is why it breaks

Focus moves through the document in source order, not in the order things appear on screen. Every layout technique that changes visual order without changing source order creates a gap between what somebody sees and what happens when they press tab.

Grid placement, flex order, absolute positioning, and any responsive rearrangement can all do this. It is not a misuse of those features, it is a property of them, and it means visual reordering is an accessibility decision whether or not anybody treats it as one.

The check is manual and it takes about a minute per page. Press tab from the top and watch where the focus ring goes. If it jumps somewhere unexpected, the DOM and the layout disagree.

The other half is that the focus ring has to be visible. Removing outlines because they are visually inconvenient is still common, and every removal without a replacement makes the page unusable for keyboard navigation while looking tidier in a screenshot.

Keyboard behaviour beyond tab

Tab order is the part people check. The behaviours that get missed are the ones inside components.

A modal has to trap focus while open, return focus to whatever opened it when closed, and close on escape. All three, not two. Returning focus is the one most often missed and it is the one that strands somebody at the top of the document with no idea where they were.

A disclosure has to be operable from the keyboard and has to announce its state, which means the control is a button with an expanded state rather than a styled div with a class.

Anything with a custom scroll or drag interaction needs a keyboard path to the same outcome, and if there is not one, the feature is not available to a portion of your users regardless of how it looks.

None of this is exotic. All of it is the kind of thing that works when a component is built once carefully and fails when the same pattern is reimplemented by three people.

ARIA is a repair kit, not a feature set

The single most common misunderstanding is that adding ARIA makes something accessible. It is closer to the opposite.

ARIA describes semantics to assistive technology when native HTML cannot. It changes what is announced and it changes nothing about behaviour. A div with role button announces itself as a button and still does not respond to the space key, still is not focusable, and still does not work.

So the ordering is: use the native element. If no native element expresses what you need, use ARIA to describe it and implement every behaviour the native element would have given you, by hand, including the ones you did not think of.

The corollary is that ARIA on the wrong element is worse than nothing, because it makes a promise the implementation does not keep. A screen reader user told something is a button will press it, and if pressing does nothing, they now believe your site is broken rather than that this element was mislabelled.

Contrast is calculable, so it should be calculated

Contrast is unusual in this field because it is fully determined by two numbers and a formula. There is no judgement in it, which makes it the easiest thing to enforce and one of the most commonly failed.

The reason it fails is that colours are chosen visually against a specific background in a specific context, and then reused somewhere with a different background. Text on a card, then the same text on a slightly darker panel, then over an image.

The mechanism that works is computing contrast from design tokens at build time and failing on a violation, rather than sampling rendered pages afterwards. Token pairs are finite and enumerable, so the whole space can be checked rather than sampled.

I should be honest about our own position here. Contrast on this site is calculated from the token values rather than measured from rendered pixels, which catches the token pairs and would not catch text placed over an image. That is a known gap and it is recorded as one rather than described as a pass.

Motion, and a contract rather than a switch

Reduced motion is usually implemented as disabling all animation, which is a defensible reading of the preference and produces an interface that feels abrupt.

The contract we work to instead is that colour and opacity still transition and movement does not. A hover state still fades rather than snapping. A panel still appears rather than materialising. Nothing translates, scales or parallaxes.

That is a decision with a reason, and the reason is that the preference is about vestibular discomfort from movement rather than about disliking transitions. Interpreting it as movement specifically produces a calmer interface than either extreme.

The implementation trap worth naming: a global rule that restricts the transition property list will silently break any component that animates transform, because it will snap instead of fading. That is the correct behaviour under the contract and it looks like a bug unless the component was designed with an opacity fallback. We hit exactly this on a card hover effect and the fix was to give it an opacity path under reduced motion rather than to loosen the rule.

Forms are where the failures cost the most

A form is where somebody is trying to give you something, so a failure there costs more than a failure anywhere else on the site.

Every input needs a label that is programmatically associated, not placeholder text doing the job of one. Placeholders disappear on focus, which means the person filling in the field can no longer see what it is for at the exact moment they are filling it in.

Errors have to be announced, associated with the field they belong to, and stated in terms of what to do rather than what is wrong. Invalid input is a description of your validator's opinion. Enter a date in the format day month year is an instruction.

Required fields have to be marked in a way that is available to assistive technology rather than only as a colour or an asterisk in a legend somebody may not have read.

And the submit path has to work without a mouse, all the way through, including whatever confirmation appears afterwards. A form that submits successfully and shows a confirmation nobody is told about has failed at the last step.

Components are where the returns compound

Everything above sounds like a lot of individual vigilance, and it would be if pages were built by hand. They are not. They are built from components, and that changes the economics completely.

Fix a component once and every instance is fixed, including the ones that do not exist yet. Get it wrong once and every instance is wrong, including the ones that do not exist yet. That is the entire argument for treating this as a system rather than a page level concern.

So the accessibility work worth doing first is on the components that appear most: buttons, links, form fields, cards, modals, disclosures, navigation. Twenty or so components cover most sites, and getting those right is a bounded piece of work that pays out indefinitely.

The corresponding discipline is that a new component is not done until its keyboard behaviour, focus handling and announced semantics are specified. Not tested afterwards, specified as part of what the component is.

  • Which element does this component render, and is it the native one
  • What is announced, and does it match what the component does
  • How does it behave from the keyboard, including escape and return of focus
  • What happens under reduced motion
  • What contrast do its token pairs produce, in every context it appears

What can be automated and what cannot

Automated testing catches a real and specific slice, and being clear about which slice prevents both false confidence and unnecessary manual work.

It reliably catches missing alt attributes, unlabelled form fields, invalid ARIA, contrast on solid backgrounds, missing document language, duplicate ids and heading level skips. Those are mechanical and should be in the build, failing on violation rather than reporting.

It cannot catch whether alt text is useful, whether focus order matches visual order, whether an error message tells somebody what to do, whether a custom component behaves as its role implies, or whether the page makes sense read linearly.

The mistake is treating a passing automated suite as a pass. It is a floor. The manual set is small, takes about twenty minutes per template, and needs a named person on a schedule rather than a general expectation that somebody will.

The system, as a checklist

This is the set that has to exist for an audit result to still be true a year later. It is deliberately short, because a long list becomes a document rather than a practice.

The last two are the ones that decide whether the rest survives contact with a deadline.

What has to exist for accessibility to hold

  • Automated checks in the build, failing the build rather than producing a report
  • Every component's keyboard behaviour and announced semantics specified before it is built
  • Contrast computed from token pairs at build time, across every context a pair appears in
  • A written reduced motion contract, so the correct behaviour is not mistaken for a bug
  • A twenty minute manual pass per template: tab through, escape out, read it linearly
  • Alt text required at upload, in the CMS, not remediated later
  • A named person who reviews new components, whose approval is a gate rather than an opinion
  • A regression check on the components that were fixed, so a refactor cannot quietly undo them

Regression is the part nobody builds

The item most often missing is the last one, and it is the reason fixes do not stay fixed.

A component gets fixed after an audit. Six months later somebody refactors it, reasonably, and the focus return on close is lost because it was not obvious why that code was there. There is no test that fails, so nothing objects.

The remedy is that every accessibility fix ships with a test that would have caught the original problem. Focus is returned to the trigger after close. Escape closes the dialog. The error message is associated with its input. Those are ordinary tests and they take minutes to write at the point of fixing, when the failure is understood.

That converts an audit from a one time expenditure into an accumulating asset, which is the difference between paying for this once and paying for it every eighteen months.

Review ownership

The organisational half, which no amount of tooling replaces.

Somebody has to be able to say a component is not done. Not to suggest improvements, to hold it. That person needs to be named and needs cover, because saying no on a release day is socially expensive and it is exactly the day it matters.

It does not need to be a specialist. In most teams the best person for it is a developer who has learned this properly and is trusted, and their approval is a gate on new components rather than a review of every page.

Where teams get this wrong is making it everybody's responsibility, which reliably means nobody checks and everybody assumes somebody did.

Where this sits in what we build

This site is the case I can speak to concretely. Explicit ARIA on the FAQ and the glossary controls, real focus visible styles rather than removed outlines, a reduced motion contract that keeps colour and drops movement, disabled alphabet rail letters that are not focusable, tables that are keyboard scrollable and labelled from their caption, and one live region per interactive list rather than per update.

The honest gaps: no screen reader pass with NVDA, JAWS or VoiceOver has been done, and contrast is calculated from tokens rather than measured from rendered pixels. Both are recorded as open rather than described as covered.

Creogen is aimed at the regression half of this at site scale, checking that a fixed property is still true across pages over time, which is the part that decays. It is in private development and it does not make a site accessible. Nothing does that except the decisions above.

Written by

Vishal Chiniwar

Co-founder and CTO

Vishal builds the systems behind Creoglyph. He wrote the four Webflow Designer apps the company ships, and now works on the architecture behind Creogen and Creobot: retrieval, grounding, CMS data models, install flows and the validation that has to sit between a model and a live marketing site.

Related reading

Questions this raises

It is a floor. It reliably catches missing alt attributes, unlabelled fields, invalid ARIA, contrast on solid backgrounds and heading skips. It cannot tell you whether alt text is useful, whether focus order matches what somebody sees, or whether a custom component behaves as its role implies.

No. ARIA changes what is announced and nothing about behaviour. A div with role button announces as a button and still does not respond to the space key. Use the native element first, and treat ARIA as a repair kit for the cases where no native element expresses what you need.

The twenty or so components that appear on every page: buttons, links, form fields, cards, modals, disclosures, navigation. Fixing a component fixes every instance including future ones, which is the only version of this work with compounding returns.

The twenty or so components that appear on every page: buttons, links, form fields, cards, modals, disclosures, navigation. Fixing a component fixes every instance including ones that do not exist yet, which is the only version of this work with compounding returns.

Ship every accessibility fix with a test that would have caught the original problem. Focus returns to the trigger after close. Escape closes the dialog. The error is associated with its input. Those are ordinary tests, they take minutes at the point of fixing, and without them the next refactor removes code nobody understood the reason for.

For solid backgrounds, computed from token pairs at build time, largely yes. It will not catch text over an image or a gradient, which is a real gap and the one we have on this site. Automated contrast is a floor rather than a pass.

Colour and opacity still transition, movement does not. That reads as calmer than disabling all animation, which makes an interface feel abrupt. The trap is that a global rule restricting the transition property list will make any transform based effect snap, which looks like a bug unless the component has an opacity fallback.

One named developer who has learned this properly and is trusted, whose approval gates new components rather than reviewing every page. Making it everybody's responsibility reliably means nobody checks and everybody assumes somebody did.

Accessibility fails at the point it stops being checked.

An audit is a snapshot. Bring your build pipeline and we will talk about where the checks would have to live to hold.

This site is built with explicit ARIA, real focus states and a reduced motion contract. Not a claim, a file you can read.

A visitor question becoming a change on a page A question enters on the left, Creobot captures it, Creogen turns it into an operation, and one block on the page is marked as changed. Creobot Creogen QUESTION TO CHANGE