What this project has
All on. Switch off what this project does not have and the rules that depend on it leave the list. Nothing is hidden until you say so.
Ticks are kept in this browser only, against the project name above. They are not shared, backed up, or visible to anyone else - copy the markdown out for anything that has to survive.
No rule matches these filters. to see all 175 again.
1Design and UI kit 9 items
-
No information is carried by colour alone greyscale filter loses nothing, 0 colour-only distinctions blocker
Never let colour be the only difference between two meanings. Links in body copy get an underline. Required fields get a mark or the word. Status dots get a label or a shape. Chart series get a pattern or a direct label. The active tab gets weight or a marker too. This is WCAG 1.4.1, Level A - below the AA bar the rest of this standard targets, and the first thing an audit reads.
Without it, a link inside a rich-text field looks the same as the words around it - both delivered themes strip underlines and never put them back.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.4.1 (A), in part
Designer The agency proves it Why this rule exists About Design and UI kit
design.not-colour-aloneComment on this rule -
Contrast is proven in the mockup, not found in the browser 4.5:1 body, 3:1 large text and UI, 0 pairs unmeasured required
Only when the build works from a new design file rather than an existing template.
Run a contrast plugin over every text-on-background pair in the kit before handoff - buttons in every state, placeholder text, disabled text, the footer. Fixed here it costs one variable. Found in the browser it costs a design change, a rebuild and a re-review, and the developer's token lint reports the same pairs straight back to you.
Without it, a focus ring nobody measured ships at 1.48:1 on the page background, and re-colouring it after the build costs a design change and a rebuild.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.4.3 (AA), in part
Designer The agency proves it Why this rule exists About Design and UI kit
design.contrast-in-mockupComment on this rule -
Every unique section and page has its own mockup one named frame per section and per page required
Only when the build works from a new design file rather than an existing template.
Draw every distinct section and every page type as its own named frame, desktop and mobile. A section with no mockup gets invented during the build and argued about at review. The developer builds one code block per named frame and names it after the frame, so the frame list is also the estimate.
Without it, a page type nobody drew - the archive, the 404, the mobile variant - gets guessed at during the build and was never in the estimate.
Client document Website Design and Development Checklist section 1, item 2not public
Designer The agency proves it About Design and UI kit
design.mockup-coverageComment on this rule -
Every animated surface has a defined motion spec 8 surfaces, each with trigger, duration and easing required
Only when the build works from a new design file rather than an existing template.
Write a motion sheet: for each animated surface, what starts it, how long it runs in milliseconds, and which easing. "Smooth fade" is not a spec - it is eight different animations depending on who builds it. A surface this project does not have is marked not applicable rather than left blank.
Without it, the developer picks every duration himself, which is what both delivered themes did, and the client first sees the timing in a browser.
Client document Website Design and Development Checklist section 2, items 1-8, in partnot public
Designer The agency proves it About Design and UI kit
design.motion-specComment on this rule -
Layer and component names are agreed with the developer the Figma name is the name in code, 0 renames after handoff required
Only when the build works from a new design file rather than an existing template.
Agree the name of every component and section frame with the developer before handoff, then keep it. That name becomes the block's slug, its folder, its template file and its root CSS class in code, and html.bem-naming requires all four to agree - so a rename after handoff is a rename in four places plus a re-review. In Figma it costs nothing.
Without it, a name agreed in the mockup is renamed after handoff, and one word changed in Figma becomes four file renames and a re-review.
Client document Website Design and Development Checklist section 1, item 7, in partnot public
Designer The agency proves it About Design and UI kit
design.naming-conventionComment on this rule -
The design specifies every state of every interactive element hover, focus, active, disabled, loading, current - all six required
Only when the build works from a new design file rather than an existing template.
Draw hover, focus, active, disabled, loading and current for every button, link, input, select, card, tab and menu item. The two that always go missing are disabled and loading, and they are the two the developer ends up inventing. Focus uses the one global focus style from the kit, not a different treatment per component.
Without it, the submit button looks identical while it is sending - one delivered theme sets a loading class in JS that no stylesheet ever consumes.
Client document Website Design and Development Checklist section 3, items 2-7not public
Designer The agency proves it About Design and UI kit
design.state-setComment on this rule -
Text over imagery carries a scrim the design specifies 4.5:1 at the image's darkest and lightest region required
Only when the build works from a new design file rather than an existing template.
Wherever text sits on a photo or a video, specify the scrim as a value - overlay colour and opacity, or gradient stops and direction. Test it against the worst photo the client will plausibly upload, not the one you chose for the mockup. With no scrim specified the developer invents one or ships none, and it fails on the first content update rather than at launch.
Without it, an editor uploads a lighter photo than the mockup's and the heading over it falls under 3:1 - a delivered hero already sits at 2.85:1.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.4.3 (AA), in part
Designer The agency proves it About Design and UI kit
design.text-over-mediaComment on this rule -
The UI kit is built and published before the build starts components, variants, variables, styles, 0 detached required
Only when the build works from a new design file rather than an existing template.
Build the kit before the build does. Every repeating element is a component with its variants, and every colour, text style, spacing step and radius is a published variable or style. No mockup carries a loose hex or a detached instance. Publish the library and share the link at kickoff - without it the build side cannot see the file, and cannot settle a single rule in this section.
Without it, raw hex spreads through the stylesheets, 171 of them across 47 files in one delivered theme, and a brand tweak becomes a search and replace.
Client document Website Design and Development Checklist section 1, item 1, in partnot public
Designer The agency proves it About Design and UI kit
design.ui-kitComment on this rule -
Design values reach the code as generated tokens, not typed numbers one token source, generated output, a drift test that fails required
Only when the build works from a new design file rather than an existing template.
Import colour, type, spacing and radius from the design file into one token source in the repository, generate the stylesheet variables from it, and never type a design value into a component by hand. Pin the built CSS to the source with a test, so a value changed on either side fails the suite instead of drifting for a month.
Without it, the button ships a hex nobody can find in the design file, and the next restyle hunts every literal by eye.
Ships in
wp-starter/design-tokens- import it rather than rebuilding it.House rule Delivered reference builds, audited together DESIGN.md to generated tokens with a drift test, third build and starter; typed variables in both older buildsnot public
Developer The build proves it About Colibrity
colibrity.tokens-from-the-design-fileComment on this rule
2Setup 19 items
-
Text and interface contrast meets AA 4.5:1 body, 3:1 large text and UI blocker
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.
Without it, a body token measured at 4.37:1 sits under the AA bar and ships as real running text on every page that uses it.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.4.3; 1.4.11 (AA), in part
Developer The build proves it About A11y
a11y.contrastComment on this rule -
Each language version has its own crawlable URL a distinct URL per language, 0 redirects by IP or header blocker
Only when the site serves more than one language.
Give every language its own URL - a subdirectory, a subdomain or its own domain - and let anyone open any of them directly. Never redirect on IP address or Accept-Language. Googlebot crawls from one country with one header, so a redirect means it never sees the other languages at all, and that costs a whole language index rather than a score. The switcher is links.
Without it, Googlebot arrives with one country and one header, gets bounced to French every time, and the English pages never enter the index.
Vendor documentation Google Search Central documentation managing-multi-regional-sites#let-the-user-switch-the-page-language, in part
Developer The build proves it About Crawl and indexation
crawl.language-urlsComment on this rule -
Production personal data does not travel to staging or a laptop submission and consent rows dropped or synthesised blocker
Only when the project has a form that collects data from a visitor.
Make the database pull drop or synthesise every table holding personal data - form submissions, consent records, orders, customer accounts - inside the pull itself, not as a step someone remembers. GDPR Article 32 requires security appropriate to the risk; using fictitious or anonymised data in test environments is CNIL guidance, not black-letter law. The blocker is ours.
Without it, real form submissions with names, emails and IP addresses end up in plaintext dumps on a laptop and in a backups folder nobody prunes.
Developer The build proves it Why this rule exists About Environments and deployment
env.no-production-data-off-productionComment on this rule -
The old site's indexed URLs are counted before anything is built crawl plus Search Console, one list blocker
Only when the project replaces an existing live site.
Take the inventory from four sources and merge them, because no single one is complete: the old sitemap, a full crawl, Search Console's indexed pages, and analytics for anything with traffic in the last twelve months. Break the total down by language and by template. Do this before estimating, not before launching.
Without it, 2,362 old blog posts nobody counted turn up as a 404 spike a week after launch, when the old site is gone and the list cannot be rebuilt.
Vendor documentation Google Search Central documentation site-move-with-url-changes#determine-your-current-urls, in part
Developer The build proves it Why this rule exists About Migration
migration.url-inventoryComment on this rule -
No credential ever reaches the repository or the web root git-ignored, out of the deploy, 403 over HTTP blocker
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.
Without it, a licence key gets fetched off the live site by anyone who guesses the filename, and rotating it is the only real fix.
House rule CONVENTIONS.md CONVENTIONS.md:76-80, in partnot public
Developer The build proves it Why this rule exists About Practices
practices.no-secretsComment on this rule -
No REST route writes without an authorisation check or a stated bound no open write route without a stated bound blocker
Give every custom route a permission callback that checks a capability. Where a route must genuinely stay public - a form submit, a nonce fetch, a violation collector - list it in the handover and name what bounds the abuse: nonce, honeypot, captcha, a hard cap on what gets stored. Capability checks elsewhere in the theme prove nothing about these routes, so read the routes themselves.
Without it, a custom route left open takes whatever a stranger posts straight into the database, with nothing capping how much lands there.
Vendor documentation REST API Handbook rest-api/extending/adding-custom-endpoints#permissions-callback, in part
Developer The build proves it Why this rule exists About Security and hardening
security.rest-route-authorisationComment on this rule -
Field definitions live in the repository, and a field name is the data registered in code; no name renamed without a migration required
Register field groups in code under version control, not in the admin UI, so a field change arrives as a reviewable diff and deploys with the release rather than with a database. Then treat the field name and key as the identity of the data. Labels, order and tab titles change freely. A name does not: renaming one orphans every value saved under it.
Without it, an editor opens the page after a rename and the content is simply gone, the old values still sitting under a key nothing reads.
House rule Delivered reference builds, audited together field groups registered in PHP, both buildsnot public
Developer The build proves it About The admin the client receives
admin.fields-in-codeComment on this rule -
Search Console is verified and the build team has access verified property, build team's account opens Coverage required
Ask the client at kickoff for a verified Search Console property on the production domain, and for the build team to be added to it. Verify by DNS TXT record, which survives a redesign and a theme change. It is the only source for what is actually indexed today, and the URL inventory takes it as one of four inputs, so a missing property quietly removes one.
Without it, nobody can see what Google actually did with the site, so live sitemap 404s and dropped pages go unnoticed until someone complains.
Vendor documentation Google Search Central documentation search-console-start#get-started-with-search-console, in part
Operations The client proves it About Crawl and indexation
crawl.search-consoleComment on this rule -
Slugs are readable, hyphenated and in the page's language no ids, no dates, under 115 chars, page's language required
Fix the permalink structure and the slugs before anything is published. Lower case, words separated by hyphens, no post id, no date segment, and in the language of the page it serves. This governs the content URL only: filter and pagination state deliberately live in the query string. Renaming after launch costs redirects, lost links and a re-crawl, which is the expensive fix.
Without it, an English page ends up served on a French slug, or /?cat=38 in the results, and fixing it after launch means redirects and a fresh crawl.
Client document Technical SEO control grid row 60not public
Developer The build proves it About Crawl and indexation
crawl.url-formatComment on this rule -
Environment switches are enforced in code, not in the database survives a database push in either direction required
Decide indexing, tracking and debug from an environment constant, with a filter that overrides whatever the database holds. A search-visibility checkbox is a database row, and a database sync carries rows between environments in both directions, which is how a staging site silently becomes indexable and a production site silently becomes noindex.
Without it, the next content push from production overwrites the one database row that was keeping staging's tracking switched off.
Vendor documentation The Twelve-Factor App III. Config, in part
Developer The build proves it About Environments and deployment
env.env-config-not-in-dbComment on this rule -
Staging matches production where the checks depend on it same PHP, server, compression and cache headers required
Put staging on the same PHP branch, the same web server and the same compression and cache configuration as production, and write down every difference that remains. Compression and cache headers are per-domain on shared hosting and off by default on a new one, so a performance or caching result measured on staging otherwise describes staging and nothing else.
Without it, staging answers on a different web server with different cache headers, so a speed or caching result signed off there describes nothing live.
Vendor documentation The Twelve-Factor App X. Dev/prod parity, in part
Operations The build proves it About Environments and deployment
env.staging-parityComment on this rule -
Staging never writes to a production system no beacon, no live CRM, no mail to a real address required
Only when the project has a form that collects data from a visitor.
Gate every outbound integration on the environment: analytics and tag containers, the CRM endpoint, transactional mail, payment keys. Staging exists to be broken, and a staging site pointed at the client's real property poisons a dataset nobody can clean, or mails a real customer from a form test. No component covers the mail and CRM half - gate those yourself.
Without it, a form test on staging lands in the client's real public inbox, and test traffic lands in the analytics property they report on.
Vendor documentation The Twelve-Factor App IV. Backing services, in part
Developer The build proves it About Environments and deployment
env.staging-sends-nothing-liveComment on this rule -
Backups cover the database and the uploads, on a stated schedule daily database, weekly files, one copy off-account required
Turn backups on before content entry starts, not before launch: the database daily, the uploads weekly, retention stated as a number of days. Keep one copy outside the hosting account, because a suspended or deleted account takes the panel's own backups with it. Write down where they live and who is able to restore them.
Without it, the newest restorable copy on the box turns out to be a month old, and the uploads have no copy at all.
Vendor documentation Hardening WordPress security/hardening#data-backups, in part
Operations The agency proves it Why this rule exists About Operations
ops.backup-coverageComment on this rule -
No write to a live database without a fresh dump of what it replaces dump the target first, size-checked, undo printed required
Any command that overwrites a live database - a content push, a core or plugin update, a search-replace - dumps the target first, checks the dump is a real dump rather than a zero-byte gzip, and prints the exact command that puts it back. Use the project's own sync tooling, never a raw import piped at the server by hand.
Without it, the dump taken before a push turns out to be a zero-byte gzip, and the only thing printed afterwards is a placeholder nobody can run.
Vendor documentation Hardening WordPress security/hardening#restricting-database-user-privileges, in part
Developer The build proves it About Operations
ops.db-write-backupComment on this rule -
A fatal error reaches a mailbox somebody actually reads admin address on the client's domain, not a dev's required
Point the site's admin email at a monitored mailbox on the client's or the agency's domain, and name in the handover the person who reads it. WordPress sends the fatal-error notice and the recovery link to that address, so pointed at a developer's personal mailbox it goes quiet the day the engagement ends.
Without it, the recovery link for a white-screened site goes to an address that routes nowhere, so the client finds the outage first.
Vendor documentation WordPress Developer Resources wordpress/common-errors#critical-error-message, in part
Operations The client proves it About Operations
ops.fatal-alertsComment on this rule -
A Content-Security-Policy is enforced, not only reported 4 directives enforced, the rest report-only required
Ship a report-only policy from the first staging deploy and read what it collects. Before go-live send a second, enforcing header carrying object-src 'none', base-uri 'self', form-action 'self' and a frame-ancestors value. Those four cost nothing - inline GSAP, JSON-LD and analytics loaders do not touch them. Leave the script and style allowlists report-only until real traffic proves them.
Without it, a report-only header watches an injected script send the form data elsewhere and does nothing but log it.
Standard Content Security Policy Level 3 §3.2, in part
Developer The build proves it Why this rule exists About Security and hardening
security.csp-enforced-baselineComment on this rule -
WordPress's default attack surface is closed at setup XML-RPC, author enum and file editing closed required
Turn XML-RPC off unless something named actually needs it, block author enumeration through the ?author=N redirect and the anonymous users route, and set DISALLOW_FILE_EDIT so nobody edits theme or plugin files from the dashboard. Each is one filter or one constant, and each closes a class of automated attack that starts arriving within hours of a domain resolving.
Without it, a bot reads a real login name off /wp-json/wp/v2/users and gets a second unwatched door at xmlrpc.php that nothing disables.
Vendor documentation Hardening WordPress security/hardening#disable-file-editing, in part
Operations The build proves it About Security and hardening
security.wp-attack-surfaceComment on this rule -
A visible focus indicator on every focusable element one global style, 3:1 against its background blocker
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.
Without it, a global focus ring hidden behind @media (hover: hover) paints nothing on a touch laptop, so the keyboard user tabs blind.
Ships in
wp-starter/focus-tokens- import it rather than rebuilding it.Standard Web Content Accessibility Guidelines (WCAG) 2.1 2.4.7; 1.4.11 (AA), in part
Developer The build proves it Why this rule exists About A11y
a11y.focus-visibleComment on this rule -
Every HTML response carries the baseline security headers 5 response headers, HSTS max-age 1 year required
Send X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN or an equivalent frame-ancestors, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy denying geolocation, camera and microphone, and Strict-Transport-Security at a year over HTTPS only. They ship from a mu-plugin the deploy syncs separately, so one skipped step drops all five with no other symptom.
Without it, the site can be framed on someone else's page, referrers leak full URLs to third parties, and browsers keep retrying over http.
Ships in
wp-starter/security-headers- import it rather than rebuilding it.Automated check:
seo-status:hdr-strict-transport-security,seo-status:hdr-x-frame-options,seo-status:hdr-x-content-type-options,seo-status:hdr-referrer-policy,seo-status:hdr-permissions-policyStandard OWASP Secure Headers Project Best Practices - Configuration proposal, in part
Developer The build proves it About Security and hardening
security.response-headersComment on this rule
3Markup and structure 19 items
-
Every form control has a real label a real label element, never a placeholder blocker
Use a label element associated with its control, not a placeholder and not an aria-label standing in for one. A placeholder vanishes as someone types, which is exactly when they need it. Mark required fields both ways, in the label text and with the required attribute, and say once whether the marker means required or optional. Add autocomplete tokens on identity and address fields.
Without it, someone using voice control has no visible text to say, and a half-filled form gives back no clue what each box was for.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 3.3.2; 1.3.1; 1.3.5 (A, AA), in part
Developer The build proves it Why this rule exists About A11y
a11y.form-labelsComment on this rule -
Icons are hidden when decorative and named when they act decorative icons unannounced, acting icons named blocker
Give every decorative icon aria-hidden="true" so it is skipped. An icon that is itself the control, like a bare icon button or an icon-only link, needs a name in text: a visually hidden label or an aria-label. Export tools leave a title element inside the SVG, and an unhidden icon then announces a name nobody wrote. An inline SVG has no alt attribute, so the alt-text rule does not reach it.
Without it, a bare icon button announces as just button, and a decorative tick gets read out on every badge it appears in.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.1.1; 4.1.2 (A), in part
Developer The build proves it About A11y
a11y.icon-semanticsComment on this rule -
A link goes somewhere, a button does something never a div with a click handler blocker
Anything that navigates is an anchor with a real href. Anything that acts on the current page is a button. Never a div with a click handler. This one decision drives keyboard support, right-click, new tab and crawlability at once.
Without it, a project card that navigates from a JavaScript click has no URL to copy, open in a new tab or share, since there is no href to take one from.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 4.1.2 (A), in part
Developer The build proves it About HTML
html.buttons-vs-linksComment on this rule -
Doctype and a correct lang attribute on every page lang matches the visible copy, per page blocker
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.
Without it, the English page inherits fr-FR from the header partial, and a screen reader pronounces the whole thing in French.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 3.1.1 (A), in part
Developer The build proves it About HTML
html.doctype-and-langComment on this rule -
All dynamic output is escaped at the point of output escape late, at output, per context blocker
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.
Without it, an editor pastes a script tag into a title field and it runs for every visitor on that page.
House rule CONVENTIONS.md CONVENTIONS.md:37-41, in partnot public
Developer The build proves it About Practices
practices.output-escapingComment on this rule -
Tap targets are big enough and far enough apart 24x24 CSS px minimum, or 24 px of spacing required
Make every tap target at least 24 by 24 CSS pixels, or leave 24 pixels of clear space around a smaller one. That is WCAG 2.2 SC 2.5.8 at AA and it is the bar this rule measures. The 44 by 44 figure is SC 2.5.5 at AAA, a house bar the design kit aims at rather than a legal floor. EN 301 549 v3.2.1 still references WCAG 2.1, so 24 px is ahead of what the law harmonises today.
Without it, ad-hoc floors on the primary buttons leave the small ones untested, and a thumb on a 390px screen hits the neighbour.
Standard Web Content Accessibility Guidelines (WCAG) 2.2 2.5.8 (AA), in part
Developer The build proves it About A11y
a11y.target-sizeComment on this rule -
A block is three files sharing one slug template, stylesheet, script, one slug required
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.
Without it, dead stylesheets and scripts pile up for blocks that no longer have a template, and every one of them still builds and ships.
House rule CONVENTIONS.md CONVENTIONS.md:144-149, in partnot public
Developer The build proves it About Colibrity
colibrity.block-file-tripleComment on this rule -
JavaScript targets data attributes, never CSS classes data- for behaviour, classes for styling required
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.
Without it, a restyle renames a BEM class and an animation silently stops working, on the client's site, weeks after anyone reviewed the code.
House rule This standard's own decision record docs/decisions.md:33-35
Developer The build proves it About Colibrity
colibrity.js-hooks-are-data-attributesComment on this rule -
Every page below the home page shows one breadcrumb trail one trail, root in page language, BreadcrumbList mirrors it required
Render one breadcrumb trail on every page below the home page, in the served HTML, reflecting where the page sits rather than how the visitor got there. The root item is in the page's language, the last item is the current page and is not a link. Emit a BreadcrumbList carrying the same items, names and order as the visible trail. Two trails, or a trail its JSON-LD contradicts, is worse than none.
Without it, the breadcrumb Google prints in the result is not the one on the page - wrong language, a level short, on every result for the site.
Vendor documentation Google Search Central documentation structured-data/breadcrumb#list-item, in part
Developer The build proves it About Crawl and indexation
crawl.breadcrumb-trailComment on this rule -
The content that must rank is in the server HTML h1, copy and links present with JavaScript off required
Put the h1, the body copy, the internal links and anything you want quoted in a result into the HTML the server returns. Client-side rendering is crawled on a second pass that is neither guaranteed nor prompt. Check it with curl or with JavaScript disabled, never in the Elements panel, which shows the rendered DOM and always looks fine.
Without it, the copy that has to rank waits in a render queue that may never come round, and DevTools shows it present the whole time.
Vendor documentation Google Search Central documentation javascript-seo-basics#how-googlebot-processes-javascript, in part
Developer The build proves it About Crawl and indexation
crawl.content-in-server-htmlComment on this rule -
Page regions use landmark elements exactly one main, every nav named required
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.
Without it, jumping by region skips the mobile menu and the site header, because neither one sits inside a landmark.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.3.1 (A), in part
Developer The build proves it About HTML
html.landmarksComment on this rule -
Lists are lists and tables are tables no layout tables, th with scope required
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.
Without it, a screen reader never announces the grid's item count, so nobody using one can tell how many results the filter returned.
Standard HTML Living Standard 4.9.1 The table element, in part
Developer The build proves it About HTML
html.lists-and-tablesComment on this rule -
Ids are unique on the rendered page required
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.
Without it, clicking a label jumps focus to the first copy of a repeated block, and the visitor fills in the wrong form.
Standard HTML Living Standard 3.2.6 Global attributes, in part
Developer The build proves it About HTML
html.no-duplicate-idsComment on this rule -
No inline event handlers or javascript: URLs required
Bind behaviour in scripts, not in onclick attributes. Inline handlers block any meaningful Content Security Policy and scatter behaviour across templates.
Without it, any Content-Security-Policy worth having has to ship with unsafe-inline, the one setting that lets the injected script run.
House rule Delivered reference builds, audited together no on* attribute in either theme, both buildsnot public
Developer The build proves it About HTML
html.no-inline-handlersComment on this rule -
One h1 per page, and no skipped heading levels required
Give each page exactly one h1 naming the page, and no two pages the same one. Descend one level at a time. If a heading looks wrong at its correct level, that is a styling problem, not a structure problem.
Without it, a screen-reader user skimming by heading level hits an h3 under an h1 and cannot tell which section it belongs to.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.3.1 (A), in part
Developer The build proves it About HTML
html.single-h1Comment on this rule -
The current page is marked in the navigation exactly one aria-current=page per navigation required
Mark the item for the current page with aria-current="page" in every navigation - header, footer, breadcrumb. A section landing page marks its own item, not its whole branch. Give it a visible cue as well, and not colour alone: design.not-colour-alone grades that half, so this rule is graded on the attribute only.
Without it, a screen-reader user tabbing through the menu gets nothing saying which item is the page they are already standing on.
Client document Website Design and Development Checklist section 3, item 7, in partnot public
Developer The build proves it About Interface states
states.current-pageComment on this rule -
Class names follow BEM and match their block slug block__element--modifier, block name = slug recommended
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.
Without it, the next developer greps a block's slug, gets no hits in the SCSS, and ends up editing the wrong stylesheet.
House rule CONVENTIONS.md CONVENTIONS.md:144-149, in partnot public
Developer The build proves it About HTML
html.bem-namingComment on this rule -
Pagination controls are links, not buttons a real a href, middle-clickable blocker
Only when the listing has more items than fit on one page.
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.
Without it, everything past page one falls out of the link graph, so those detail pages rank only if something else happens to link them.
Ships in
wp-starter/archive-state- import it rather than rebuilding it.Client document Technical SEO control grid row 53not public
Developer The build proves it Why this rule exists About Colibrity
colibrity.pagination-crawlableComment on this rule -
A working skip link is the first focusable element required
Put a skip link at the top of the body pointing at the main landmark's id. Hidden until focused, clearly visible when focused.
Without it, every keyboard visitor re-tabs the full header and nav on every single page before reaching the content they came for.
Ships in
wp-starter/skip-link- import it rather than rebuilding it.Standard Web Content Accessibility Guidelines (WCAG) 2.1 2.4.1 (A), in part
Developer The build proves it About A11y
a11y.skip-linkComment on this rule
4Interaction and states 16 items
-
Everything works with a keyboard alone Tab, Shift-Tab, Enter, Space, Escape blocker
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.
Without it, a mobile menu built from an href-less anchor never opens for someone on a keyboard - one delivered build shipped exactly that.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 2.1.1; 2.1.2 (A), in part
Developer The build proves it Why this rule exists About A11y
a11y.keyboard-operableComment on this rule -
Content is visible when JavaScript does not run JS off: 0 elements left at opacity 0 or out of viewport blocker
Never ship a reveal whose resting state is invisible in the stylesheet. Set the hidden state from script, or gate it on a class the script adds, so a page with no JavaScript renders everything in its final position. crawl.content-in-server-html, in the SEO volume, owns the other half of one JavaScript-off load: the copy is in the response bytes. This rule owns that it is then visible.
Without it, one failed script leaves dozens of sections at opacity 0, and the visitor gets a blank page while the copy sits there in the HTML.
Client document Website Design and Development Checklist section 2, item 12not public
Developer The build proves it Why this rule exists About Interface states
states.no-js-fallbackComment on this rule -
The preloader has a hard cap and a fallback page visible within 3 s of first byte whatever fails blocker
Only when the project shows a preloader before the first paint.
Give the preloader a timeout that reveals the page whatever has not finished, and never gate it on an event that can fail - a font, a video, a third-party script. Until it clears the visitor sees nothing at all, on exactly the connections it hurts most. wp-starter has no preloader: site-transition.php is a route-change wipe and nothing gates first paint, so the cap is project work.
Without it, one font or third-party script that never resolves leaves the visitor staring at an opaque overlay with no way past it.
Client document Website Design and Development Checklist section 2, item 11, in partnot public
Developer The build proves it Why this rule exists About Interface states
states.preloader-budgetComment on this rule -
Errors say what is wrong and how to fix it in text, announced, focus on the first required
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.
Without it, a red border alone says nothing to the one man in twelve who cannot see it, and the failed field sits off-screen unfound.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 3.3.1; 3.3.3; 1.4.1; 4.1.3 (A, AA), in part
Developer The build proves it Why this rule exists About A11y
a11y.form-errorsComment on this rule -
Animation is cheap and stops when off screen transform and opacity only required
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.
Without it, an accordion animating height relays out the page on every frame, making the one interaction people actually use the jankiest thing there.
Vendor documentation web.dev developer guides articles/animations-guide, in part
Developer The build proves it About Perf
perf.animation-costComment on this rule -
Scripts clean up after themselves on page transition listeners, tweens and observers torn down required
Only when the project uses client-side page transitions.
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.
Without it, the fifth page a visitor opens runs five copies of the same scroll handler, and only long sessions ever show it.
House rule Delivered reference builds, audited together src/js/core/page-lifecycle.js destroyPage(), both buildsnot public
Developer The build proves it About Practices
practices.self-cleaning-modulesComment on this rule -
Every interactive element ships all of its states hover, active, disabled and open, script-inserted included required
Style hover, active, disabled and open for every control - buttons, links, inputs, selects, accordion and menu triggers. Bind the styles to a class or a data attribute, never to the markup the server rendered, so a card arriving from a filter fetch gets them too. Anything a hover reveals is reachable by a tap as well, or it is decoration. Focus belongs to a11y.focus-visible.
Without it, a card arriving from a filter fetch is dead to the touch while an identical one above it lights up on hover.
Client document Website Design and Development Checklist per-page item 13, in partnot public
Developer The build proves it About Interface states
states.interactive-setComment on this rule -
Anything that takes time says so visible feedback within 500 ms, cleared on failure too required
Any action that can outlast half a second - a submit, a filter fetch, a load-more, a route change - shows it is working within 500 ms, and clears that state on failure as well as on success. Make it aria-busy or a live-region message, not only a spinner graphic. wp-starter ships no generic skeleton or spinner, so this is per-feature work. This rule owns the 500 ms threshold.
Without it, people hit submit a second time because nothing moved, and a failed request leaves the spinner turning forever.
Client document Website Design and Development Checklist section 4, item 14, in partnot public
Developer The build proves it About Interface states
states.loading-feedbackComment on this rule -
Scroll reveals fire on deep links and on back navigation 0 elements hidden past their trigger, after #anchor or back required
Only when content on the project animates in as the visitor scrolls.
Three arrivals start the visitor below the trigger: a /page#section deep link, the back button, and a bfcache restore. A reveal bound to a downward scroll fires on none of them. Play anything already past its trigger on init, and refresh trigger positions on pageshow rather than on load alone.
Without it, arriving on a /page#section link or pressing back lands the visitor in a stretch of page that never un-hides.
Client document Website Design and Development Checklist section 2, item 14not public
Developer The build proves it About Interface states
states.scroll-reveal-recoveryComment on this rule -
Sticky header and menu scroll behaviour is decided up front 0 shift on stick, anchor clears the header, scroll locked required
Write down what the header does on scroll - stays, hides going down and returns going up, or none of it. Static is a valid answer; nobody deciding is not, and that is how one section hides the header and another does not. Reserve its height so nothing jumps when it sticks, offset anchor targets by it, and lock the body scroll while an off-canvas menu is open.
Without it, the page behind an open off-canvas menu scrolls away under the visitor's finger, and every anchor lands its heading under the header.
Client document Website Design and Development Checklist section 11, item 6, in partnot public
Developer The build proves it About Interface states
states.sticky-chromeComment on this rule -
prefers-reduced-motion is honoured everywhere final state, not a faster animation blocker
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 cause the harm. Stopping auto-play is SC 2.2.2 at Level A and binds; honouring the preference is SC 2.3.3 at AAA, which is ours.
Without it, footer parallax keeps running for a visitor with a vestibular disorder who already asked their OS to stop it, and nausea lasts hours.
Ships in
wp-starter/motion-guard- import it rather than rebuilding it.Standard Web Content Accessibility Guidelines (WCAG) 2.1 2.2.2; 2.3.3 (A, AAA), in part
Developer The build proves it Why this rule exists About A11y
a11y.reduced-motionComment on this rule -
Filters write their state to the URL state in the URL, cleared parameters deleted blocker
Only when the page has a listing, archive or search with filters.
Keep one named list of filter parameters. On any filter change write the active values into the query string and delete the parameter when a filter is cleared, without adding a history entry. On load, the filter UI and the result set must both match the query string before results first paint. Client-rendered and server-rendered listings get there differently; the explainer has both.
Without it, a visitor who opens a result and presses Back lands on the unfiltered list, and nobody can send anyone the filtered view.
Ships in
wp-starter/filter-url-state- import it rather than rebuilding it.Client document Front-end brief "each filter should update the URL parameters when changed", in partnot public
Developer The build proves it Why this rule exists About Colibrity
colibrity.filter-url-stateComment on this rule -
Every page of a listing has its page number in the URL /page/2/, and it works with JS off blocker
Only when the listing has more items than fit on one page.
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.
Without it, a crawler loads the listing, sees twelve of two hundred items and never presses the button, so the other 188 go undiscovered.
Ships in
wp-starter/archive-state- import it rather than rebuilding it.Client document Front-end brief "each pagination page should have the page number reflected in the URL", in partnot public
Developer The build proves it Why this rule exists About Colibrity
colibrity.pagination-in-urlComment on this rule -
Modals trap focus, return it, and close on Escape required
Only when the project has a modal, drawer, cookie banner, lightbox or off-canvas menu.
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.
Without it, tabbing out of an open drawer lands on links behind it, and closing sends focus back to the top of the document, not the trigger.
Ships in
wp-starter/dialog-a11y- import it rather than rebuilding it.Standard Web Content Accessibility Guidelines (WCAG) 2.1 2.1.2; 2.4.3; 4.1.2 (A), in part
Developer The build proves it About A11y
a11y.dialog-behaviourComment on this rule -
Filter changes do not litter the back button one back press leaves the page required
Only when the page has a listing, archive or search with filters.
Do not add a history entry per filter change. Ten filter clicks must not cost the visitor ten presses of the back button to leave the page. In a client-rendered listing that means history.replaceState rather than pushState. In a server-rendered one replaceState cannot help, because the navigation that follows pushes its own entry regardless - use location.replace(), which navigates without one.
Without it, ten filter clicks cost ten presses of Back to leave the page, which every visitor reads as a broken back button.
Ships in
wp-starter/filter-url-state- import it rather than rebuilding it.House rule Delivered reference builds, audited together replaceState on filter change, one build of the twonot public
Developer The build proves it Why this rule exists About Colibrity
colibrity.filter-history-noiseComment on this rule
5Forms 12 items
-
File uploads declare what they accept and why they refuse sniffed-MIME allow-list, a byte cap, a named reason blocker
Only when the project has a form that collects data from a visitor, and a form on the project accepts a file upload.
wp-starter ships no upload handling at all, so this is new code on every project and gets reviewed as such. Check the file's sniffed type against an allow-list, cap the bytes, generate the stored filename yourself, and keep uploads out of the web root or behind a rule that will not execute them. An extension check is not a check - a PHP renamed to .pdf passes it. Name the limit they hit.
Without it, an extension check passes a file that is not what it claims, and it lands in a folder that will happily execute PHP.
Client document Website Design and Development Checklist section 4, item 16, in partnot public
Developer The build proves it About Forms
forms.file-uploadsComment on this rule -
Stored form submissions are unreachable without logging in submission URLs return 401 or 404 anonymously blocker
Only when the project has a form that collects data from a visitor.
Register anything holding a submission - messages, subscribers, consent records - as a non-public post type: not public, not publicly queryable, excluded from search, with mapped meta capabilities. Nothing you import covers a submission type your project adds, so check every one you register. GDPR article 32 makes confidentiality a legal obligation, not good practice, which is why it blocks.
Without it, every message the contact form ever took is readable at a guessable URL, which is a personal-data breach the client has to report.
Developer The build proves it Why this rule exists About Security and hardening
security.submission-store-privateComment on this rule -
Forms carry three layers of spam defence honeypot, a scored gate, and a per-IP rate limit required
Only when the project has a form that collects data from a visitor.
Three layers, because each stops a different attacker. A honeypot field, hidden from people and filled in by naive bots. A scored gate such as reCAPTCHA v3 or Turnstile, configured to refuse when its verifier is unreachable - wp-starter's is off by default and fails open. And a per-IP rate limit, which wp-starter does not ship at all: build it, or one solved nonce buys unlimited submissions.
Without it, the captcha verifier times out once and everything sent that minute is waved through - both delivered themes return a pass on a transport error.
Client document Website Design and Development Checklist section 4, item 11, in partnot public
Developer The build proves it Why this rule exists About Forms
forms.anti-spamComment on this rule -
A successful submit produces a confirmation nobody can miss focus lands on it, announced, still there at 30 s required
Only when the project has a form that collects data from a visitor.
Decide here whether success is a thank-you URL or an inline confirmation, and write the decision into the handover - the noindexed thank-you page and the conversion event both depend on which it is. Then move focus to the confirmation, announce it to assistive technology, and let it stay. A toast that fades after four seconds fails: the one who most needs it looked away while it sent.
Without it, the confirmation is gone in three seconds, as it is in both delivered themes, and the visitor sends the form again to check it worked.
Client document Website Design and Development Checklist section 4, item 15, in partnot public
Developer The build proves it About Forms
forms.confirmationComment on this rule -
An error appears on blur, not while someone is typing shown when the field is left, cleared once valid required
Only when the project has a form that collects data from a visitor.
Validate a field when the visitor leaves it, not while they are still typing. An error on the third character of an email address tells someone they are wrong before they have finished being right. Once a message is showing, clear it the moment the value becomes valid, without making them leave the field a second time. On submit, check everything again.
Without it, half a typed email address gets marked wrong in red mid-word, and the message sits there after the visitor has finished fixing it.
Client document Website Design and Development Checklist section 4, item 5, in partnot public
Developer The build proves it About Forms
forms.error-timingComment on this rule -
Form inputs are 16 pixels so iOS does not zoom on focus 16 px computed at 390 px, and pinch-zoom still works required
Only when the project has a form that collects data from a visitor.
Give every input, select and textarea a computed font-size of at least 16px at mobile widths. Under that, iOS Safari zooms the page on focus and never zooms back out. Do not fix it by disabling pinch-zoom in the viewport meta: that trades one bug for a WCAG 2.1 resize-text failure and contradicts the 200 percent zoom rule.
Without it, tapping the email field on an iPhone zooms the page in and leaves the rest of the form off the right edge with no way back.
Client document Website Design and Development Checklist section 4, item 1, in partnot public
Developer The build proves it About Forms
forms.input-font-sizeComment on this rule -
Every field declares its type and its autocomplete token a token from the spec list on every identity field required
Only when the project has a form that collects data from a visitor.
Set the input type that matches the data - email, tel, url, number - so the phone keyboard changes with the field. Put an autocomplete token from the HTML spec list on every field asking for the visitor's own details. SC 1.3.5 at AA asks only that such a field's PURPOSE be programmatically determinable; autocomplete is how we do it, and the word appears nowhere in WCAG 2.1.
Without it, a phone user retypes name, email, phone and postcode by hand - a delivered contact form ships six identity fields with no autofill token.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.3.5 (AA), in part
Developer The build proves it About Forms
forms.input-type-and-autocompleteComment on this rule -
A submit happens once and loses nothing 1 record on a double click, values kept on failure required
Only when the project has a form that collects data from a visitor.
Make a repeated submission idempotent on the server, not merely unclickable in the browser. Derive a key from the payload, claim it once, and answer a replay with the same success - the theme already does this for conversion events in wps_capi_claim(). wp-starter ships nothing for forms: the client latch defeats a double click and nothing else, so two identical POSTs write two records.
Without it, an impatient double click writes two message records and two consent records, and the consent log is the thing a GDPR auditor counts.
Client document Website Design and Development Checklist section 4, item 12, in partnot public
Developer The build proves it About Forms
forms.submit-lifecycleComment on this rule -
Email and phone are checked without rejecting valid values email needs a dot TLD, phone stored E.164 if international required
Only when the project has a form that collects data from a visitor.
type=email is not enough on its own: the HTML Living Standard accepts a@b, because intranet mail is valid. Add a check that the domain has a dot and a TLD, and stop there. Anything stricter refuses real customers, and the only proof an address works is a confirmation email that arrives. Put no pattern on the phone field - numbering plans change. Strip spaces, store international numbers as E.164.
Without it, phone numbers reach the CRM as +33 6 12 34 56 78, spaces and all, so nothing downstream can dial, dedupe or match them.
Standard HTML Living Standard 4.10.5.1.5 Email state (type=email), in part
Developer The build proves it About Forms
forms.value-formatsComment on this rule -
Login and every public form endpoint are rate limited 20 rapid POSTs, refused before the 20th required
Only when the project has a form that collects data from a visitor.
Count attempts per IP and per route and refuse past a threshold. A transient counter is enough - there is no CDN and no WAF in front of these sites, so if the application does not count, nothing does. Cover the login form and every public route that accepts a submission. A honeypot and a captcha stop bots; neither stops a script pointed at the login.
Without it, wp-login.php takes password guesses as fast as a script can send them, and only the host is counting, if it counts at all.
Standard OWASP Application Security Verification Standard 5.0.0 6.3.1 (L1), in part
Developer The build proves it About Security and hardening
security.public-post-rate-limitComment on this rule -
Every validation rule runs again on the server a POST that bypasses the form is rejected field by field blocker
Only when the project has a form that collects data from a visitor.
Re-run every rule the browser runs, on the server, before anything is stored or emailed: required fields, declared types, lengths, consents. Everything the browser checks is optional from the attacker's side - the endpoint is public and takes a POST from anything. Use wp-starter's form registry, where one field map drives validation, storage, the notification and the export.
Without it, curl posts past every browser check and writes a message record, a consent record and a notification email out of whatever junk it sent.
Ships in
wp-starter/form-registry- import it rather than rebuilding it.Client document Website Design and Development Checklist section 4, item 10not public
Developer The build proves it Why this rule exists About Forms
forms.server-side-validationComment on this rule -
A form still submits from a cached page fresh token at submit time, from a no-store endpoint required
Only when the project has a form that collects data from a visitor.
Assume every page reaches an anonymous visitor from a cache. A nonce printed into the page is printed into the cache, where it expires for everyone while it keeps working for the logged-in developer who tests it. Fetch the token at submit time from an endpoint that answers no-store, and never read the consent cookie server-side on a page that can be cached.
Without it, every anonymous submit fails with a 403 while the developer's logged-in test passes, and the enquiries are lost in silence.
Ships in
wp-starter/form-registry- import it rather than rebuilding it.House rule Delivered reference builds, audited together a fresh-nonce route in the third build; the two others print the nonce into the pagenot public
Developer The build proves it About Forms
forms.cache-safe-submitComment on this rule
6Consent and legal 9 items
-
Refusing cookies is as easy as accepting them refuse in one click, at the same visual weight as accept blocker
Only when the project loads analytics, advertising or marketing tags.
Give refuse and accept the same prominence on the banner's first screen - same size, same contrast, same level in the hierarchy. A refuse buried one panel deeper is the asymmetry CNIL sanctioned on 31 December 2021: EUR 150 million against Google, EUR 60 million against Meta. CNIL also recommends a choice be honoured for six months; wp-starter's cookie runs twelve, which is the developer's half.
Without it, refusing takes more clicks than accepting, so CNIL does not count what the banner collected as consent at all.
Regulator Recommandation cookies et autres traceurs, version consolidee 2.3 (modalites du refus), in part
Designer The build proves it Why this rule exists About Consent and legal
legal.cookie-bannerComment on this rule -
The privacy policy is a real page, linked where data is taken own permalink, 200, linked from footer and every form blocker
Publish the privacy policy at its own permalink and link it from the footer and from every form that collects personal data. The text is the client's to supply; the developer publishes it. Keep it out of any blanket noindex sweep - it is the first page a regulator looks for. A French site also owes a separate mentions legales page. That is LCEN, not GDPR, and this rule does not score it.
Without it, a form collects names and emails while its policy link 404s or sits behind a noindex sweep.
Law Regulation (EU) 2016/679 (General Data Protection Regulation), consolidated text Art. 13(1), in part
Developer The client proves it About Consent and legal
legal.documents-publishedComment on this rule -
Newsletter consent is its own optional checkbox terms ticked, newsletter unticked: 0 subscriber records blocker
Only when the project has a form that collects data from a visitor, and the project captures newsletter or marketing consent.
Give the newsletter its own box: unticked, optional, with its own wording naming the sender and the frequency. One box must never cover both the terms and the mailing list - consent has to be specific to each separate purpose, and a bundled box cannot show which one the person agreed to. wp-starter's subscriber store ships commented out, so this is a module you switch on.
Without it, somebody downloading a PDF lands on the mailing list from a box that never mentioned one, and no record says which they agreed to.
Law Regulation (EU) 2016/679 (General Data Protection Regulation), consolidated text Art. 4(11), in part
Developer The build proves it About Consent and legal
legal.newsletter-separateComment on this rule -
The terms box starts unchecked and the server enforces it unchecked after a back-nav, refused server-side, names it blocker
Only when the project has a form that collects data from a visitor.
Ship the box unchecked, refuse the submit server-side when it is missing, and name the document and its version in the label beside a working link. Set autocomplete=off on that box: Firefox and Safari restore form state on back-navigation and would re-tick it. That is the opposite of the advice on identity fields, and deliberate. This proves acceptance of a contract, so do not label it consent.
Without it, Safari and Firefox re-tick the box on a back-navigation, so the record shows an acceptance the visitor never made.
Client document Website Design and Development Checklist section 5, item 3, in partnot public
Developer The build proves it About Consent and legal
legal.terms-consent-checkboxComment on this rule -
The consent log is exportable and has a retention period a stated period, an export, and a job that runs required
Only when the project has a form that collects data from a visitor.
Ask the client or their DPO for the retention period and publish it in the privacy policy: Art. 13(2)(a) requires the period, or the criteria for setting it, to be told to the person. Then build the job that deletes past it, which is what discharges Art. 5(1)(e). A stated period nobody enforces satisfies neither. wp-starter ships no purge job, so this one is yours to write.
Without it, records pile up behind a policy that promises they are deleted, which is worse than promising nothing.
Operations The client proves it Why this rule exists About Consent and legal
legal.consent-retentionComment on this rule -
No personal data travels in a URL 0 emails, names, phones or tokens in a query string or path required
Keep personal data out of every URL you generate, redirect to or link. A URL leaks four ways at once: the access log, the Referer header sent to every third party the next page loads, the analytics page path, and browser history on a shared machine. The usual failure is a thank-you redirect carrying the address so the page can greet the visitor - use a server-side flash instead.
Without it, an address typed into one form turns up months later in the analytics page-path report and in a shared browser's history.
Developer The build proves it About Consent and legal
legal.no-pii-in-urlsComment on this rule -
Consent records carry the context of the consent source URL, purpose and method, each set by the caller recommended
Only when the project has a form that collects data from a visitor.
Alongside the four fields that prove a consent, store where it was given, what it was for, and how it was collected. Set the method at the call site: wp-starter hardcodes it to checkbox, which is honest for a site with one path and becomes a lie the day a double opt-in is added. Where one form captures two consents, the purpose is what tells the records apart.
Without it, every record still claims a ticked checkbox the day a double opt-in ships, and two consents from one form cannot be told apart.
Law Regulation (EU) 2016/679 (General Data Protection Regulation), consolidated text Art. 7(1), in part
Developer The build proves it About Consent and legal
legal.consent-record-contextComment on this rule -
Every consent is recorded with what it takes to prove it who, when, the exact wording shown, and its version blocker
Only when the project has a form that collects data from a visitor.
Write one record per consent carrying who gave it, the timestamp, the exact wording they were shown and the version of that wording. Resolve the wording from the same function the label renders from, so shown and stored cannot drift. Records stay deletable by an administrator, so where a dispute is plausible, back the log up rather than trusting the post table.
Without it, a disputed opt-in comes down to your word against theirs, and nothing stored says what wording the person actually saw.
Ships in
wp-starter/consent-log- import it rather than rebuilding it.Law Regulation (EU) 2016/679 (General Data Protection Regulation), consolidated text Art. 7(1), in part
Developer The build proves it Why this rule exists About Consent and legal
legal.consent-recordComment on this rule -
No analytics or marketing tag fires before a choice 0 third-party requests before a choice, exactly 1 container blocker
Only when the project loads analytics, advertising or marketing tags.
Load nothing from a tag vendor until the visitor has chosen. Grade this on network requests, not configuration: Consent Mode defaulted to denied still loads gtag and still pings Google. Ship exactly one tag container. Withhold any vendor with no consent-mode equivalent entirely, and record in the handover that container access lets the client undo this.
Without it, Google has already been pinged before the visitor is asked, so refusing changes nothing and the banner is decoration.
Ships in
wp-starter/analytics-registry- import it rather than rebuilding it.Regulator Recommandation cookies et autres traceurs, version consolidee 2 (chapeau), in part
Developer The build proves it Why this rule exists About Consent and legal
legal.no-tags-before-consentComment on this rule
7Media and payload 7 items
-
Images carry alt text that earns its place empty alt on decorative, never "image of" blocker
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.
Without it, a screen reader reads out the filename instead, and a client-filled media library passes the scanner with alt text nobody wrote.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.1.1 (A), in part
Developer The build proves it Why this rule exists About A11y
a11y.alt-textComment on this rule -
Uploads cannot execute, and no script-capable type is allowed no script-capable type, uploads do not execute blocker
Add no mime type that can carry script - svg, html, js - unless a named sanitiser handles it and a capability gates who may upload it. Then stop the uploads directory executing PHP at the server level. That deny rule cannot live in the theme directory, it goes out with the mu-plugin sync or the stage script, so it is easy to write once and never deploy.
Without it, an SVG with a script in it runs on the site's own origin, so one contributor upload becomes a shell or a stolen admin session.
Standard OWASP Application Security Verification Standard 5.0.0 5.3.1 (L1), in part
Operations The build proves it About Security and hardening
security.upload-types-and-executionComment on this rule -
Video and audio carry captions and controls captions on speech; audio over 3s, motion over 5s required
Only when the project has video or audio, or content that starts moving on its own.
Caption anything with speech. Audio that plays automatically for more than three seconds needs a pause, stop or volume control - SC 1.4.2, Level A, and this rule said five seconds for everything until 2026-08-26. Moving content that starts on its own and runs past five seconds needs the same, under SC 2.2.2. Captions must be accurate, not machine output nobody read.
Without it, a deaf visitor gets nothing from a video testimonial, and the one delivered build with speech video shipped zero track elements.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.4.2; 1.2.2; 2.2.2 (A), in part
Developer The build proves it About A11y
a11y.media-alternativesComment on this rule -
Fonts are subset woff2, self-hosted, with a swap policy woff2 only, none over 100 KB, one preload required
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.
Without it, half a megabyte of typefaces ships unnoticed, since nothing in the performance score is weighted against font bytes.
House rule PERFORMANCE.md PERFORMANCE.md:135-144, in partnot public
Developer The build proves it Why this rule exists About Perf
perf.fontsComment on this rule -
Images ship in a modern format at the size they display WebP or AVIF with srcset and sizes, 200 KB per image at most required
Serve WebP or AVIF with a fallback, generate responsive sizes, and use srcset and sizes so a phone never downloads a desktop image. Cap every image at 200 KB on the wire, and ship an animated GIF as a muted looping video, never as a GIF. A full-width PNG hero is the single most common cause of a slow mobile LCP on these projects.
Without it, a phone on mobile data downloads the 2000px desktop hero to paint it 390px wide, several times the bytes it needed.
Vendor documentation Web Vitals choose-the-right-image-format, in part
Developer The build proves it About Perf
perf.image-formatsComment on this rule -
Below-the-fold images lazy load, the LCP image does not lazy below the fold, fetchpriority on LCP required
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.
Without it, a bulk find-and-replace puts loading=lazy on the hero and delays the one request the entire metric hangs on.
Vendor documentation Web Vitals optimize-lcp, in part
Developer The build proves it Why this rule exists About Perf
perf.lazy-and-priorityComment on this rule -
Video is deferred and never blocks the first paint poster, preload none, muted, playsinline, 2 MB per file required
Only when the project has background or hero video.
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. Keep each file under 2 MB; anything longer streams from a service or is cut. Above-the-fold autoplay video is usually the reason mobile LCP cannot be fixed by anything else.
Without it, someone on mobile data pays for two megabytes of decorative clip before the page is usable, and never presses play.
House rule PERFORMANCE.md PERFORMANCE.md:465-520, in partnot public
Developer The build proves it About Perf
perf.video-weightComment on this rule
8SEO head and schema 11 items
-
Every indexable page declares a self-referencing canonical one, absolute, on the live host blocker
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.
Without it, one rename leaves 87 URLs canonicalising somewhere else, and the crawler consolidates the whole set onto the wrong address.
Automated check:
seo-status:canonicalVendor documentation Google Search Central documentation crawling-indexing/consolidate-duplicate-urls, in part
Developer The build proves it About SEO
seo.canonicalComment on this rule -
Filter combinations do not become a crawl trap indexable and in the sitemap, or canonicalised required
Only when the page has a listing, archive or search with filters.
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.
Without it, multi-select filters mint combinations faster than any crawler can exhaust them, and the budget meant for product pages goes there instead.
Vendor documentation Google Search Central documentation crawling-managing-faceted-navigation, in part
Developer The build proves it Why this rule exists About Colibrity
colibrity.filter-indexabilityComment on this rule -
Paged views are canonical to themselves page two canonicals to page two required
Only when the listing has more items than fit on one page.
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.
Without it, page two tells the crawler it does not exist, so it stops being followed and the items only listed there quietly leave the index.
Automated check:
seo-status:canonicalVendor documentation Google Search Central documentation pagination-and-incremental-page-loading#use-urls-correctly, in part
Developer The build proves it About Colibrity
colibrity.paged-canonicalComment on this rule -
Every URL-minting template has a recorded index decision one recorded decision per template, and the sitemap agrees required
List every template that mints a URL - post types, taxonomy archives, author and date archives, attachment pages, search results, paginated tags - and every non-HTML route, feeds, PDFs, uploads, and record index or noindex against each. Apply the decision in the theme, not in a plugin checkbox; a non-HTML URL carries it as an X-Robots-Tag header. Anything undecided is indexed by default.
Without it, author, date and attachment archives nobody thought about get indexed by default, and thin near-duplicates compete with the real pages.
Automated check:
seo-status:tax-sitemapsClient document Website Design and Development Checklist section 8, item 5, in partnot public
Developer The build proves it About Crawl and indexation
crawl.indexability-decidedComment on this rule -
Structured data says what the page actually says type fits the page, values match the rendered copy required
Read every value in the JSON-LD against the page it sits on. The headline is the editorial title, the author is a real person or organisation, the dates are the real ones, and wordCount, if you emit it, is the real count. Never emit an aggregateRating for reviews that only exist client-side. A validator checks the shape and can see none of this.
Without it, Google is told the article is 8 words long when 659 are on screen, and the validator that would catch a typo reports it all green.
Automated check:
seo-status:org-brand,seo-status:org-sameasVendor documentation Google Search Central documentation appearance/structured-data/sd-policies, in part
Developer The build proves it About On-page signals
onpage.schema-fidelityComment on this rule -
Multilingual sites declare reciprocal hreflang every version, plus itself, plus x-default, ISO codes only required
Only when the site serves more than one language.
Emit hreflang alternates on every translated page, including a self-reference and an x-default, with codes that are ISO 639-1 languages and, where needed, an ISO 3166-1 alpha-2 region after a hyphen. Every alternate must point back, or search engines discard the whole set.
Without it, a visitor searching in French is served the English URL, and nothing in the markup looks broken because a discarded set is silent.
Automated check:
seo-status:hreflangVendor documentation Google Search Central documentation specialty/international/localized-versions, in part
Developer The build proves it About SEO
seo.hreflangComment on this rule -
Open Graph and Twitter card tags are complete 8 tags, absolute og:image at 1200x630 required
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.
Without it, the client shares the launch post on LinkedIn and gets a grey box with a bare URL where the brand image should be.
Standard The Open Graph protocol og:title, og:type, og:image, og:url, in part
Developer The build proves it About SEO
seo.open-graphComment on this rule -
robots.txt exists and points at the sitemap an absolute Sitemap: line, itself a 200, no asset Disallowed required
Ship a robots.txt that disallows sandbox, styleguide and any internal preview route, and names the sitemap with an absolute URL. Never disallow the theme's assets or the uploads: a crawler that cannot fetch the CSS renders a blank page and grades it.
Without it, a relative Sitemap line is never resolved, and the preview routes sit crawlable - /sandbox/ answered 200 on a live site.
Automated check:
seo-status:robotsVendor documentation Google Search Central documentation crawling/docs/robots-txt/create-robots-txt, in part
Developer The build proves it About SEO
seo.robots-txtComment on this rule -
The sitemap lists every indexable URL and nothing else one generated sitemap, all 200, none noindex required
Generate the sitemap rather than maintaining it by hand, from one source: when an SEO plugin emits it, switch the core one off, so two indexes never disagree. Exclude anything that is noindex, redirected or gone. Hand-maintained sitemaps go stale the first week nobody remembers them.
Without it, seven URLs in one live sitemap answer 404, six of them category archives with no template, and the crawler is handed them on purpose.
Automated check:
seo-status:sitemap,seo-status:tax-sitemapsVendor documentation Google Search Central documentation crawling-indexing/sitemaps/build-sitemap, in part
Developer The build proves it About SEO
seo.sitemapComment on this rule -
Structured data is present, valid and singular one node per type per page, no errors required
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.
Without it, two Organization nodes give Google two different names for the brand, and nobody decides which one it takes.
Automated check:
seo-status:org-one,seo-status:org-brand,seo-status:org-sameasVendor documentation Google Search Central documentation appearance/structured-data/intro-structured-data, in part
Developer The build proves it About SEO
seo.structured-dataComment on this rule -
The site ships a favicon Google can crawl square, at least 48 px, 200, not Disallowed recommended
Set a site icon rather than shipping the WordPress default W. Google wants a square image, at least 8 pixels a side and larger than 48 recommended, referenced from a link element in the head, on a URL that robots.txt does not block. Ask the agency for the source icon at kickoff - it is a brand asset, and a build cannot invent one.
Without it, the WordPress W, or a blank globe, sits next to the brand in every mobile search result and every browser tab.
Vendor documentation Google Search Central documentation favicon-in-search#guidelines, in part
Developer The build proves it About On-page signals
onpage.faviconComment on this rule
9Content and language 15 items
-
Link text makes sense out of context never "here", "read more" or "click" required
Write links that describe their destination. Screen reader users list links out of context, where twelve links called "read more" are twelve identical links. SC 2.4.4 at Level A asks only that the purpose be clear from the link plus its surrounding sentence; making it clear from the link ALONE is SC 2.4.9 at AAA, and that is the bar this rule sets.
Without it, a listing of twenty cards gives a screen reader twenty links all reading Découvrir and all going somewhere different.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 2.4.4; 2.4.9 (A, AAA), in part
Developer The build proves it About A11y
a11y.link-textComment on this rule -
Every field and every fixed string says how it translates a preference per field, zero source-language theme strings required
Only when the site serves more than one language.
Where a custom field is registered, declare how it travels between languages: translated, copied once, or copied on every save. A field with no preference gets whatever the plugin defaults to that week. Route every fixed string the theme prints, labels, empty states, form messages, through the translation plugin's string table, so an editor changes it without a deploy.
Without it, a translated page keeps the source language in every label and empty state, and a copied field overwrites the translation on the next save.
House rule Delivered reference builds, audited together a preference on every field, all three builds; the string table in two of threenot public
Developer The build proves it About The admin the client receives
admin.translation-declaredComment on this rule -
Every language publishes the same set, or says what it leaves out one parity report per language, gaps listed with a reason required
Only when the site serves more than one language.
Measure each secondary language against the source language: published pages and posts, menu items, terms, and the block count of each translated page. A gap is filled or listed with a reason before launch, and a translation with fewer blocks than its source is a gap. Run the report from the content, not from the code: a green suite cannot see an empty menu.
Without it, the French site ships with no navigation for days while every gate in the project is green.
House rule Delivered reference builds, audited together a parity checker in one older build and the third; nothing of the kind in the other older buildnot public
Developer The build proves it About Crawl and indexation
crawl.language-parityComment on this rule -
Core CMS fields carry the content, not only the builder 0 published items with an empty title, excerpt or image required
Fill the core fields - title, excerpt, featured image - on every published item, not only the page builder's own hero and intro fields. The SEO plugin, site search, the RSS feed, the og:description fallback and the schema headline all read the core fields, and none of them shows up in a rendered page, so a builder-only site fails all five silently.
Without it, site search returns blank rows and shared links show no description, while the page itself looks perfect to everyone checking it.
Client document Website Design and Development Checklist section 8, item 12not public
Developer The agency proves it About On-page signals
onpage.core-fieldsComment on this rule -
The site's identity is set on purpose and the share card is seen title, tagline, separator, sameAs set; card seen in 3 apps required
Set the site title, the tagline and the title separator once, in the SEO plugin, and check that no page prints the WordPress default tagline. List every official brand account in the Organization node's sameAs. Then render the share card for the home page and one article in the Facebook, LinkedIn and iMessage previews, and fix what each shows before launch.
Without it, the client's first share reads Just another WordPress site, and the profile links Google shows beside the brand belong to nobody.
Client document Website Design and Development Checklist section 8, item 4, in partnot public
Developer The build proves it About On-page signals
onpage.site-identityComment on this rule -
Every dynamic component handles empty, long and broken zero items, one, many, long title, no image required
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.
Without it, a filter that matches nothing leaves a blank strip on the page and the visitor decides the site is broken.
House rule Delivered reference builds, audited together a rendered empty state per listing, both buildsnot public
Developer The build proves it About Practices
practices.empty-statesComment on this rule -
Editable content is editable, not baked into templates copy, images, links and order, from fields required
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.
Without it, the client emails you to change a phone number, and a one-word fix costs a developer, a deploy and an invoice.
House rule Delivered reference builds, audited together page.php loops have_rows(page_blocks), both buildsnot public
Developer The build proves it About Practices
practices.no-hardcoded-contentComment on this rule -
Titles and descriptions are present, unique and the right length titles 30-60, descriptions 70-160, one of each per page required
Give every template a title pattern and every published page a description. Titles run roughly 30 to 60 characters, descriptions 70 to 160. One of each per page: a theme and a plugin both emitting a description is a defect, not redundancy. Do not ship the theme defaults.
Without it, two templates share one title, so the results give no way to tell the pages apart and Google rewrites the title link itself.
Vendor documentation Google Search Central documentation appearance/title-link, in part
Developer The build proves it About SEO
seo.title-descriptionComment on this rule -
The 404 and 500 pages are designed, not server defaults site chrome, a route back, no raw server 500 required
Both pages render inside the site's own header, footer and type styles, and give a route back into the site. wp-starter's 404.php is a styled stub with no search and no suggested links, and there is no 500 or maintenance template at all, so anything past that stub is project work. Decide what a PHP fatal shows before launch, not after one.
Without it, a PHP fatal drops the visitor onto WordPress core's unstyled critical-error screen, with no header and no way back.
Client document Website Design and Development Checklist section 3, item 11, in partnot public
Developer The build proves it About Interface states
states.error-page-designComment on this rule -
A missing URL returns a real 404 in the right language 404 status on a missing URL, correct lang attribute required
Answer a URL that does not exist with a 404 status, never a 200 carrying an error message. A soft 404 keeps the missing page in the index and hides the fault from every monitoring tool. On a multilingual site the 404 under /fr/ carries lang="fr" and French copy. This applies to a new build as much as to a relaunch, where the SEO volume's redirect map covers it.
Without it, a missing URL answers 200, so Search Console files it as a soft 404 and no monitoring tool ever flags the broken link.
Vendor documentation Google Search Central documentation http-network-errors#2xx-success, in part
Developer The build proves it About Interface states
states.error-pagesComment on this rule -
The site publishes an accessibility statement target, known gaps, a contact route recommended
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.
Without it, nobody can say what the site targets or where to report a problem, and the client has nothing to produce when a complaint arrives.
Developer The build proves it About A11y
a11y.statementComment on this rule -
The admin reads in the language the editor set labels follow wp-admin's language, keys never do recommended
Only when the site serves more than one language.
On a multilingual project, switch the admin's own labels with the language wp-admin is displayed in, not with the language of the content being edited. Post type labels, list table columns and meta box rows all read in whatever language the editor chose. Slugs, meta keys and field names stay in one language and are never translated: they are identity, and a translated key is data loss.
Without it, labels follow the content-language switcher instead of the editor's own, so half the meta boxes stay in a language they cannot read.
House rule Delivered reference builds, audited together bilingual label helper, one build of the twonot public
Developer The build proves it About The admin the client receives
admin.editor-languageComment on this rule -
The heading outline reads as an outline recommended
Write headings that describe the section beneath them. Headings chosen for their size are a styling decision wearing a structural tag.
Without it, a screen-reader user skimming the headings hears the brand name and a sentence fragment ending on a preposition, and learns nothing.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 2.4.6 (AA), in part
Developer The build proves it About SEO
seo.headings-describe-contentComment on this rule -
An editor can duplicate any content item, translations included one action, one draft per language, no linked twin required
Give every content type the editor works in a Duplicate action. The copy is a draft carrying the original's fields, terms and featured image, owned by whoever clicked, with no slug until it is published. On a multilingual site one action copies every language from its own original into one new translation group, never a plugin twin the source overwrites on save.
Without it, the editor rebuilds a forty-field page by hand, or clones it with a plugin whose copy the original silently overwrites on the next save.
Ships in
wp-starter/duplicator- import it rather than rebuilding it.House rule Delivered reference builds, audited together inc/duplicate-core.php, the third build; a prefetch guard only in one of the twonot public
Developer The build proves it About The admin the client receives
admin.duplicate-contentComment on this rule -
Adding a block shows a picture first and never lands empty a rendered thumbnail per layout, real default copy recommended
The block picker is the client's first contact with the build. Show each block as a picture of itself, rendered from the block and regenerated when it changes, so the editor chooses by sight rather than by name. Ship every block with real default copy, so an added block is a first draft rather than an empty box.
Without it, the editor adds four blocks by name to see what they look like and deletes three, every time.
Ships in
wp-starter/block-defaults- import it rather than rebuilding it.House rule Delivered reference builds, audited together inc/admin-blocks.php and inc/block-defaults.php, the third build onlynot public
Developer The build proves it About Colibrity
colibrity.adding-a-blockComment on this rule
10Performance 13 items
-
CLS at or under 0.1 blocker
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.
Without it, a reader taps one link and lands on another, because a late image or web font shoved the page down under their thumb.
Vendor documentation Web Vitals cls, in part
Developer The build proves it About Perf
perf.clsComment on this rule -
LCP at or under 2.5 seconds on mobile 2.5s or less, median of 3 on mobile blocker
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.
Without it, a faded-in hero is dropped from the running and something slower below becomes the metric, so shaving bytes off the hero changes nothing.
Vendor documentation Web Vitals lcp, in part
Developer The build proves it Why this rule exists About Perf
perf.lcpComment on this rule -
Static assets are compressed and cached far-future br or gzip, one year immutable on hashed required
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.
Without it, a returning visitor re-downloads a byte-identical hashed bundle on every page view, paying for files that could not have changed.
Vendor documentation Web Vitals http-cache, in part
Developer The build proves it Why this rule exists About Perf
perf.caching-and-compressionComment on this rule -
Every asset loads once, has content, and only where used no asset requested twice, no 0-byte file, 4 hints max required
Open one network log per template and read it. No file requested twice, no near-empty stylesheet or script, no library loaded by a page that does not use it, and at most four preconnect or dns-prefetch hints. Past four, hints compete with the requests they should be speeding up. Nothing in the starter theme does this for you: it has one Vite entry and no per-block enqueue layer.
Without it, the same file is fetched twice on one page load, and each request looks reasonable on its own until somebody reads the whole log.
Client document Website Design and Development Checklist section 10, item 5, in partnot public
Developer The build proves it About Perf
perf.enqueue-hygieneComment on this rule -
TBT at or under 200ms and INP at or under 200ms required
Keep the main thread free during load. Defer non-critical scripts, break long tasks, and keep third-party tags off the critical path.
Without it, a tap on the burger does nothing while the main thread finishes parsing script, so people tap twice and open then close the menu.
Vendor documentation Web Vitals inp, in part
Developer The build proves it About Perf
perf.inp-tbtComment on this rule -
Nothing render-blocking that does not have to be defer or type=module, own critical CSS only required
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.
Without it, a blank white page for as long as another company's font server takes to answer, and longer still on the day it is down.
Vendor documentation Web Vitals optimize-lcp, in part
Developer The build proves it About Perf
perf.no-render-blockingComment on this rule -
Optimiser output is regenerated and state classes safelisted every JS-toggled class in prod CSS, critical CSS rebuilt required
Safelist every class that only JavaScript ever adds, before the CSS optimiser runs, or the state it styles ships with no rules behind it. Rebuild the critical CSS in the same step as the bundle so it can never describe an older layout. Both faults look fine locally, because the optimiser only runs in the production build.
Without it, the menu opens perfectly on staging and unstyled on the live site, once Remove Unused CSS strips the class only JavaScript adds.
Client document Website Design and Development Checklist section 10, item 7not public
Developer The build proves it About Perf
perf.optimiser-output-freshComment on this rule -
Each template has a transfer and DOM budget under 2 MB without video, 1400 elements or measured required
Give each template a budget and check it before launch: under 2 MB of transfer on first load, video excluded, and under 1400 DOM elements. Video keeps its own rule because one background clip swallows the whole budget. A template whose length is set by how much content it holds may pass the element half by measurement instead, on the largest page it renders.
Without it, a template renders 2774 DOM elements against a 1400 budget, and every style recalc and scroll handler pays for all of them.
Client document Technical SEO control grid rows 134, 139, in partnot public
Developer The build proves it About Perf
perf.page-weightComment on this rule -
Mobile performance score at or above 90 required
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.
Without it, the client runs PageSpeed themselves a week after launch, gets a red number, and there is no recorded median to answer it with.
Vendor documentation Lighthouse performance scoring performance/performance-scoring, in part
Developer The build proves it Why this rule exists About Perf
perf.score-floorComment on this rule -
Every deliberate split point is asserted in the build paired chunk-graph assertions, no module in two chunks required
Where a heavy dependency is kept off a critical path by a dynamic import, assert the shape of the built graph rather than trusting it. A bundler exits 0 on a specifier it cannot resolve, so a broken split builds green and smaller. Assert in pairs: negative, that the critical entry cannot reach it, and positive, that the chunk meant to load it still can. Assert also that no module lands twice.
Without it, a mistyped dynamic import makes the build smaller and greener while the feature it was meant to load is quietly dead in production.
House rule Delivered reference builds, audited together bin/assert-chunks.mjs, one build of the twonot public
Developer The build proves it About Perf
perf.split-points-assertedComment on this rule -
Server response starts inside 800 ms TTFB 800 ms or less, median of 3, mobile required
Get the server answering before optimising anything the browser does. Cache the HTML, keep query work out of the request, and check the host is not adding a redirect hop before the document. A 900 ms first byte makes a 2.5 s LCP arithmetically hard, and no amount of image work buys it back.
Without it, a second passes before the browser holds a single byte, and a week of image work cannot buy back what the server already spent.
Vendor documentation Web Vitals ttfb, in part
Developer The build proves it About Perf
perf.ttfbComment on this rule -
Third-party scripts are counted and justified under 250ms combined main-thread time recommended
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.
Without it, a Meta pixel nobody on the team added eats most of the main-thread budget, and no one can say who owns it or whether it may go.
Vendor documentation Web Vitals third-party-javascript, in part
Developer The build proves it About Perf
perf.third-partyComment on this rule -
JavaScript and CSS stay inside their budgets a gzip budget per chunk, failing the build blocker
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.
Without it, a bundle doubles across a year of small commits, each too small to notice, and no single one is blameable once the site is slow.
Ships in
wp-starter/bundle-size-test- import it rather than rebuilding it.House rule PERFORMANCE.md CONVENTIONS.md:133-134, in partnot public
Developer The build proves it About Perf
perf.bundle-budgetsComment on this rule
11Responsive and devices 4 items
-
The page survives 200 percent zoom and a 320px viewport no loss at 200%, no h-scroll at 320px, body text 16 px+ required
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. Body text computes to 16 pixels or more at every width; 14 px copy is the first thing zoom gets used for.
Without it, body overflow-x hidden suppresses the measurable instead of passing it, and the clipped content is unreachable, not just off-screen.
Standard Web Content Accessibility Guidelines (WCAG) 2.1 1.4.10; 1.4.4 (AA), in part
Developer The build proves it About A11y
a11y.zoom-and-reflowComment on this rule -
The layout survives the phone's real viewport nothing clipped by the toolbar or notch, landscape usable required
Check a real phone in both orientations, not a narrow desktop window. Full-height sections want dvh or svh rather than vh, so the browser toolbar cannot cut one off, and edge content wants safe-area inset padding so nothing hides under the notch. In landscape a sticky header should not take more than 15 percent of the height. wp-starter has dvh and svh but no safe-area handling.
Without it, the phone's home indicator sits on top of the fixed CTA bar, and the visitor cannot tap the one button that matters.
Client document Website Design and Development Checklist section 11, item 5, in partnot public
Developer The build proves it About Practices
practices.mobile-viewport-chromeComment on this rule -
Anything animated or video-driven is checked on a real device a real iPhone and a real mid-range Android required
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.
Without it, the hero video plays in every simulator and stays black on the client's iPhone, where iOS refuses play() outside a real gesture.
House rule Delivered reference builds, audited together a real-device debugging procedure in .claude/, both buildsnot public
Developer The build proves it About Practices
practices.real-device-checkComment on this rule -
The layout holds at every width, not just the design widths 320 to 2560, no h-scroll, no overlap required
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.
Without it, a 900px laptop sits between two drawn widths, and nobody opens one before launch, so that is where the heading lands on the image.
House rule Delivered reference builds, audited together one breakpoint map plus body overflow-x hidden, both buildsnot public
Developer The build proves it About Practices
practices.responsive-breakpointsComment on this rule
12Email, CRM and analytics 7 items
-
Every form field lands in the right CRM field a dated signed field map, plus one test record read back blocker
Only when the project has a form that collects data from a visitor, and submissions go to a CRM or another third-party system.
Agree a field map before launch - each form field, the CRM field it lands in, its type, whether it is required - and have it signed and dated. wp-starter ships no CRM connector, only the wps_form_submitted seam, so the integration is project code. Log every outgoing payload with a timestamp, so the send stays provable when nobody will grant CRM access.
Without it, a lead arrives in the CRM with its phone number in a notes field and its consent value missing, and nobody can prove where it went.
Client document Website Design and Development Checklist section 13, item 4, in partnot public
Operations The client proves it About Email, CRM and analytics
comms.crm-field-mapComment on this rule -
Automated mail arrives with SPF, DKIM and DMARC passing all three pass on one dated test send blocker
Only when the site sends email of its own - notifications, autoresponders, transactional.
Publish SPF and DKIM for the address the site sends from, then DMARC at p=none once both pass. Gmail and Yahoo require SPF or DKIM of every sender, and all three only of bulk senders at 5000 messages a day - a contact form is not bulk, so DMARC here is our bar, not theirs. Ask at kickoff who holds the DNS zone: if Colibrity holds it, this rule is agency-owned.
Without it, Gmail and Yahoo drop unauthenticated mail into spam or refuse it outright, so the enquiry is lost and nobody gets a bounce.
Vendor documentation Email sender guidelines Requirements for all senders, in part
Operations The client proves it Why this rule exists About Email, CRM and analytics
comms.email-authenticationComment on this rule -
A submission is stored before anything is emailed with mail broken on purpose, the record still lands blocker
Only when the project has a form that collects data from a visitor.
Write the submission to the database first, then send the notification and push to the CRM. A mail or CRM failure becomes a flag on a stored record, never a failed submission and never a lost lead. wp-starter drives the store and the mail from one field map but does not guarantee the order, so check it. The consent value travels with the record to the CRM.
Without it, one SMTP failure takes the whole enquiry with it, leaving no record, no consent proof and nothing to replay once mail works again.
House rule Delivered reference builds, audited together inc/rest-contact.php stores before mailing, both buildsnot public
Developer The build proves it Why this rule exists About Email, CRM and analytics
comms.submission-deliveryComment on this rule -
A broken form or CRM connection raises an alert a failed send alerts a named human inside one day required
Only when the site sends email of its own - notifications, autoresponders, transactional.
Route every bounced notification and every CRM error to somewhere a person looks - a named inbox, or a channel with an owner. Never a silent retry, never a log file only. Name that person in the handover with the date they take it on. If Colibrity will not hold the inbox after handover, record it as the client's from that date.
Without it, a broken credential eats enquiries into a log file nobody reads, and the first to notice is the client asking why nobody called back.
Client document Website Design and Development Checklist section 13, item 5, in partnot public
Operations The agency proves it About Email, CRM and analytics
comms.delivery-alertingComment on this rule -
Transactional emails render in the clients people use Gmail, Outlook and iPhone Mail, plus a text part required
Only when the site sends email of its own - notifications, autoresponders, transactional.
Send every automated email to a real Gmail, Outlook and iPhone Mail account and read it in each one. Keep the HTML under 102 KB, because Gmail clips past that and hides the end behind a link. Always send a plain-text part alongside. wp-starter has no templating layer - bodies are concatenated strings - so anything richer is project code you own and test.
Without it, an Outlook reader gets a broken stack of tables and a Gmail reader gets the end of the message hidden behind a clipping link.
Client document Website Design and Development Checklist section 13, item 2, in partnot public
Developer The build proves it About Email, CRM and analytics
comms.email-renderingComment on this rule -
Automated email carries a correct From, Reply-To and language From on the site's domain, Reply-To a real inbox required
Only when the site sends email of its own - notifications, autoresponders, transactional.
Send From an address on the site's own domain. Never set From to the visitor's address - that breaks SPF and DMARC and is the commonest reason a form mail is rejected. Put the visitor in Reply-To on the notification, and a monitored inbox in Reply-To on the autoresponder. Send each locale's mail in that locale's language, subject line included.
Without it, an English visitor gets a French autoresponder, and a From on a domain the site does not own fails SPF before anyone reads it.
Client document Website Design and Development Checklist section 13, item 3, in partnot public
Developer The build proves it About Email, CRM and analytics
comms.sender-identityComment on this rule -
The events that count are agreed, fired once, and observable an agreed list, each firing exactly once per action required
Only when the project loads analytics, advertising or marketing tags.
Agree with Colibrity and the client which actions count as a conversion, and write the list down with the event name each one sends. wp-starter maps seven theme events to GA4 and Meta and dedupes the server copy by a shared id, so a pair counts once. Its browser twin is queued in a request that renders no page, so dispatch it client-side or the visitor never sends it.
Without it, analytics shows traffic and never a single lead, because a fetch-submitted form fires no page load for a tag to hang on.
Ships in
wp-starter/conversion-events- import it rather than rebuilding it.Client document Website Design and Development Checklist section 14, item 2, in partnot public
Developer The agency proves it About Email, CRM and analytics
comms.conversion-eventsComment on this rule
13Pre-launch QA 17 items
-
No automated accessibility violations axe, zero violations at WCAG 2.1 A and AA blocker
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.
Without it, the cheap third of WCAG failures ships anyway, and a Lighthouse score of 98 gets read as a pass it never was.
Standard EN 301 549 - Accessibility requirements for ICT products and services 9.6, in part
Developer The build proves it Why this rule exists About A11y
a11y.axe-cleanComment on this rule -
Every published URL answers at its own address 0 non-200 across published URLs and nav links blocker
Ask the CMS for every published URL, add every navigation link, and request the lot against the built site. A page can be published, sitemapped, linked from the main menu and still answer 404, because the template that would render it does not exist and the hierarchy fell through to a catch-all that returns one. Nothing else reports this: it is not a broken link, it is a broken page.
Without it, a menu item the client clicks on launch day lands on a 404, and no link checker reports it because the link itself is fine.
House rule Delivered reference builds, audited together a nav item published, sitemapped and 404 on a live build, 2026-08-25not public
Developer The build proves it About Crawl and indexation
crawl.published-urls-resolveComment on this rule -
Canonical, robots, sitemap and meta tell one story 0 URLs both noindex and Disallowed, 0 conflicts in 10 blocker
A Disallowed URL is never fetched, so its noindex is never read and the page stays in the index. To remove a page: serve noindex, wait for it to drop out of the Coverage report, and only then Disallow it. Never both at once. On any one URL the canonical, the meta robots, the X-Robots-Tag and sitemap membership have to point the same way.
Without it, the page you meant to drop stays indexed for good: robots.txt stops the fetch, so the noindex that would have removed it is never read.
Automated check:
seo-status:robots,seo-status:home-indexClient document Website Design and Development Checklist section 7, item 13not public
Developer The build proves it About Crawl and indexation
crawl.signals-agreeComment on this rule -
No placeholder content survives to launch 0 lorem, TODO or demo-asset matches across the sitemap blocker
Sweep every URL in the sitemap for lorem ipsum, TODO, FIXME, placeholder image names and the starter theme's demo assets, and clear what you find. That sweep is the build's half of this item. Proofreading the real French copy is Colibrity's, and it is agreed as their sign-off rather than quietly implied here.
Without it, the client finds our lorem in the hero on launch day, and Google has already indexed the page carrying it.
Client document Website Design and Development Checklist section 15, item 1, in partnot public
Developer The build proves it About Launch and handover
delivery.real-contentComment on this rule -
One page and one form are driven with a screen reader VoiceOver or NVDA, one content page plus one form required
Open one content page and one page with a form in VoiceOver on macOS or NVDA on Windows, and drive both without looking. Move by heading, by landmark and by link, then submit the form with an error in it. This is the pass that finds what a scanner cannot: an announcement order that makes no sense, a control named after a file, an error message nobody hears.
Without it, nobody has driven the site by ear even once, so an announcement order that makes no sense ships with every automated check green.
Client document Website Design and Development Checklist section 12, item 9, in partnot public
Developer The build proves it About A11y
a11y.screen-reader-passComment on this rule -
Every indexable page is reachable by a static HTML link 0 orphans, 3 clicks max, crawled with JavaScript off required
Crawl from the home page with JavaScript off and diff the result against the sitemap. Every indexable URL has to be reachable in three clicks or fewer from a real anchor in the served HTML. A menu, listing or map that only renders after JavaScript is not a crawl path - one built that way hid 102 pages for a month - and a sitemap entry is a discovery hint, not a path.
Without it, whole page sets stay orphaned behind a JavaScript map - live in the CMS, listed in the sitemap, reachable by no anchor a crawler can follow.
Client document Technical SEO control grid row 7not public
Developer The build proves it About Crawl and indexation
crawl.internal-reachabilityComment on this rule -
A renamed slug leaves a redirect from its old URL every renamed URL: one 301 to a 200 required
WordPress only self-heals a renamed post or custom post type, through the _wp_old_slug meta. Pages and every other hierarchical type get nothing, and neither does a changed parent, a renamed taxonomy term or a changed permalink structure. Save a dated copy of the sitemap before any restructure, then add a 301 for every URL that moved to the committed redirect file.
Without it, a renamed page 404s where a renamed post would have self-healed, and every external link and ranking pointing at the old URL dies with it.
Client document Website Design and Development Checklist section 8, item 24, in partnot public
Developer The build proves it About Crawl and indexation
crawl.slug-change-redirectComment on this rule -
Every template is opened in all three browser engines Blink, Gecko and desktop WebKit - three passes required
Open every template in Chrome, Firefox and desktop Safari. That is three engines, not five browsers: Edge is Blink, so testing it as well buys almost nothing. iOS Safari is a separate pass and belongs to the real-device check, because iOS ships its WebKit with the operating system.
Without it, a Firefox-only break ships because every check ran in Chromium, and the client's own team is the one that finds it.
Client document Website Design and Development Checklist section 15, item 4, in partnot public
Developer The build proves it About Launch and handover
delivery.browser-matrixComment on this rule -
Every template produces valid HTML W3C validator, zero errors, every template required
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.
Without it, browsers each guess a different repair for broken markup, so the bug reproduces on the client's machine and not on yours.
Standard HTML Living Standard 2.1.8 Conformance classes, in part
Developer The build proves it About HTML
html.valid-markupComment on this rule -
Scheduled tasks actually fire on the production host no scheduled event overdue past its interval required
WP-Cron only runs when someone visits, so on a quiet site scheduled publishing, cleanup, image conversion and any backup job stop silently. Add a real cron entry on the host and set the matching constant. Then check for overdue events rather than for the constant: code that quietly does the work inline when cron is off hides the fault instead of reporting it.
Without it, a scheduled post on a quiet site publishes whenever the next visitor happens to arrive, or not at all that day.
Vendor documentation WordPress Developer Resources plugins/cron/hooking-wp-cron-into-the-system-task-scheduler, in part
Operations The build proves it About Operations
ops.cron-runsComment on this rule -
A destructive deploy refuses to run against the wrong target the resolved target is checked, not just typed required
Anything that can delete on a remote - an rsync with --delete, a database import - resolves its target, compares it to a pinned value and refuses when they disagree. A typed confirmation is not that check: it confirms intent, never destination. The destination comes from an SSH alias in a file the script cannot see, so the one thing nobody verifies decides where the delete lands.
Without it, an rsync --delete aimed at the wrong alias empties a live site's files, and the typed confirmation was answered correctly.
House rule Delivered reference builds, audited together PROD_EXPECTED_URL_HOST, one build of the twonot public
Operations The build proves it About Operations
ops.deploy-target-pinnedComment on this rule -
The site has been restored and rolled back, once, on purpose restore and rollback both run, dated, output kept required
Before go-live, restore the newest off-host backup into a non-production target and bring the site up on it, then take one deploy back to its previous release. Save the terminal output of both runs with their dates. Nothing in the theme does this for you, and nothing stops the line being ticked without the work, so the evidence is the output and not the claim.
Without it, the first restore anybody attempts is during a real outage, and that is when the rollback turns out not to exist.
Vendor documentation Hardening WordPress security/brute-force#hardening, in part
Operations The build proves it Why this rule exists About Operations
ops.recovery-drillComment on this rule -
Production loads with a clean console no error, no rejection, no failed request required
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.
Without it, the asset 404 that broke the client's tracking sits unread for months underneath the debug logging.
House rule CONVENTIONS.md PERFORMANCE.md:267-276, in partnot public
Developer The build proves it About Practices
practices.console-cleanComment on this rule -
The theme's contracts are tests, and a deploy runs them first a suite in the repo, run by the deploy, red means no upload required
Pin every mechanism the handover relies on with a test in the repository: budgets, split points, environment gates, comments off, noindex on the developer pages. Run the suite from the deploy script, after the build and before the upload, and stop on red. A hook that only formats code is not a gate, and a test that skips when the build is missing is not a pass: build first, then test.
Without it, a green suite that no deploy ever runs ships the broken split point it exists to catch.
House rule Delivered reference builds, audited together a suite in the third build and the starter, run by no deploy script in any build; nothing in the two older buildsnot public
Developer The build proves it About Practices
practices.tests-before-deployComment on this rule -
No response leaks a PHP error, a stack trace or a server path 500 status, no path, trace or error text required
Turn error display off on staging and production and log to a file instead. Add an error drop-in that renders a plain 500 page saying nothing about the failure. A fatal that prints its trace hands over the absolute path of the install, the plugin that broke, the function that called it and often a query - a map for whoever asked for it.
Without it, the first fatal on the client's site prints a page of PHP at a visitor and a map of the install at whoever asked for it.
Standard OWASP Application Security Verification Standard 5.0.0 16.5.1 (L2), in part
Developer The build proves it About Security and hardening
security.no-error-disclosureComment on this rule -
No internal link is broken no 404, no 5xx, no chain over one hop required
Crawl internal links before handover. Broken links usually come from content entry and trailing-slash mismatches, not from templates, so this has to run against real content.
Without it, a published page sits twice in the main menu and 404s from every template on the site, reaching the sitemap before anyone notices.
Automated check:
seo-status:link-ok,seo-status:linkVendor documentation Google Search Central documentation crawling/docs/troubleshooting/http-status-codes, in part
Developer The build proves it About SEO
seo.internal-links-resolveComment on this rule -
Comments are closed at every layer unless the brief asks for them closed on every existing and new item, no form rendered required
WordPress ships comments on. Unless the brief asks for them, close them at every layer: remove the post type support so nothing new offers them, make the open check answer closed for every row already stored open, and take the Comments screen out of the admin. Removing the menu alone leaves every existing post open and the comment form rendering to the crawler and the spammer.
Without it, the first spam comment lands a month after launch on a feature nobody built and nobody moderates.
Ships in
wp-starter/comments-off- import it rather than rebuilding it.House rule Delivered reference builds, audited together closed in all three builds; in code only in the third, by configuration in the othersnot public
Developer The build proves it About Security and hardening
security.comments-closedComment on this rule
14Launch and handover 17 items
-
Only the production host serves an indexable copy of the site every other host gated or noindex blocker
Enumerate every hostname that resolves to this project - admin, dev, staging, preview, ETL, the agency's own subdomain, and whatever the platform hands out by default - and make every one of them that is not production either refuse the request or answer noindex on every response. Ask the client for the list rather than assuming the brief is complete, and write the list down.
Without it, Google can pick the staging copy as the canonical one, and the production URLs quietly stop being the ones that rank.
Client document Technical SEO control grid row 6not public
Developer The build proves it Why this rule exists About Crawl and indexation
crawl.sibling-hostsComment on this rule -
Every inventoried URL reaches its replacement in one hop 200 or a single 301, never bulk to home blocker
Only when the project replaces an existing live site.
Map every URL on the inventory to its closest equivalent with a 301. No equivalent means the nearest parent, and genuinely gone means 410. Never redirect in bulk to the home page. Build the map as a file that ships with the project rather than as rules somebody typed into a control panel.
Without it, every old deep link lands on the home page, Google reads the lot as soft 404s, and the rankings the old site had are gone.
Client document Website Design and Development Checklist section 8, item 24, in partnot public
Developer The build proves it About Migration
migration.redirect-mapComment on this rule -
HTTPS is enforced and nothing loads over http one 301 to https, no http:// asset blocker
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.
Without it, a migrated page pulls one script over http, the browser blocks it outright, and that block only shows up on the client's live domain.
Automated check:
seo-status:https-redirect,seo-status:mixed-contentVendor documentation web.dev developer guides articles/what-is-mixed-content, in part
Developer The build proves it About SEO
seo.https-and-mixed-contentComment on this rule -
Production is indexable and staging is not driven by environment, never a checkbox blocker
Drive the noindex decision from the environment, never from a checkbox someone has to remember. Staging returns noindex on every response; production returns none.
Without it, a database pull from staging carries blog_public=0 into production and the site goes dark after a deploy that reported success.
Automated check:
seo-status:home-index,seo-status:robotsVendor documentation Google Search Central documentation crawling-indexing/block-indexing, in part
Developer The build proves it Why this rule exists About SEO
seo.no-noindex-leakComment on this rule -
Replaced URLs redirect to their new home one 301 to a 200, never bulk to the home page blocker
Only when the project replaces an existing live site.
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.
Without it, every ranking and inbound link the old site earned dies at cutover, and by the time anyone notices the list of old URLs is gone too.
Vendor documentation Google Search Central documentation crawling-indexing/site-move-with-url-changes, in part
Developer The build proves it Why this rule exists About SEO
seo.redirects-on-launchComment on this rule -
What the client must not do is refused, not hidden a denied capability, verified by opening the URL required
Decide for each thing the client must not do whether it is clutter or a boundary, because the two are fixed by different mechanisms. Clutter is hidden. A boundary is a capability: deny it where the post type is registered and map it, so the action is refused wherever it is attempted rather than merely absent from the screen it was attempted from. CSS is not how you get one.
Without it, a hidden menu reads as a locked one, and the screen still answers at its own URL to anyone who types it.
House rule Delivered reference builds, audited together create_posts => do_not_allow with map_meta_cap, both buildsnot public
Developer The build proves it Why this rule exists About The admin the client receives
admin.capability-boundariesComment on this rule -
The client's admin is trimmed, and the trim can always be undone declared in code, reversible, reaches the real accounts required
Take developer clutter out of the admin the client uses, and declare what goes as a list in code, not as a description. A site-wide switch and per-user self-service both qualify. What does not is a trim that exempts the role the client's own people hold: that is a no-op dressed as a feature. Whoever it applies to has to be able to undo it, so the control that undoes it is never itself hidden.
Without it, the client's own people are administrators, and a trim that exempts administrators leaves the only accounts it exists to help untouched.
House rule Delivered reference builds, audited together inc/admin-cleanup.php, both buildsnot public
Developer The build proves it Why this rule exists About The admin the client receives
admin.client-modeComment on this rule -
One control writes one setting no two boxes claiming the same value required
When a plugin takes over something the theme also offers, stop rendering the theme's version while that plugin is active instead of leaving both on screen. Two boxes claiming to set the same value teach the client that the site is unpredictable, and the one they fill in will be the one you did not wire up. One settings page for the site's own options, not one per feature.
Without it, the client sets the value in the plugin, the theme's box overwrites it on the next save, and nothing says which one won.
House rule Delivered reference builds, audited together theme SEO panel hidden while Yoast is active, both buildsnot public
Developer The build proves it About The admin the client receives
admin.one-control-per-settingComment on this rule -
The client leaves handover able to run the site a dated recording, plus a credential inventory by holder required
Record a session with the people who will run the site: editing content, adding a page, what must not be touched. Then write the credential inventory - every account the site depends on, who holds it, and whether it transferred at handover or Colibrity retains it and why. It is what settles who owns the DNS zone, the analytics property, the CRM and the hosting.
Without it, the client cannot add a page without ringing us, and nobody can say who holds the DNS zone or the mail relay when it breaks.
Client document Website Design and Development Checklist section 15, item 7, in partnot public
Developer The agency proves it About Launch and handover
delivery.client-handoverComment on this rule -
Uptime monitoring and a restorable backup are live at launch checks at 5 min or better, one dated restore performed required
Put an uptime check on the home page at five minutes or better, alerting a named person. Then take a backup and actually restore it somewhere before handover - a backup nobody has restored is a hope with a filename. Where Colibrity resells the hosting this is agency-owned. If nobody will own it at handover, write that down rather than ticking it.
Without it, the site is down all weekend before anyone notices, because the client is the only monitor and nobody was named to be told.
Client document Website Design and Development Checklist section 15, item 6, in partnot public
Operations The client proves it About Launch and handover
delivery.monitoring-and-backupsComment on this rule -
What production is running is a commit you can name the live build id resolves to a named commit required
Deploy from a committed tree, and expose the commit where the live site can be read: a stamped file, a response header, or the built asset path. Never deploy from a dirty working tree. Without this a hotfix edited on the server exists nowhere in git, nobody can reproduce the running site, and a rollback restores something that was never live.
Without it, nobody can tell whether the bug they are chasing lives in the repository or in a file somebody edited on the server months ago.
Vendor documentation The Twelve-Factor App V. Build, release, run, in part
Operations The build proves it About Environments and deployment
env.deploy-provenanceComment on this rule -
The project ships with the notes needed to run it one page in the repo, not a chat thread required
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.
Without it, somebody deactivates a plugin the theme quietly depends on, because no page anywhere says which ones are load-bearing.
House rule Delivered reference builds, audited together README.md plus a .claude/ doc set, both buildsnot public
Developer The build proves it About Practices
practices.handover-notesComment on this rule -
The administrator list is named and owned before handover admin list named, build access removed required
At handover, list every administrator with its email address, remove or demote the build's own accounts, and delete anything generic. Ask the client to confirm in writing that each remaining account is theirs, and keep that reply. Old agency logins are how a site gets changed two years later by someone nobody can reach, and they show up in no automated check.
Without it, the developer's own login is still an administrator on the live site a year later, and nobody has confirmed who the rest belong to.
Vendor documentation Hardening WordPress security/brute-force#hardening, in part
Operations The client proves it About Security and hardening
security.admin-accounts-at-handoverComment on this rule -
Core, plugins, themes and PHP are current, and updates have an owner 0 pending, PHP supported, owner named required
Take the site to zero pending core, plugin and theme updates before launch, and check PHP is on a branch php.net's supported-versions table still lists as receiving security fixes. Name in the handover who applies updates for the next twelve months, or record in writing that nobody does. Drop any plugin that has gone two years without a release - an abandoned plugin is an unpatchable one.
Without it, a published plugin CVE stays open for months on a site where nobody ever agreed to be the person applying updates.
Vendor documentation Hardening WordPress security/hardening#vulnerabilities-in-wordpress, in part
Operations The build proves it About Security and hardening
security.versions-currentComment on this rule -
Performance and behaviour changes ship one at a time recommended
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.
Without it, eight changes ship in one commit, the score drops, and there is no way to tell which one did it.
House rule PERFORMANCE.md PERFORMANCE.md:89-92, in partnot public
Developer The build proves it About Practices
practices.one-change-per-deployComment on this rule -
The admin has been tested by someone who did not build it a dated sheet, a tester off the build recommended
Before the handover session, sit someone who did not build the site at the admin with a task script: add a page, add a block, change a translation, find a submission. Note each point where they needed help. Fix what can be fixed and write the rest into the handover notes, so the client's first session is not the first test.
Without it, the client is the first person to discover the block picker is a mystery, on a call, after the invoice.
Ships in
wp-starter/editor-test-kit- import it rather than rebuilding it.House rule Delivered reference builds, audited together docs/editor-test in the third build; nothing of the kind in the two othersnot public
Developer The build proves it About Launch and handover
delivery.editor-testComment on this rule