Production gate

Run this before a project goes live. Ordered by how each item is verified, so the automated section can be started in one go.

PASS means no blocker and no required item failing, with nothing left unverified. CONDITIONAL means a required item failed and was waived in writing, or something could not be checked. FAIL means a blocker is open.

An item that could not be verified is never counted as a pass. That is the difference between a gate and a formality.

Dimension
Severity
How it is checked

Showing all 66 rules

A. Automated

Run the command, paste the number. A machine decides pass or fail.

Filters write their state to the URL

blocker colibrity

Only when the page has a listing, archive or search with filters.

Changing a filter changes location.search. Reloading that URL restores the same result set and the same visible filter UI. Clearing a filter removes its parameter rather than leaving it empty. Copying the URL to another browser reproduces the view.

Build instruction: Keep one named list of filter parameters. On any filter change, write the active values into the query string with history.replaceState and delete the parameter when a filter is cleared. On load, read the query string back and restore both the filter UI and the result set before the first paint of results.

Ships in wp-starter/filter-url-state - import it rather than rebuilding it.

Why this rule exists, and how to satisfy it

Pagination controls are links, not buttons

blocker colibrity

Only when the listing has more items than fit on one page.

Every page control in the pagination nav is an a element with an href that resolves to a 200. Middle-clicking page two opens it in a new tab.

Build instruction: Render each page control as an anchor with a real href to that page's URL. A button type="button" is invisible to crawlers and to anyone who opens links in a new tab. Keep the existing accessible markup around it - nav with an accessible name, an ordered list, aria-current on the active page.

Ships in wp-starter/archive-state - import it rather than rebuilding it.

Why this rule exists, and how to satisfy it

Every page of a listing has its page number in the URL

blocker colibrity

Only when the listing has more items than fit on one page.

Page two is reachable at a distinct URL containing the page number, that URL loads the correct items with JavaScript disabled, and its page number is visible in the address bar after using load-more.

Build instruction: Render real server-side pagination at /page/2/ and up. Load-more, if present, is an enhancement layered over it and must advance the URL as it loads each batch. A listing whose only navigation is an offset load-more button does not satisfy this rule.

Ships in wp-starter/archive-state - import it rather than rebuilding it.

Why this rule exists, and how to satisfy it

Doctype and a correct lang attribute on every page

blocker html

Every sampled page has the doctype and a lang attribute whose value matches the language of the visible copy.

Build instruction: Start every document with the HTML5 doctype and set lang on the html element to the page's actual language. On a multilingual site the value changes per page rather than being hard-coded in the header partial.

JavaScript and CSS stay inside their budgets

blocker perf

The budget test passes, or a raised budget is accompanied by that note in the commit. Without repository access this is reported as unverified, never as a pass.

Build instruction: Assert a gzip budget per chunk in the build and fail the build when it is exceeded. Raising a budget is allowed, in the same commit, with a written note saying what the extra bytes bought.

Ships in wp-starter/bundle-size-test - import it rather than rebuilding it.

CLS at or under 0.1

blocker perf

Median mobile CLS is 0.1 or less, and no visible shift occurs during a slow-network reload watched by eye.

Build instruction: Reserve space for everything that arrives late: width and height on every image and video, explicit dimensions on embeds and ads, and font fallbacks metric-matched to the web font.

LCP at or under 2.5 seconds on mobile

blocker perf

Median of three or more mobile runs is 2.5s or less, and the identified LCP element is stated in the report alongside the number.

Build instruction: Identify the LCP element before touching payload. It is usually being discarded rather than loading slowly - an element that finishes loading while hidden is dropped permanently, and opacity zero, clip-path and overflow hidden are all treated the same way. You cannot fade in your largest above-the-fold element and have a fast LCP.

Why this rule exists, and how to satisfy it

No credential ever reaches the repository or the web root

blocker practices

No credential appears in git history or in the deployed web root. Requesting the environment file over HTTP returns 403 or 404.

Build instruction: Keep every key, password, host and licence in an environment file that is git-ignored and excluded from deployment. Whatever the deploy filter does not exclude ends up served over HTTPS - a licence file has been fetched from a live site this way.

Why this rule exists, and how to satisfy it

Every indexable page declares a self-referencing canonical

blocker seo

Each sampled page returns one canonical, absolute, on the live host, equal to the URL that was requested once redirects have resolved.

Build instruction: Emit exactly one canonical link per page pointing at that page's own absolute URL, on the production domain, with the scheme and host the site actually serves.

Automated check: seo-status:canonical

HTTPS is enforced and nothing loads over http

blocker seo

http:// returns a single 301 to https://, and no sampled page contains an http:// asset URL.

Build instruction: Redirect http to https in one hop and make sure no stylesheet, script, image, font or iframe has a hard-coded http:// URL. These usually arrive with migrated content, not code.

Automated check: seo-status:https-redirect, seo-status:mixed-content

Production is indexable and staging is not

blocker seo

The production home page carries no noindex in the meta robots or the X-Robots-Tag header, and its robots.txt does not disallow the whole site. Staging carries noindex everywhere.

Build instruction: Drive the noindex decision from the environment, never from a checkbox someone has to remember. Staging returns noindex on every response; production returns none.

Automated check: seo-status:home-index, seo-status:robots

Why this rule exists, and how to satisfy it

Paged views are canonical to themselves

required colibrity

Only when the listing has more items than fit on one page.

The canonical on page two equals page two's own URL, and page two's title differs from page one's.

Build instruction: Point the canonical of /page/2/ at /page/2/, never back at page one, and put the page number in the title. Canonicalising every page to the first one tells the crawler the other pages do not exist.

Automated check: seo-status:canonical

Ids are unique on the rendered page

required html

The validator reports no duplicate id on any sampled page, including pages that render the same block more than once.

Build instruction: Watch for ids baked into a partial that repeats. A repeated block with a hard-coded id silently breaks label association, aria references and anchor links.

No inline event handlers or javascript: URLs

required html

No rendered page contains an on* event attribute or an href starting with javascript:.

Build instruction: Bind behaviour in scripts, not in onclick attributes. Inline handlers block any meaningful Content Security Policy and scatter behaviour across templates.

One h1 per page, and no skipped heading levels

required html

Each sampled page has exactly one h1, and its heading outline never jumps a level going down, for example h2 straight to h4.

Build instruction: Give each page exactly one h1 naming the page. Descend one level at a time. If a heading looks wrong at its correct level, that is a styling problem, not a structure problem.

Every template produces valid HTML

required html

The validator reports zero errors on the sampled pages. Warnings are read and either fixed or noted with a reason.

Build instruction: Run the W3C validator over a sample covering every template, not just the home page. Unclosed elements and duplicate ids break assistive technology and JavaScript long before they look wrong.

Static assets are compressed and cached far-future

required perf

Content-Encoding is br or gzip on HTML, CSS and JS, and hashed assets carry a cache lifetime of a year with immutable.

Build instruction: Confirm what the host actually sends rather than assuming. Some hosts compress nothing, in which case every estimate made from a gzipped size is wrong. Fingerprinted assets take a one-year immutable cache header.

Why this rule exists, and how to satisfy it

TBT at or under 200ms and INP at or under 200ms

required perf

Median mobile TBT is 200ms or less. Where field data exists, INP at the 75th percentile is 200ms or less.

Build instruction: Keep the main thread free during load. Defer non-critical scripts, break long tasks, and keep third-party tags off the critical path.

Below-the-fold images lazy load, the LCP image does not

required perf

Every below-the-fold image has loading lazy, the LCP image has neither lazy nor async decoding and carries fetchpriority high.

Build instruction: Put loading lazy on images below the fold and never on the LCP image, which instead takes fetchpriority high. Lazy-loading the hero delays the very metric it is meant to help.

Why this rule exists, and how to satisfy it

Mobile performance score at or above 90

required perf

Median of three or more mobile runs is 90 or above, quoted with the spread of the runs.

Build instruction: Treat the score as a summary of the metrics above, not a target of its own. Chasing the number rather than the metric is how a site ends up fast in a lab and slow for people.

Why this rule exists, and how to satisfy it

Multilingual sites declare reciprocal hreflang

required seo

Only when the site serves more than one language.

Each language version lists every other version plus itself plus x-default, and following any alternate lands on a page that lists the original back.

Build instruction: Emit hreflang alternates on every translated page, including a self-reference and an x-default. Every alternate must point back, or search engines discard the whole set.

Automated check: seo-status:hreflang

Open Graph and Twitter card tags are complete

required seo

All eight tags are present on a sampled page, og:url matches the canonical, and the og:image URL is absolute and returns an image of at least 1200 by 630.

Build instruction: Emit og:title, og:description, og:type, og:site_name, og:locale, og:url and an absolute og:image, plus twitter:card. A relative og:image renders as no image at all when shared.

robots.txt exists and points at the sitemap

required seo

/robots.txt returns 200, contains a Sitemap: line with an absolute URL that itself returns 200, and does not disallow any route that should rank.

Build instruction: Ship a robots.txt that disallows sandbox, styleguide and any internal preview route, and names the sitemap with an absolute URL.

Automated check: seo-status:robots

The sitemap lists every indexable URL and nothing else

required seo

The sitemap index returns 200, every child returns 200, a sample of ten listed URLs returns 200 with no redirect, and no listed URL carries noindex.

Build instruction: Generate the sitemap rather than maintaining it by hand. Exclude anything that is noindex, redirected or gone. Hand-maintained sitemaps go stale the first week nobody remembers them.

Automated check: seo-status:sitemap, seo-status:tax-sitemaps

Titles and descriptions are present, unique and the right length

required seo

No sampled page is missing a title or description, no two sampled pages share either, and lengths sit in range.

Build instruction: Give every template a title pattern and every published page a description. Titles run roughly 30 to 60 characters, descriptions 70 to 160. Do not ship the theme defaults.

B. Assisted

A tool runs, a human reads the result and judges it.

No automated accessibility violations

blocker a11y

axe reports zero violations at the WCAG 2.1 A and AA rule sets across the sampled pages, including one page with a form and one with a listing.

Build instruction: Run axe over every template and fix everything it reports. Treat a clean run as the starting line for the manual checks below, not as evidence of accessibility.

Why this rule exists, and how to satisfy it

Text and interface contrast meets AA

blocker a11y

No contrast violation is reported, and text over imagery is checked manually at its worst point rather than its best.

Build instruction: 4.5:1 for body text, 3:1 for text at 24px or 19px bold and above, 3:1 for interface components and focus indicators. Check text sitting on images, video and gradients by hand, because scanners cannot see those.

Every form control has a real label

blocker a11y

Every input, select and textarea has an associated label element that is visible. Placeholders, where present, are supplementary rather than the only name.

Build instruction: Use a label element associated with its control, not a placeholder and not an aria-label standing in for one. A placeholder disappears the moment someone types, which is exactly when they need it. Add autocomplete tokens on identity and address fields.

Why this rule exists, and how to satisfy it

All dynamic output is escaped at the point of output

blocker practices

No template prints an unescaped dynamic value. A field containing a script tag renders as visible text rather than executing.

Build instruction: Escape every value as it is printed, using the function that matches its context - HTML text, attribute, URL, or a filtered allow-list for rich text. Escape late, at output, not early on input.

Replaced URLs redirect to their new home

blocker seo

Only when the project replaces an existing live site.

Every URL on the old-site list returns 200 or a single 301 to a 200. No mass redirect to the home page. Spot-check the twenty highest-traffic URLs by hand.

Build instruction: Export the old site's indexed URLs before launch and map each one to its replacement with a 301. Anything with no equivalent goes to the nearest parent, not to the home page in bulk.

Why this rule exists, and how to satisfy it

A block is three files sharing one slug

required colibrity

For each block, the four names agree. No block has a stylesheet or script with no template, or a data-block value that matches no SCSS class.

Build instruction: Every block ships as template, stylesheet and script under the same slug, and the ACF layout name, the data-block attribute, the SCSS class and the script filename all derive from it. Use the scaffold command rather than creating the files by hand.

Filter changes do not litter the back button

required colibrity

Only when the page has a listing, archive or search with filters.

After changing filters five times, one press of the browser back button leaves the listing page entirely rather than stepping back through filter states.

Build instruction: Use history.replaceState for filter changes, not pushState. Ten filter clicks must not cost the visitor ten presses of the back button to leave the page.

Ships in wp-starter/filter-url-state - import it rather than rebuilding it.

Why this rule exists, and how to satisfy it

Filter combinations do not become a crawl trap

required colibrity

Only when the page has a listing, archive or search with filters.

A filtered URL either canonicalises to the base listing or is deliberately indexable and present in the sitemap. No filter combination generates an unbounded set of indexable URLs.

Build instruction: Decide per filter whether its URLs should be indexed. Filters that match real search demand stay indexable and appear in the sitemap. Sort orders, free-text search and multi-value combinations canonicalise to the unfiltered listing or carry noindex. Write the decision down in the project handover.

Why this rule exists, and how to satisfy it

JavaScript targets data attributes, never CSS classes

required colibrity

No querySelector or querySelectorAll in block or module scripts selects by a styling class. Renaming a BEM class in the stylesheet breaks nothing at runtime.

Build instruction: Select elements for behaviour and animation through data- attributes. CSS classes are for styling only. This is what lets a component be restyled without breaking its animation.

Page regions use landmark elements

required html

Each sampled page exposes exactly one main landmark, all content sits inside a landmark, and no two navigation landmarks share a name.

Build instruction: Use header, nav, main, aside and footer for what they are. One main per page and it wraps the page content. Give multiple navs distinct accessible names. Drop redundant role attributes that repeat the element's own semantics.

Lists are lists and tables are tables

required html

No layout is built from table elements, every table has header cells with scope, and repeated content is inside a list element.

Build instruction: Mark repeated items as ul or ol. Use table only for tabular data, with th and scope, never for layout. Card grids and pagination are lists.

Animation is cheap and stops when off screen

required perf

No looping animation runs while off screen, a performance trace shows no layout thrash during scroll, and no animation drives a layout-triggering property.

Build instruction: Animate transform and opacity only. Pause any infinitely repeating tween while its section is out of view. Use svh rather than lvh on pinned sections. Do not add per-block resize handlers.

Fonts are subset woff2, self-hosted, with a swap policy

required perf

No font file over 100 KB, no ttf or otf served to browsers, font-display is set, and at most one font is preloaded.

Build instruction: Ship woff2 only, subset to the characters the site actually uses, self-hosted, with font-display swap and a preload for the one font used above the fold. An unsubset variable font can be several hundred kilobytes on its own.

Why this rule exists, and how to satisfy it

Images ship in a modern format at the size they display

required perf

No image transfers more than twice the bytes needed at its displayed size, and the image audits report no meaningful savings from format or sizing.

Build instruction: Serve WebP or AVIF with a fallback, generate responsive sizes, and use srcset and sizes so a phone never downloads a desktop image. A full-width PNG hero is the single most common cause of a slow mobile LCP on these projects.

Nothing render-blocking that does not have to be

required perf

The render-blocking resources audit lists nothing beyond the site's own critical stylesheet.

Build instruction: Scripts are deferred or type module. Only above-the-fold CSS is render-blocking. No synchronous third-party script in the head. No font stylesheet from another origin blocking first paint.

Video is deferred and never blocks the first paint

required perf

Only when the project has background or hero video.

The video's own bytes are not on the critical path, LCP resolves to the poster or another element, and total transfer on first load stays inside the page budget.

Build instruction: Give the video a poster, set preload none until it is near the viewport, keep autoplay muted and playsinline, and start playback after load so the poster carries LCP. Above-the-fold autoplay video is usually the reason mobile LCP cannot be fixed by anything else.

Production loads with a clean console

required practices

Loading each sampled page and navigating between two of them produces no console error, no unhandled rejection and no failed network request.

Build instruction: Clear every error and warning, remove debug logging, and make sure no asset 404s. A console full of noise is where the next real error goes unnoticed.

Scripts clean up after themselves on page transition

required practices

Only when the project uses client-side page transitions.

Navigating between two pages five times and returning leaves the same number of listeners and scroll triggers as the first load.

Build instruction: Every module returns or registers a teardown that removes its listeners, kills its animations and disconnects its observers. Without it, navigating back and forth stacks duplicate handlers until the page stutters.

Structured data is present, valid and singular

required seo

Google's Rich Results Test reports no errors on the home page and one interior page, and exactly one Organization node exists per page with a name matching the brand.

Build instruction: Emit one Organization or LocalBusiness node for the site and a BreadcrumbList on pages that have a breadcrumb trail. One node per type per page - a plugin plus a theme both emitting Organization is a validation error, not redundancy.

Automated check: seo-status:org-one, seo-status:org-brand, seo-status:org-sameas

Class names follow BEM and match their block slug

recommended html

Every block's root class equals its slug, and no class name outside the design system describes a colour or a size.

Build instruction: Name classes block__element--modifier, where the block name is the block's own slug. A class that names its appearance rather than its role gets stale the first time the design changes.

Third-party scripts are counted and justified

recommended perf

The third-party summary is reviewed, each origin has a stated purpose, and their combined main-thread time stays under 250ms.

Build instruction: List every third-party origin the page loads and what it is for. Load analytics and chat after interaction or after load. Anything nobody can name an owner for comes out.

C. Eyes only

No tool can answer these. They are read, clicked and listened to.

Images carry alt text that earns its place

blocker a11y

Every image has an alt attribute, decorative ones are empty, and reading the alt text of a page aloud conveys the same information as seeing it.

Build instruction: Describe what the image conveys in context. Decorative images take an empty alt so they are skipped. Do not write "image of". An image inside a link describes the destination.

Why this rule exists, and how to satisfy it

A visible focus indicator on every focusable element

blocker a11y

Tabbing through a page shows an obvious indicator on every stop, with at least a 3:1 contrast against its background, including over images and gradients.

Build instruction: Define one global focus-visible style in the base layer, not per component. Never set outline none without an equally visible replacement. Per-component focus styling leaves every unstyled control with whatever the browser happens to do.

Ships in wp-starter/focus-tokens - import it rather than rebuilding it.

Why this rule exists, and how to satisfy it

Everything works with a keyboard alone

blocker a11y

Every interactive control can be reached and operated by keyboard, focus never enters a hidden element, and no component traps focus except a modal, which releases it on Escape.

Build instruction: Operate the whole site with Tab, Shift-Tab, Enter, Space and Escape. Menus, modals, carousels, filters, video controls and custom selects all included. Nothing may need a mouse or a hover.

Why this rule exists, and how to satisfy it

prefers-reduced-motion is honoured everywhere

blocker a11y

With reduced motion enabled at OS level, no element animates on scroll or entrance, all content is visible and in its final position, and nothing auto-plays.

Build instruction: Check the media query in every animated module and in CSS, and honour it. Reduced motion means the content arrives in its final state, not that the animation runs faster. Parallax, auto-playing carousels and scroll-driven reveals are the ones that cause harm.

Ships in wp-starter/motion-guard - import it rather than rebuilding it.

Why this rule exists, and how to satisfy it

Modals trap focus, return it, and close on Escape

required a11y

Only when the project has a modal, drawer or off-canvas menu.

Opening a modal moves focus inside, Tab cycles only within it, Escape closes it, and focus returns to the trigger.

Build instruction: Move focus into the dialog on open, keep it inside while open, close on Escape, and return focus to the control that opened it. Mark the dialog and hide the rest of the page from assistive technology.

Ships in wp-starter/dialog-a11y - import it rather than rebuilding it.

Errors say what is wrong and how to fix it

required a11y

Submitting an invalid form produces a text message identifying the field and the fix, the message is announced, and focus lands on the first invalid control.

Build instruction: Describe the problem in words next to the field, announce it to assistive technology, and move focus to the first error. Colour alone is not an error message.

Why this rule exists, and how to satisfy it

Video and audio carry captions and controls

required a11y

Only when the project has video or audio with speech.

Every video with speech has captions that match the audio, and every auto-playing item can be paused with a keyboard.

Build instruction: Caption anything with speech. Give any media longer than five seconds a pause control. Auto-playing media must be muted and stoppable. Captions must be accurate, not machine output nobody read.

The page survives 200 percent zoom and a 320px viewport

required a11y

At 200 percent zoom no content is lost or overlapping, and at 320px wide the page scrolls vertically only.

Build instruction: Set text-only zoom to 200 percent and check nothing clips or overlaps. At 320 CSS pixels wide there must be no horizontal scrolling of the page body.

Every dynamic component handles empty, long and broken

required practices

Emptying a listing shows a deliberate empty state rather than a blank region. A title of double the expected length does not break its layout.

Build instruction: Design for zero items, one item, many items, a very long title, a missing image and a failed request. Test with the real content, not the placeholder set that always has three items of equal length.

The project ships with the notes needed to run it

required practices

Someone who has never seen the project can build, run and deploy it from that page alone.

Build instruction: Write down how to build and deploy it, where the environment values live, which plugins are load-bearing, what was deliberately left out, and the indexability decision for filters. One page in the repository, not in a chat thread.

Editable content is editable, not baked into templates

required practices

A content editor can change the text and images of every public page, and reorder the sections of a flexible page, without a developer.

Build instruction: Anything the client will plausibly want to change - copy, images, links, section order - comes from fields. Hard-coded strings turn every text tweak into a developer task and a deploy.

Anything animated or video-driven is checked on a real device

required practices

Every page with entrance animation or video is opened on a real phone in a fresh private tab, and scrolling stays smooth with video playing.

Build instruction: Open the site on a real iPhone and a real mid-range Android. Simulators do not reproduce Safari's video behaviour, momentum scrolling, or the cost of an animation on a slow GPU.

The layout holds at every width, not just the design widths

required practices

No horizontal scrollbar at any width from 320 up, no clipped or overlapping content, and no layout that only works at the exact design breakpoints.

Build instruction: Drag the window slowly from 320 to 2560 and watch for overlap, clipping, orphaned words and horizontal scroll. Designs are drawn at three widths; visitors use every width.

The site publishes an accessibility statement

recommended a11y

A linked, reachable page names the standard targeted, lists known exceptions, and gives a working contact address.

Build instruction: Publish a page stating the conformance target, known gaps and a contact route for accessibility problems. Under the EAA this is what a client is asked for first when challenged, and it is cheap to produce at handover and expensive to reconstruct later.

Performance and behaviour changes ship one at a time

recommended practices

Each performance claim in the handover names the single change it came from and the before and after measurement.

Build instruction: Deploy one change, verify it, then start the next. Never widen a change while debugging it. Two changes in one deploy means neither result is attributable.

The heading outline reads as an outline

recommended seo

Reading only the headings of a page conveys what the page is about, in order, without needing the body copy.

Build instruction: Write headings that describe the section beneath them. Headings chosen for their size are a styling decision wearing a structural tag.