← Back to Proof

The rulebook

Every rule, and where each one is wrong.

These are the published rules behind the monthly check: 10 rulebooks and 92 rules, grouped by the department that runs them. They are public because a check you cannot argue with is a check you should not act on. Every rule carries the case where it gives the wrong answer, written down before you ask. Nothing here predicts a search ranking. Each rule states a property of the page and nothing more.

A version number that did not move while the rules did would make every past receipt a lie, so it moves in the same change as the rules. This page is generated from the same files the checks run from, so it cannot describe rules that are not the ones running.

Found

Whether searching your name finds you.

seo-onpageonpage-2026.09 · 10 rules
  1. onpage.title-missinghightitleweight 5

    The page has no title

    The title is the clickable line in a search result and the label on a browser tab. Without one, search engines invent something from the page content and the tab shows a URL.

    How to avoid it Give every route a title before it ships. A title is not optional content.

    When this rule is wrong Never wrong on a public page. It IS wrong on a fragment that was never a whole page: an email template, an embedded widget, or an iframe body. If the artifact is not a page a person can navigate to, this rule does not apply and the report should be scoped instead.

  2. onpage.title-truncatesmediumtitleweight 2

    The title is long enough that a search result will cut it off

    Google renders titles to a pixel width that works out at roughly 60 characters for typical prose. Past that the end is replaced with an ellipsis, so anything load-bearing at the end of the title is not read.

    How to avoid it Put the distinguishing words first and the brand last.

    When this rule is wrong This is a truncation fact, not a ranking claim, and it is measured in CHARACTERS while the real limit is pixels: a title of narrow characters can run longer and a title in wide caps gets cut sooner. It is also legitimately wrong when the tail is deliberately expendable, for example a brand name after a pipe that the reader does not need. Judge whether the part past 60 characters carries meaning before changing anything.

  3. onpage.meta-description-missingmediumdescriptionweight 3

    The page has no meta description

    With no description, the snippet under a search result is assembled from whatever text the engine finds, which for a business page is often a navigation menu or a cookie notice.

    How to avoid it One sentence per page saying what the page is for, written for a stranger.

    When this rule is wrong A meta description is not a ranking factor and this rule does not claim it is. It is also genuinely optional: an engine will often write a better snippet from page content than a generic hand-written description, and a description that repeats the title adds nothing. The real defect is having neither a description nor snippet-worthy opening prose.

  4. onpage.h1-missinghighstructureweight 4

    The page has no first-level heading

    The h1 is the page's own statement of what it is, and it is the first thing a screen reader user hears after the title. A page with h2s and no h1 has a table of contents with no name at the top of it.

    How to avoid it Exactly one h1 per page, and make it the sentence the page is about.

    When this rule is wrong Wrong when the visible title is an image carrying the wording, which is a real design choice on a brand page, though it then needs the wording in the image's alt text. Also wrong on a page whose h1 is rendered by client-side script AFTER this probe read the DOM, which is why `counts.headings` is reported alongside: if that is zero the page may simply not have finished rendering, and the honest answer is abstention rather than a finding.

  5. onpage.h1-multiplelowstructureweight 1

    The page has more than one first-level heading

    Several h1s means the page asserts several different things to be its main subject, so an engine and a screen reader both have to guess which one is the page.

    How to avoid it One h1, and h2 for the sections under it.

    When this rule is wrong Weak on purpose, weight 1, because the HTML5 outline algorithm was specified to permit exactly this and plenty of correct pages have a per-section h1 inside `section` elements. It is a smell rather than a defect, and on a page whose sections really are independent documents it is not even that.

  6. onpage.heading-level-skippedmediumstructureweight 2

    A heading level is skipped, so the outline has a gap

    Jumping from h2 to h4 tells assistive technology there is a missing level of structure. A screen reader user navigating by heading level hears a subsection with no section.

    How to avoid it Choose heading levels by document structure and size them with CSS.

    When this rule is wrong This reads the DOM ORDER, which on a page using CSS grid or flex ordering is not the visual order. A page can be visually correct and trip this, and the reverse. It is also wrong when a level is skipped for styling reasons and the real fix is a class rather than a tag, in which case the outline is the thing to fix and not the appearance.

  7. onpage.image-alt-missingmediummediaweight 3

    An image has no alt attribute at all

    A missing alt attribute is different from an empty one. Empty means decorative and is correct. Missing means nobody decided, so a screen reader falls back to reading the file name, and an image search has nothing to index.

    How to avoid it Every img gets an alt attribute at the moment it is written: the wording if it carries meaning, alt="" if it is decoration.

    When this rule is wrong The distinction this rule rests on is the whole point: `alt=""` is CORRECT for a decorative image and this rule deliberately does not fire on it. It fires only on a wholly absent attribute. It is still wrong on an `img` injected by a third-party embed the site does not control, such as a payment badge or a map tile, which cannot be fixed from this codebase.

  8. onpage.lang-missingmediumstructureweight 2

    The document does not declare its language

    Without a lang attribute a screen reader reads the page in whatever voice it defaults to, which mispronounces everything, and translation tools have to guess.

    How to avoid it Set lang on the html element in the layout, once.

    When this rule is wrong Effectively never a false positive on a real page. The one legitimate case is a document genuinely mixing languages with per-element lang attributes and no dominant language, which is rare and which this rule cannot distinguish, so check before acting on it.

  9. onpage.canonical-missinglowduplicationweight 1

    The page declares no canonical URL

    The same page is usually reachable at several URLs: with and without www, with tracking parameters, with and without a trailing slash. A canonical says which one is the real one.

    How to avoid it Set canonical from one place in the layout, derived from the route.

    When this rule is wrong Weight 1 because a single-page site with one URL genuinely does not need this, and a self-referential canonical adds nothing an engine cannot work out. It matters when a page is reachable by several URLs, and this rule CANNOT see whether that is true, because determining it requires requesting other URLs and nothing here makes a network request. So treat it as a question rather than a defect.

seo-technicaltechnical-2026.09 · 11 rules
  1. technical.noindex-on-pagehighindexabilityweight 10

    The page tells search engines not to index it

    A noindex directive removes the page from search results completely. Nothing on the page looks different, no error is raised, and traffic goes to zero over the following weeks, which is slow enough that it gets attributed to something else. It is the highest-weighted rule in this product because it is a nineteen-character mistake with a total effect, and it usually arrives from a staging template that was copied rather than configured.

    How to avoid it Set the directive from an explicit per-route value rather than a template default, and make the production build fail if a route that should be public carries noindex.

    When this rule is wrong Wrong whenever the exclusion is intended, and there are several honest reasons for it: a thank-you or order-confirmation page, a checkout step, a print view, a staging deployment, or a paginated duplicate. This rule reports the directive rather than a mistake, so on a page that should not be in search it is telling you the configuration is doing what you asked. It also CANNOT see robots.txt or an X-Robots-Tag header, so a clean result here is not a statement that the page is indexable.

  2. technical.robots-directives-conflicthighindexabilityweight 7

    Two robots directives contradict each other

    When one tag says index and another says noindex, the outcome is decided by rules nobody on the team is thinking about: the most restrictive directive usually wins, and a crawler-specific name overrides the generic one. The page's indexability becomes an accident rather than a decision, and it can change when a crawler changes.

    How to avoid it Emit exactly one robots meta tag from one place. Two tags almost always mean two layouts each adding their own, which is invisible until somebody views source.

    When this rule is wrong Wrong when the contradiction is deliberate targeting, for example allowing one crawler and excluding another, which is a real if unusual choice. It is also wrong to read this as a prediction of which directive wins: this rule reports that two signals disagree, and deliberately does not claim to know the outcome.

  3. technical.canonical-multiplehighcanonicalweight 8

    The page declares more than one canonical URL

    A canonical link says which URL is the real one. Two of them say nothing, and the documented behaviour is that all of them are ignored, so the page falls back to whatever a crawler decides. The effort that went into setting a canonical is spent and the signal is gone.

    How to avoid it Render the canonical from one component that owns the whole head, and assert in a test that a built page contains exactly one canonical link.

    When this rule is wrong Wrong when both tags carry the SAME href, which is harmless duplication from two layouts and worth tidying rather than fixing. This rule reports that case with both values quoted so the difference is visible, and a reader can see immediately which of the two situations they are in without opening the page.

  4. technical.canonical-relativemediumcanonicalweight 6

    The canonical URL is relative

    A canonical is resolved against the page it was found on, so a relative one cannot disambiguate the two things it exists to disambiguate: the same page reached over http and https, and the same page reached with and without a www prefix. A relative canonical resolves to whichever version was crawled, which is the situation it was added to fix.

    How to avoid it Build the canonical from a configured absolute origin rather than from the request, so it cannot vary with how the page was reached.

    When this rule is wrong A relative canonical does resolve and is not invalid markup, so on a site served from a single origin with redirects already forcing one scheme and one host, this is tidiness rather than a defect. It is a real problem where those redirects are absent, and there is nothing in the page that tells us which case this is.

  5. technical.meta-refresh-redirecthighcrawlabilityweight 7

    The page redirects with a meta refresh

    A meta refresh is a redirect performed by the document rather than by the server. It is slower, it is a documented accessibility failure because it moves people who are still reading, and it passes its signals to the destination less reliably than an HTTP redirect. It usually exists because whoever needed the redirect did not have access to the server configuration.

    How to avoid it Move the redirect to the server or the edge as a 301 or 308. If that is not available, treat it as a blocker rather than a workaround.

    When this rule is wrong Wrong when the refresh is a deliberate timed reload rather than a redirect, for example a dashboard or a live scoreboard that refreshes itself with no url in the content attribute. This rule fires only when the content names a destination, so a self-refresh is not reported.

  6. technical.insecure-subresourcehightransportweight 8

    The page loads a subresource over http

    A browser on an https page blocks an http script or stylesheet outright and warns about an http image. The consequence is not a warning in a console, it is a feature that silently does not work for every visitor while working perfectly for whoever tests the page over http locally.

    How to avoid it Reference subresources with a scheme-less path or an https URL, and add a content security policy that blocks the rest, so a new one fails in development rather than in production.

    When this rule is wrong Wrong on a page that is genuinely served over http, where the subresource matches the page and nothing is blocked. It is also wrong on a preconnect or dns-prefetch hint, which loads nothing, and those are excluded. On any site with a certificate this rule is reporting a resource that does not load for anybody.

  7. technical.hreflang-malformedmediuminternationalisationweight 6

    An hreflang value is not a well-formed language tag

    A malformed hreflang value is ignored in full, so the page it points at is not associated with the language it was meant to serve. The most common form is an underscore instead of a hyphen, which is what a locale looks like in most programming languages and is not what a language tag looks like.

    How to avoid it Generate hreflang values from the same locale identifiers the application already uses, converting underscores to hyphens in one place, and keep x-default spelled exactly.

    When this rule is wrong This rule checks the SHAPE of the tag, not whether the language exists, and that is deliberate: vendoring a list of valid subtags from memory would report a correctly marked-up page in a language we happened to omit. So it is wrong in one direction only, by staying quiet on a well-shaped tag that names no real language, such as zz-ZZ.

  8. technical.hreflang-duplicatemediuminternationalisationweight 6

    The same hreflang value points at two different URLs

    One language tag can only name one page. Declared twice with different targets, the set is contradictory and the usual outcome is that the whole cluster is discarded, which loses the association for every language on the page rather than only the duplicated one.

    How to avoid it Build the hreflang set from one map of locale to URL, so a duplicate key is impossible rather than merely unlikely.

    When this rule is wrong Wrong when the two URLs are the same page reached differently, for example one with a trailing slash and one without, which is a redirect problem wearing this rule's clothes. Both values are quoted in the finding so that case is visible immediately rather than requiring somebody to open the page.

  9. technical.charset-missing-or-latemediumencodingweight 5

    The character encoding is undeclared or declared too late

    A browser has to decide how to decode the bytes before it can read them, so the declaration has to arrive in the first 1024 bytes. Without it the browser guesses, and the guess is usually right for English and usually wrong for a name with an accent in it, which is how a page ends up displaying a business owner's own name incorrectly.

    How to avoid it Put the charset meta tag first inside head, before the title and before any other tag. It is the one element whose position is load-bearing.

    When this rule is wrong Wrong when the encoding is declared in a Content-Type response header, which this probe cannot see because it reads a document rather than a response. A page with a correct header and no meta tag is fine, and this rule will still report it. The byte offset it quotes is also approximate: it counts head markup only.

seo-structured-datastructured-data-2026.09 · 9 rules
  1. sd.json-unparseablehighsyntaxweight 9

    A structured data block is not valid JSON

    A JSON-LD block that does not parse is discarded in full and in silence. Nothing warns you, the page looks finished, and every property in that block is doing nothing. This is the highest-weighted rule in the rulebook because the failure is total and invisible.

    How to avoid it Serialise structured data with a JSON encoder rather than a string template. A trailing comma, an unescaped quote in a business name, or an interpolated value that arrived empty all produce this.

    When this rule is wrong Never wrong about the parse itself: the block either parsed or it did not, and the parser's own message is quoted. It IS wrong about the cause when a template engine is expected to substitute a value at request time and the file on disk was checked instead of the served page. Check the response a browser receives before editing a template.

  2. sd.context-missinghighsyntaxweight 7

    A structured data block does not declare the schema.org context

    Without @context pointing at schema.org, the vocabulary is undefined and a consumer has no basis for reading @type or any property. The block parses as JSON and means nothing, which is why it survives review: it looks like data.

    How to avoid it Put a single string @context of https://schema.org at the top of every block. If a block is generated by concatenating fragments, the context belongs on the wrapper.

    When this rule is wrong Wrong when @context is an object or an array that maps prefixes rather than a plain string, which is valid JSON-LD and is normal in output from a CMS that mixes vocabularies. This rule reports that case separately as an object context rather than as a missing one, so a finding quoting an object is telling you it could not read it, not that it is absent.

  3. sd.entity-has-no-typehighsyntaxweight 6

    A structured data entity has no @type

    An entity with properties and no @type is a bag of values nobody can interpret. The properties are read against no definition and the whole entity is ignored, so the effort that went into filling it in is spent and invisible.

    How to avoid it Every top-level entity and every member of @graph carries a @type. If a fragment is built in one place and typed in another, type it where it is built.

    When this rule is wrong Wrong on a node that is deliberately a plain value bag, for example a bare object under a property whose expected range is a literal. This rule only looks at TOP-LEVEL entities and direct members of @graph for that reason, so a nested value bag is never reported. If a reported node is intentionally untyped at the top level, it is not doing anything.

  4. sd.type-wrong-casehighvocabularyweight 8

    A @type is a known schema.org type spelled with the wrong case

    schema.org types are case-sensitive. localbusiness is not LocalBusiness, it resolves to nothing, and the entity is dropped. It is a single-character defect with the same effect as deleting the block, and it survives review because it reads correctly to a person.

    How to avoid it Copy type names from the schema.org page for the type rather than typing them. Lowercasing usually arrives from a config value or a database column that normalises case.

    When this rule is wrong This rule only fires on a POSITIVE match: the value matches a type in our vendored list case-insensitively and differs from it exactly. It cannot be wrong about that comparison. The reason there is no companion rule reporting an UNKNOWN type is that our type list is a subset of schema.org, so absence from it would be evidence of nothing.

  5. sd.required-property-missingmediumcompletenessweight 6

    A typed entity is missing a property its type requires

    A required property is the difference between markup that is read and markup that is discarded. An Offer without a price, or a LocalBusiness without an address, is the shape that gets built once from a tutorial and never revisited, because nothing on the page looks broken.

    How to avoid it Check the type against the schema.org page for it when you add markup, and treat a required property that arrives empty from a database as a reason not to emit the entity at all.

    When this rule is wrong Wrong on a REFERENCE node, and that is the most likely false positive in this rulebook: {"@type":"Organization","@id":"/#org"} is a pointer at an entity defined on another page and correctly carries nothing else. Such nodes are excluded. It is also silent by design on any type absent from our required-property table, which is small, so a clean result here does not mean every type on the page was checked.

  6. sd.placeholder-valuehighcontentweight 8

    Structured data carries a template placeholder

    A placeholder in structured data is worse than one in visible copy, because nobody reads structured data on the way past. Your Company Name or example.com sits in the machine readable description of the business until somebody looks, and it is exactly what a search engine reads first.

    How to avoid it Fail the build when structured data contains a value from the placeholder set. A template default that is a valid-looking string is a template default that ships.

    When this rule is wrong Wrong when the placeholder-looking value is the real one: a company genuinely called Example or a product line named Test Kit. That is why short generic words are matched only as the WHOLE value and distinctive strings like example.com are matched anywhere. If a reported value is real, it is real, and the rule was still right to ask.

  7. sd.aggregate-rating-not-on-pagehighcontentweight 8

    A rating in the markup does not appear anywhere on the page

    Structured data is required to describe what the page shows. A rating that exists only in the markup is invisible to a visitor and, if a search engine shows it, the visitor arrives at a page that does not support it. This is the rule with the largest gap between how small the defect looks and how much it costs, because the consequence is a manual action rather than a ranking change.

    How to avoid it Render the rating from the same value that generates the markup, so the two cannot diverge. A rating hardcoded in a template while the page renders a live average is the usual cause.

    When this rule is wrong Wrong when the rating is rendered as an image, a canvas, or a chart with no text and no accessible label, since this rule reads visible text AND aria-label, title, alt and content attributes. It is also wrong when the rating loads after an interaction this probe did not perform. If the number is genuinely on the page in text a person can select, this rule does not fire.

  8. sd.self-serving-reviewmediumpolicyweight 5

    A business marks up reviews of itself

    Review markup attached to the organisation publishing the page is self-serving, and the review snippet guidelines exclude it. The markup is not merely ignored: it is the pattern that a manual review looks for, so it puts the rest of the site's markup under scrutiny to buy nothing.

    How to avoid it Attach reviews to a Product, a Service, or a specific offering rather than to the Organization or LocalBusiness node, and keep the aggregate on the thing being reviewed.

    When this rule is wrong Wrong when the marked-up organisation is not the publisher of the page, which is normal on a directory, a marketplace listing, or a review site describing somebody else's business. Nothing in the page tells us who owns it, so this rule cannot distinguish the two and it reports the shape rather than a violation. On a directory it is expected and correct.

  9. sd.relative-urlmediumsyntaxweight 5

    A URL in the markup is relative

    Structured data is consumed away from the page it came from, so a relative URL has nothing to resolve against. An image at /logo.png or a url of /about is dropped, which usually means the logo or the canonical link the markup exists to provide is missing.

    How to avoid it Build absolute URLs from a single configured origin when generating markup. A relative URL here almost always arrives from reusing the same helper that renders an href.

    When this rule is wrong Wrong on a protocol-relative URL beginning with two slashes and on a data URI, both of which resolve, and both are excluded. It is right about a leading single slash or a bare path even when the page renders correctly, because the browser resolves those and a structured data consumer does not.

measurementmeasurement-2026.09 · 7 rules
  1. measurement.nothing-is-countinghighdenominatorweight 9

    No analytics of any kind is installed, so no report about this site can have a denominator

    Every monthly report a client pays for rests on a number somebody recorded. With no tag on the page, there is nothing recording, and any figure quoted later came from somewhere other than this site. This is the cheapest possible finding and the most expensive one to discover six months into a retainer.

    How to avoid it Install one analytics tool and confirm it reports, or write down where the numbers actually come from so the monthly report can cite a source instead of implying one.

    When this rule is wrong Wrong when measurement is server-side, which is increasingly common and completely legitimate: log-based analytics, a reverse proxy, Cloudflare Web Analytics injected at the edge, or a tag injected by a platform after this file was built. None of those appear in the bytes here. It is also wrong for a page deliberately excluded from measurement, such as a thank-you page kept out of the funnel. Read it as: nothing in THIS FILE counts anything, so confirm what does.

  2. measurement.placeholder-idhighdenominatorweight 9

    A tag is installed with a placeholder id, so it is reporting into nothing

    A tag carrying G-XXXXXXX or YOUR_TRACKING_ID is the template's example value. It loads, it runs, it looks installed in every screenshot, and it sends to no property. This is worse than having no analytics, because everybody believes the numbers are being collected.

    How to avoid it Put the real measurement id in, then load the deployed page and confirm a hit arrives in the property. An id you have not seen receive a hit is not installed.

    When this rule is wrong Wrong when the real id is injected at runtime from an environment variable and the placeholder is the fallback in the source. That is a real pattern, and it is also exactly how a site ships to production with the fallback live, so the finding stands and asks you to confirm which one the deployed page carries.

  3. measurement.two-tools-counting-the-same-thingmediumagreementweight 5

    Two general-purpose analytics tools are installed, and they will not agree

    Two tools counting the same visits produce two different numbers, because they differ on bot filtering, session length, consent handling and what a pageview is. Nobody reconciles them; people quote whichever is higher. A report that cites one without saying which is a report whose denominator moves.

    How to avoid it Name one tool as the source of truth for reported numbers, and say so wherever those numbers are published. Keep the second if it earns its place.

    When this rule is wrong Deliberately wrong during a migration, which is a normal and correct reason to run two for a few weeks. It is also wrong when one is scoped to a subdomain or a single funnel. The fix is usually to name which one is canonical rather than to remove either.

  4. measurement.csp-blocks-its-own-taghighdeliveryweight 8

    A Content-Security-Policy on this page cannot permit the analytics host it loads

    A script-src directive that does not list the analytics host stops the browser fetching it. The tag is in the HTML, the site looks instrumented to anybody reading the source, and the browser refuses it silently to everyone except whoever opens the console.

    How to avoid it Add the analytics host to script-src and connect-src, then load the page and confirm the request is made rather than blocked.

    When this rule is wrong Only the META policy is read here. A CSP sent as an HTTP header is the more common case and is invisible to a parser, so a page with a header policy shows nothing and a page with both may be governed by the header instead. This rule therefore finds a real subset and never claims the absence of one means the CSP is fine.

  5. measurement.consent-ui-with-unconditional-taghighconsentweight 7

    A cookie banner is on the page and the analytics tag loads regardless

    A banner asking permission next to a tag that never waited for it is the shape regulators and customers both treat as the bad case: the site collected first and asked after. It is also a measurement problem, because consent-mode numbers and unconditional numbers are different populations and mixing them makes a trend meaningless.

    How to avoid it Gate the tag behind the consent decision, or drop the banner if the tool genuinely sets no cookies and you can say so. A banner that changes nothing is worse than no banner.

    When this rule is wrong Wrong when the consent tool blocks tags at the network layer rather than by marking them, which several CMPs do, and wrong when the tag is a cookieless analytics product that legitimately needs no consent. The check looks for a consent-related attribute or a blocking script type on the tag itself, so a correctly wired CMP that marks its tags does not fire.

  6. measurement.policy-and-page-disagreemediumconsentweight 6

    This policy page names tools it does not load, or loads tools it does not name

    A privacy or cookie policy is a published claim about what the site collects, and this house's standing rule is that a published claim needs something true behind it. Both directions are wrong in the same way: naming a tool you removed overstates collection, and loading one you never named understates it.

    How to avoid it Reconcile the list in the policy with the tags that actually ship, in both directions, and date the policy so the next reconciliation has a starting point.

    When this rule is wrong Scoped to a page that calls itself a privacy or cookie policy, and compared only against the tools loaded on THAT page. A site that loads its pixel only on the checkout will look like an unnamed absence here, which is why the finding says what it compared. It also cannot see tools a tag manager loads at runtime, and a policy that names them is correct even though nothing on the page matches.

  7. measurement.no-search-console-verificationlowdenominatorweight 3

    No search-console verification token, so nobody can see what this site ranks for

    Analytics says what happened after somebody arrived. Search Console is the only place the queries, impressions and positions live, and it is free. Without verification, every claim about search performance in a monthly report is unsourced.

    How to avoid it Verify the property once, by whichever method you prefer, and record which method was used so the next person does not re-verify it.

    When this rule is wrong Frequently wrong, and low weight for that reason: verification by DNS record or by an uploaded HTML file is at least as common as the meta tag, and neither is visible in this file. Treat it as a prompt to confirm verification exists somewhere, not as evidence that it does not.

Craft

The site itself: the phone version, access, and anything quietly broken.

accessibilitya11y-2026.09 · 13 rules
  1. a11y.control-unlabelledhighlabelsweight 8

    A form control has no label of any kind

    A screen reader announces a control by its accessible name. With no label element, no aria-label and no aria-labelledby, it announces the control type and nothing else, so the user hears 'edit text' and has to guess what to type.

    How to avoid it Give every control a label element tied to it by for and id, or wrap the control in the label.

    When this rule is wrong The accessible name is computed here in the real order, so a control named by a wrapping label, a label[for], aria-label or aria-labelledby is not reported. What remains is genuinely unnamed. It IS wrong on a hidden control used to carry state rather than take input, for example a honeypot field or a CSRF token, which nobody is meant to fill in: those want type=hidden, and if one is a visible input for technical reasons then this rule does not apply to it.

  2. a11y.placeholder-as-labelmediumlabelsweight 4

    A control is labelled only by its placeholder, which disappears as soon as somebody types

    A placeholder is a hint, not a name. It vanishes on the first keystroke, so anybody interrupted mid-form loses the only indication of what the field was for, and it is commonly rendered at a contrast ratio too low to read.

    How to avoid it Keep the placeholder as an example of the format and add a visible label above the field.

    When this rule is wrong Fires only where there is a placeholder AND an aria-label or aria-labelledby but no visible label element, which is the case a checker looking at accessible names alone would pass. That is a real pattern in compact search bars where a visible label is a genuine design decision and the aria-label carries the name, so treat this as a question about that trade rather than as a defect. It does not fire on a control with a real label element.

  3. a11y.interactive-no-namehighlabelsweight 8

    A button or link has no accessible name

    An icon-only button with no aria-label is announced as 'button' with no indication of what it does. Screen reader users can list every link on a page out of context, and an unnamed entry in that list is unusable.

    How to avoid it Every icon-only control gets an aria-label saying what it does, in the words a user would use.

    When this rule is wrong The name is computed in the real resolution order including a wrapped image's alt text, so an icon button whose img carries alt is not reported. It is wrong on a decorative anchor used purely as a scroll target, which has no name because it needs none: those should not be focusable at all, and the honest fix there is to remove the href rather than to invent a name.

  4. a11y.duplicate-idhighstructureweight 6

    The same id is used more than once

    Every label-for, aria-labelledby and aria-describedby reference resolves to the FIRST element with that id. A duplicate means one of those references silently points at the wrong element, and nothing in a browser reports it.

    How to avoid it Derive ids from something unique, or scope them to the component instance.

    When this rule is wrong Effectively never wrong, and it is worth saying why it looks harmless: duplicated ids often produce a page that renders and behaves correctly for a sighted mouse user, because only reference resolution breaks. The one legitimate case is a template fragment repeated by a component that was never meant to appear twice on one page, and that is a real defect in the component rather than a false positive here.

  5. a11y.label-points-nowheremediumlabelsweight 5

    A label points at an element that does not exist

    A label with for="email" and no element with id="email" labels nothing. The field is unnamed to a screen reader and clicking the label does not focus the input, both of which look fine to a sighted mouse user.

    How to avoid it Generate the for and the id from the same value so they cannot drift apart.

    When this rule is wrong Wrong when the target is rendered by client-side script after this check reads the document, which is why `counts.controls` is reported alongside: if that is zero the page may simply not have rendered its form yet, and the honest reading is that this check saw a shell.

  6. a11y.tabindex-positivemediumkeyboardweight 5

    A positive tabindex forces this element ahead of the natural focus order

    Any tabindex above zero jumps that element to the front of the whole page's tab sequence, ahead of everything with the default order. One positive value effectively requires every other focusable element to be managed by hand, and the result is a tab order that does not match the visible layout.

    How to avoid it Use tabindex="0" to make something focusable and tabindex="-1" to take it out, and let document order do the rest.

    When this rule is wrong Not wrong often, but the exception is real: a deliberately managed order inside a complex widget, such as a grid or a custom listbox that implements its own keyboard model, can use positive values coherently. This rule reads the DOM and cannot tell a coherent scheme from an accidental one, so check whether the values form a deliberate sequence before changing them.

  7. a11y.aria-hidden-focusablehighkeyboardweight 7

    Something hidden from screen readers can still be focused

    aria-hidden="true" tells a screen reader the element does not exist, but it does not remove it from the tab order. A keyboard user tabs onto a control their screen reader refuses to announce, so focus lands somewhere that reads as nothing at all.

    How to avoid it Pair aria-hidden="true" with tabindex="-1", or hide the element properly instead.

    When this rule is wrong Elements carrying tabindex="-1" are excluded, because that is the correct pairing. It is wrong when the element is also visually hidden by CSS in a way that removes it from focus, such as display:none, because this reads parsed HTML and cannot see computed styles: a control inside a closed menu will be reported and may be fine. Check whether the container is actually rendered before acting.

  8. a11y.role-not-in-vocabularymediumariaweight 5

    A role is not a real ARIA role

    A misspelled or invented role is ignored, and the element falls back to whatever its tag means. A div with role="buton" is announced as a plain group, so the control that looked labelled is silent.

    How to avoid it Prefer the native element. When a role is genuinely needed, copy it from the spec.

    When this rule is wrong Checked against a vendored snapshot of the WAI-ARIA 1.2 taxonomy, not against a hand-written list, and the snapshot's date is recorded in corpus/aria-vocabulary.mjs. Two things follow. A role added to a later specification than that snapshot would be reported wrongly, so check the date before believing this about a very new role. And a role used inside a design system that polyfills its own behaviour may be deliberate.

  9. a11y.role-is-abstractmediumariaweight 4

    An abstract role is used on an element, which the specification forbids

    Roles like widget, input and landmark exist to organise the taxonomy and the specification explicitly forbids authors from using them. They are ignored on an element, so the element silently keeps its native meaning.

    How to avoid it Use a concrete role that inherits from the abstract one, or the native element.

    When this rule is wrong Separated from the unknown-role rule ON PURPOSE, and that separation is the point. These roles are real, published and spelled correctly, so a checker that only asks whether a role exists passes them, and one that only knows concrete roles calls them typos, which sends the author hunting a misspelling that is not there. A vocabulary has three states here, valid, abstract and absent, and collapsing the middle one wastes somebody's afternoon.

  10. a11y.no-main-landmarkmediumlandmarksweight 4

    The page has no main landmark

    Screen reader users navigate by landmark to skip past the header and navigation. With no main, there is nothing to skip to, and reaching the content means tabbing through every link in the menu on every page.

    How to avoid it Wrap the page's own content in a main element, once per page.

    When this rule is wrong Wrong on a fragment that was never a whole page, such as an email template or an embedded widget, where there is no document to have a main region. If the artifact is not a page somebody navigates to, this rule does not apply and the report should be scoped instead.

  11. a11y.multiple-main-landmarkslowlandmarksweight 3

    The page has more than one main landmark

    Two main regions means the page asserts two different things to be its content, so navigating by landmark stops being a shortcut and becomes another list to read through.

    How to avoid it One main per page. Use section or article for the rest.

    When this rule is wrong Weak on purpose. A page rendering two views at once, such as a master and detail layout where only one is visible at a time, can end up with two main elements where only one is displayed, and this reads parsed HTML so it cannot see which. Check what is actually rendered.

  12. a11y.data-table-no-headersmediumtablesweight 5

    A table of data has no header cells

    Header cells are what let a screen reader announce which column a value belongs to. Without them a price table is read as a stream of numbers with no indication of what each one measures.

    How to avoid it Use th for the header row, with scope="col", and caption for what the table is.

    When this rule is wrong Tables with role="presentation" or role="none" are excluded, because those are declared as layout rather than data and that declaration is the correct way to say so. Single-row tables are also excluded, since one row is usually layout. What remains is a multi-row table claiming to be data with nothing naming its columns.

  13. a11y.media-autoplay-uncontrolledhighmediaweight 6

    Audio or video plays automatically with no way to stop it

    Unexpected sound covers a screen reader's own speech, which makes the page unusable rather than merely annoying, and there is no way to find the control that stops it if you cannot hear the announcement telling you where it is.

    How to avoid it Do not autoplay sound. If media must autoplay, mute it and add controls.

    When this rule is wrong Muted media is excluded, because a muted autoplaying video is a background decoration and makes no sound. What remains is unmuted autoplay with no controls attribute. It is wrong if script attaches controls after load, which this cannot see, so confirm in a browser before concluding the page is silent about it.

forms-and-captureforms-2026.09 · 6 rules
  1. forms.no-declared-destinationhighdeliveryweight 7

    A form declares no destination, so only script can be delivering it

    With no action attribute, submitting the form posts back to the same URL, which for a static page does nothing. That means a script is the only thing delivering it, and if that script is wrong or fails the visitor still sees whatever the page tells them.

    How to avoid it Confirm the handler's destination is real, and make it report a failure to the visitor rather than a success it cannot back up. A fallback that opens the visitor's own mail app is better than a lost submission.

    When this rule is wrong This fires on most modern forms and it is a QUESTION rather than a defect, which is why the wording says only script can be delivering it. A React or Next form with an onSubmit handler legitimately has no action, and that is correct practice. The right response is not to add an action, it is to confirm the handler delivers somewhere real and reports honestly when it cannot. This corpus cannot see the handler, so it cannot answer that for you, and this rule exists to make sure somebody asks.

  2. forms.no-submit-controlhighcompletionweight 8

    A form has no way to submit it

    With no submit button, the only way to send the form is pressing Enter in a text field, which does not work at all from a textarea and is not discoverable. On a touch screen there is no way at all.

    How to avoid it One clearly labelled submit button per form, inside the form element.

    When this rule is wrong Wrong when the submit control is rendered by script after this check reads the document, which is why the field count is reported alongside: a form with fields and no button is worth looking at, and one with neither is probably a shell that had not rendered. Also wrong for a search form that submits on input change by design, though those still want a visible control for touch users.

  3. forms.email-field-wrong-typemediuminputweight 4

    A field asking for an email address is not an email input

    type="email" gets the phone keyboard with the at sign on it, browser validation, and autofill. As type="text" it gets none of those, so a phone user types an address on the alphabetic keyboard and a typo reaches you instead of a warning reaching them.

    How to avoid it Use type="email" with autocomplete="email".

    When this rule is wrong Matched on the field's name or id containing 'email', so a field named 'emailPreference' that is genuinely a checkbox or a select is not reported, because only text-like inputs are considered. It IS wrong where a field deliberately accepts a comma-separated list of addresses, which type="email" rejects without the multiple attribute.

  4. forms.autocomplete-missingmediumcompletionweight 3

    A field a browser could autofill does not say what it holds

    An autocomplete token lets a browser or password manager fill the field in one tap. Without it a customer on a phone types their name, email and address by hand, and every extra field typed by hand is a chance to abandon the form.

    How to avoid it Add the matching autocomplete token. The list is short and it is in the HTML spec.

    When this rule is wrong Only fires on names this corpus can map with confidence, listed in AUTOFILL_HINTS, so an unusual field name is left alone rather than guessed at. It is wrong where filling from a saved profile would be actively unhelpful, for example a form asking about somebody else's details, and in that case autocomplete="off" is the honest answer rather than nothing at all.

  5. forms.radio-group-no-fieldsetmediumstructureweight 4

    A group of radio buttons has no shared question

    Each radio has its own label, but nothing states the question they answer. A screen reader announces 'Saturday, radio button, one of three' with no indication that the question was which day you want, so the options arrive without the thing they are options for.

    How to avoid it Wrap the group in a fieldset and put the question in a legend.

    When this rule is wrong Fires only on groups of two or more sharing a name, so a single radio used as a toggle is not reported. It is wrong when the question is carried by a heading immediately above the group and tied to it with aria-labelledby on a role="radiogroup" container, which is a valid alternative this rule cannot distinguish from nothing at all.

  6. forms.novalidate-with-requiredmediuminputweight 5

    The form marks fields required and then turns validation off

    novalidate disables the browser's own check, so a required field can be submitted empty. Either the script validates instead, or the customer submits an incomplete form and finds out later, or does not find out at all.

    How to avoid it Keep novalidate if you are validating in script, and make sure that script blocks submission and names the field that is wrong.

    When this rule is wrong Very often deliberate and correct: novalidate is the standard way to replace browser messages, which cannot be styled and read poorly, with your own. So this is a prompt to confirm the replacement exists rather than a defect on its own. It only fires where required attributes are also present, which is the combination that suggests the intent was validation rather than no validation.

broken-thingsbroken-2026.09 · 12 rules
  1. broken.interpolation-failedhighrenderingweight 10

    The page shows a value that failed to render

    undefined, null, NaN and [object Object] are what a template leaves behind when the value it expected was not there. A visitor reads them as the site being broken, and they are right. This is weighted at the top of the rulebook because it needs no expertise to confirm and no expertise to judge: the quoted text is either on the page or it is not.

    How to avoid it Render nothing rather than a failed value. A template that cannot find a name should omit the greeting, and a build that produces the literal string undefined in HTML should fail.

    When this rule is wrong Wrong on a page that is ABOUT programming, where undefined and NaN are ordinary vocabulary, which is why they are matched only as whole words and why a documentation site will still trip this. It is also wrong when the text is rendered correctly by JavaScript after delivery: this reads the HTML that arrived, so a placeholder replaced on the client looks broken here and is not.

  2. broken.template-syntax-unrenderedhighrenderingweight 10

    Template syntax reached the page unrendered

    Curly braces or angle-percent tags in the delivered HTML mean the template engine never ran over that part of the page, or ran and could not resolve it. The visitor sees the source of the site instead of the site. Like a failed interpolation, this is certain rather than inferred and needs no expertise to confirm.

    How to avoid it Make an unresolved placeholder a build failure rather than a runtime string. Most template engines can be configured to throw on a missing key instead of emitting the tag.

    When this rule is wrong Wrong on documentation that DEMONSTRATES template syntax, which is a real and common page type and will trip this rule every time. It is also wrong on a client-side framework that resolves its own bindings in the browser after delivery, which is why the finding quotes the surrounding text: a binding inside an app shell is expected, and the same string inside a sentence of marketing copy is not.

  3. broken.local-addresshighenvironmentweight 10

    A URL points at the machine the site was built on

    localhost and 127.0.0.1 resolve to the visitor's own computer, so a link or an image pointing there fails for everybody except the person who built the page, on whose machine it works perfectly. That asymmetry is why it survives testing, and it is most often on a form action or an API call, which means the thing that breaks is the thing that takes the money.

    How to avoid it Build every URL from one configured origin that differs by environment, and never from a literal. A hardcoded localhost is a value that was correct once.

    When this rule is wrong Wrong only on a page never intended to be public: a local development build, or a template checked before deployment. If the file being checked is a production build, this rule cannot be wrong about the address, because localhost has exactly one meaning and it is not the server.

  4. broken.filesystem-pathhighenvironmentweight 10

    A URL is a path on somebody's own computer

    A file:// URL or a path beginning with a drive letter or a home directory refers to the filesystem of whoever wrote the page. It loads nothing for a visitor. It also publishes the name of the account it came from, which is usually somebody's real name, into the page source.

    How to avoid it Add images to the project and reference them by their served path. A file:// URL almost always arrives from dragging a file into an editor or a visual builder.

    When this rule is wrong Wrong only on a page meant to be opened from disk rather than served, which is rare enough to be worth confirming when it happens. On a served page this cannot be right: a visitor's browser will not read a file from the author's Desktop, and no configuration makes it.

  5. broken.development-hosthighenvironmentweight 8

    A URL points at a staging or tunnel host

    A staging subdomain, an ngrok tunnel or a .local address is reachable by whoever set it up and by nobody else, and the tunnel ones stop existing when the session that created them ends. A visitor gets a failure whose cause is invisible from the page.

    How to avoid it Keep environment hostnames in configuration, never in content, and check a production build for them before deploying rather than checking the running site afterwards.

    When this rule is wrong This rule deliberately does NOT list vercel.app or netlify.app, because a great many real small business sites are served from one of those as their genuine public address and reporting it would tell somebody their live domain is a mistake. Only hosts that cannot be a public address are matched. It is still wrong on a site that genuinely operates a subdomain called test or dev as a real service.

  6. broken.image-has-no-sourcehighassetsweight 8

    An image element has nothing to load

    An img with no src, an empty src, or a src of # renders as a broken image placeholder or as nothing at all, depending on the browser. An empty src is worse than nothing: some browsers resolve it to the page's own URL and request the whole page again as an image.

    How to avoid it Render no img element at all when there is no image, rather than one with an empty src. Use the loading attribute for lazy loading instead of an empty src and a script.

    When this rule is wrong Wrong on an image whose src is set by JavaScript after delivery, which is how lazy loading worked before the loading attribute existed and is still common. An img carrying a data-src or a srcset is likely that case, and images with a srcset are excluded for exactly this reason.

  7. broken.empty-urlmediumassetsweight 6

    A URL attribute is present but empty

    An empty href or action is resolved against the current page, so the link goes nowhere visible and the form submits to itself. Nothing appears broken and nothing works, which is the combination that takes longest to find. On a form action it means submissions are lost.

    How to avoid it Treat an empty URL as a missing one at the point the value is built, and omit the attribute rather than emitting it empty.

    When this rule is wrong An empty action IS a documented way to submit a form back to its own URL, so on a page that handles its own submission this is correct and intentional. It is not correct on an anchor, where an empty href produces a link to the current page with no indication that is what it does.

  8. broken.fragment-target-missingmediumnavigationweight 6

    A link jumps to a section that is not on the page

    A link to #pricing does nothing at all if no element on the page has that id. The click registers, the page does not move, and a visitor concludes the site is broken rather than that they missed something. It is the commonest result of renaming a section.

    How to avoid it Generate in-page links from the same list that generates the sections, so a renamed section cannot leave a link behind.

    When this rule is wrong Wrong when the target is added by JavaScript after delivery, or when the fragment is meant for a single-page router that reads it rather than for the browser's own scrolling. Both are real. This reads the HTML that arrived, so anything created later is invisible to it.

  9. broken.escaped-markup-in-texthighrenderingweight 7

    HTML tags are being shown to the visitor as text

    A paragraph reading <strong>Open today</strong> means content was escaped twice: once when it was stored and again when it was rendered. The visitor sees the markup rather than the formatting. It usually affects one field everywhere it appears, so it looks like a site-wide problem to whoever reports it.

    How to avoid it Escape once, at output. Storing pre-escaped HTML and escaping it again on the way out is the usual cause, and it is fixed at the point of storage rather than in the template.

    When this rule is wrong Wrong on any page that deliberately shows markup as an example, which includes documentation, a tutorial and a code sample, and those will trip this rule. The finding quotes the surrounding text so that case is obvious to a reader. It is also narrow on purpose: only a handful of common tag names are matched, so escaped markup using an uncommon tag is missed rather than guessed at.

  10. broken.zero-dimension-imagelowassetsweight 4

    An image is sized to nothing

    A width or height of zero renders the image invisible while still downloading it, so the visitor waits for something they never see. It is usually left over from hiding an element by resizing it rather than by removing it.

    How to avoid it Hide an element with CSS or omit it, rather than by setting a dimension to zero, so the browser can also skip the download.

    When this rule is wrong Wrong on a tracking pixel or a beacon, which is deliberately sized to nothing and is doing its job, and that is a common enough pattern that this rule is weighted low rather than reported as serious. An image with no alt text and a zero dimension is almost certainly a pixel rather than a defect.

  11. broken.doubled-path-separatorlowassetsweight 4

    A URL contains a doubled slash in its path

    A path like /assets//logo.png is what joining a base ending in a slash to a path starting with one produces. Many servers tolerate it and some do not, and the ones that do not return a not-found for an asset that exists, which makes the failure depend on hosting rather than on the site.

    How to avoid it Join URL segments with a helper that collapses separators rather than by concatenating strings, so a base path with or without a trailing slash produces the same result.

    When this rule is wrong Wrong wherever the doubled slash is meaningful or harmless, which is most servers, so this is weighted low and reported as tidiness. The scheme's own two slashes and a protocol-relative URL are both excluded, since neither is the case this describes.

conversion-auditorconversion-2026.09 · 6 rules
  1. conversion.no-route-to-any-actionhighreachabilityweight 8

    Nothing on this page asks the visitor to do anything

    A page with links and buttons but no contact link, no phone, no form and no action wording is a page somebody can read and leave with no next step. On a small-business site that is the whole cost of the page: the visitor was interested enough to arrive and had nowhere to go.

    How to avoid it Give every page that could end a visit one clear next step, even if it is a link to the page that has the real action on it.

    When this rule is wrong Wrong on pages that SHOULD have no action, which is why it is scoped to pages with interactive elements and why the finding names what it found instead. An article, a policy page or a blog index legitimately asks for nothing. Read it as a question about whether this particular page is one of those, not as a defect on every page it names.

  2. conversion.phone-number-is-not-tappablehighreachabilityweight 8

    A phone number is printed as text with no tel: link anywhere on the page

    Most visitors to a local business site are on a phone. A number they cannot tap is a number they have to memorise, switch apps for and retype, and a meaningful share of them do not. This is the single cheapest conversion fix on a small-business site and it is almost always missed, because it works perfectly on the desktop the site was built on.

    How to avoid it Wrap it: <a href="tel:+15551234567">(555) 123-4567</a>. Keep the human formatting in the text and put the E.164 form in the href.

    When this rule is wrong Wrong when the matched text is not a phone number at all: an order reference, a licence number, an ABN or a date range can take the same shape, which is why the pattern requires separators or a country prefix rather than accepting any run of digits. It is also wrong when the number is deliberately not for calling, such as a fax line or a number shown as an example. The observed text is quoted so that is decidable at a glance.

  3. conversion.email-is-not-a-mailtomediumreachabilityweight 5

    An email address is printed as text with no mailto: link anywhere on the page

    Same failure as the phone number and a smaller one, because an address can be copied. It still costs the visitor a deliberate act at the exact moment they had decided to get in touch.

    How to avoid it Link it, or replace it with a form. If it is unlinked to deter scraping, say so somewhere a reviewer will see, so the next audit does not re-raise it.

    When this rule is wrong Frequently deliberate, and that is why this is medium rather than high: addresses are often printed unlinked to slow down scrapers, which is a real trade a business is allowed to make. Wrong too when the address belongs to somebody else, such as a quoted reference or a licence contact.

  4. conversion.primary-action-shipped-disabledhighblockingweight 9

    An action control is disabled in the bytes that shipped

    A disabled button in the built HTML is a control nobody can press until script enables it. If that script fails, is blocked, or never runs, the visitor sees the thing they came to do and cannot do it. It is also invisible in review, because whoever checks it has JavaScript working.

    How to avoid it Ship the control enabled and let the server reject bad input, or make sure the enabling script cannot fail silently. A control that is disabled with no visible reason is a dead end.

    When this rule is wrong Correct and expected on a form that enables its submit only once the fields validate, which is good practice. The finding is worth reading anyway: it tells you the page depends on script for its primary action, and that is a dependency worth knowing rather than a defect by itself.

  5. conversion.form-asks-for-too-muchmediumfrictionweight 5

    A form makes more than five fields required before anybody can send it

    Every required field is a place to stop. For a first contact, the business usually needs a way to reply and a sentence about what is wanted; the rest can be asked in the reply, when the person is already talking to you rather than deciding whether to.

    How to avoid it Require the minimum that lets you reply. Keep the rest, optional, for the people happy to give it.

    When this rule is wrong Wrong whenever the fields are genuinely needed before anybody can respond, which is common: a booking needs a date, a quote needs the job, a regulated trade may need details it cannot proceed without. The required field names are listed in the finding so that judgement can be made without opening the page.

template-tellstells-2026.09 · 10 rules
  1. tells.scaffold-titlehighscaffoldweight 9

    The page title is still a framework or builder default

    The title is the clickable line in a search result and the label on the browser tab. When it still says Create Next App or Untitled, that is what a search engine shows, what a bookmark is named, and what appears when somebody shares the page. It is the single most visible unfinished thing on a site and it costs one line to fix.

    How to avoid it Set the title per route as the first thing after scaffolding, before any styling. A default title survives because it looks like content rather than like configuration.

    When this rule is wrong Wrong when the default IS the intended title, which happens on a page genuinely called Home or Welcome. Those two are the most likely honest matches in the list and a one-word title is worth reconsidering anyway. This rule only fires on a POSITIVE match against a vendored list of known defaults, so a title absent from that list is never reported: the list being incomplete costs coverage, never correctness.

  2. tells.placeholder-copyhighscaffoldweight 9

    Template placeholder copy is still on the page

    Lorem ipsum, Your headline here, or Feature One is copy the template shipped with. A visitor reads it as the business not being open yet, or not being real. Unlike a styling choice this is unambiguous: nobody writes Feature One on purpose.

    How to avoid it Delete placeholder copy when you delete the placeholder image, in the same pass. It survives because it is grammatical and sits in a section that looks finished.

    When this rule is wrong Wrong on a page that is deliberately showing a template, which is what a theme demo, a component gallery and a design system page all are, and those will trip this rule. The finding quotes the surrounding sentence so a reader can see immediately which case they are in. It is also wrong on a business genuinely offering a product called something like Step Two, which is rare but real.

  3. tells.no-faviconmediumfinishweight 6

    The site declares no icon

    With no icon a browser tab shows a blank sheet of paper, and a bookmark or a phone home screen shortcut shows the same. It is the detail that makes a site look like a document somebody uploaded rather than a business, and it is visible every time anybody has the tab open.

    How to avoid it Declare the icon explicitly with a link tag rather than relying on the root request, so it is visible in the page and survives a move to a subdirectory or a CDN.

    When this rule is wrong Wrong when a favicon.ico sits at the site root, which browsers request WITHOUT any link tag and this probe cannot see because it reads a document rather than making requests. So a site with a root favicon and no link tag is fine and this rule will still report it. Check for the file before changing anything.

  4. tells.no-share-imagemediumfinishweight 6

    The page has no image for when it is shared

    Without og:image, a link posted to a message, a group chat or a social platform renders as a bare line of text while every link around it has a picture. That is the whole difference between a link somebody taps and one they scroll past, and it is decided by one tag.

    How to avoid it Set a site-wide default share image in the layout and override it per page where it matters, so a new route is never shipped without one.

    When this rule is wrong Wrong when a share image is supplied another way, for example a twitter:image tag alone or a platform-specific card this rule does not read. It is also of no consequence on a page nobody shares, such as a checkout step or an account settings screen, where the correct response is to ignore it rather than to add an image.

  5. tells.bare-platform-domainmediumfinishweight 5

    The site is published on the hosting platform's own subdomain

    A canonical URL ending in vercel.app or wixsite.com tells every visitor and every search engine that no domain has been bought. It is not broken and it costs nothing technically, but it is the single clearest signal that a site is a project rather than a business, and it is in the address bar of every page.

    How to avoid it Buy the domain before the site is shown to anybody, and set the canonical to it, so the address does not change once links to it exist.

    When this rule is wrong A platform subdomain is a perfectly working public address and plenty of real businesses operate on one, so this is a finish finding rather than a defect and is weighted accordingly. Note the deliberate difference from `broken.development-host`, which asks whether a visitor can reach the address and correctly says these are fine. This rule asks whether anybody bought a domain. IT ALSO FIRES ON PROOF'S OWN SITE TODAY, and that is left in rather than exempted.

  6. tells.builder-generator-with-another-tellmediumscaffoldweight 5

    A site builder's generator tag sits alongside another unfinished tell

    A generator tag naming a page builder is not a defect on its own: plenty of good sites are built with one and are proud of it. It matters when it is the ONLY thing on the page that has been filled in, because then it says the template was published rather than used.

    How to avoid it Nothing needs doing about the generator tag. Fix the other finding this one is paired with; this rule exists only to say the two together mean something the first does not.

    When this rule is wrong This rule is deliberately CONDITIONAL and will not fire on a builder tag alone, because doing so would report every Squarespace and Webflow site on the web as defective, which is both wrong and insulting to the people running them. It requires a second, independent tell on the same page. It is still wrong where that second tell is itself a false positive, so read both findings together rather than either alone.

  7. tells.copyright-year-stalemediumfinishweight 6

    The copyright year is years out of date

    A footer reading 2021 tells a visitor the site has not been touched in years, which for a business raises the only question that matters: are they still open. It appears on every page and it is the last thing on each of them.

    How to avoid it Render the current year rather than writing it, and check that the value is computed at request or build time rather than baked into a static export made years ago.

    When this rule is wrong Wrong on a page whose copyright genuinely dates a fixed work rather than the site, such as an archived article or a document with its own date of publication. It is also wrong for a year range like 2019 to 2026, and ranges are excluded. The threshold is two full years behind, not one, so a site updated last year is never reported.

  8. tells.em-dash-densitylowcopyweight 3

    Em dashes appear far more often than in ordinary business writing

    A high em dash rate is one of the more reliable textual signals that copy was generated rather than written, because the character is common in generated prose and rare in what a small business writes about itself. It is reported with the rate and the denominator so it can be judged rather than believed.

    How to avoid it Nothing here needs fixing if the punctuation is deliberate. If the copy was generated, the thing to change is the copy rather than the dashes.

    When this rule is wrong Wrong on any writer who genuinely uses the em dash, which includes most publishers with a style guide and a good many careful writers, and on any page reproducing edited prose such as a press quote or an excerpt. This is a signal about STYLE, never about honesty, and it is weighted at the bottom of the rulebook for that reason. It says nothing about whether the copy is true or good.

Money

Checkout, prices and the path to a receipt.

No published rulebook yet. This department's work is judgement rather than measurement, so nothing it does is claimed on this page.

Reach

What to post and send, and whether your email arrives.

No published rulebook yet. This department's work is judgement rather than measurement, so nothing it does is claimed on this page.

Customers

The questions you answer every week, and bookings.

No published rulebook yet. This department's work is judgement rather than measurement, so nothing it does is claimed on this page.

Business

Margins, cash timing and renewal dates.

No published rulebook yet. This department's work is judgement rather than measurement, so nothing it does is claimed on this page.

Trust

What the site claims against what you can back up.

claims-officerclaims-2026.09 · 8 rules
  1. claims.performance-figurehighsubstantiationweight 8

    A numeric performance claim, which somebody may ask you to prove

    A stated percentage or multiple is the most substantiable kind of claim there is, which cuts both ways: it is persuasive precisely because it sounds measured, and if it was not measured there is nothing to produce when asked. The FTC's consent order against an AI detection company in 2025 turned on a single accuracy figure the company could not substantiate.

    How to avoid it Keep the number and be able to show where it came from, or replace it with something you can demonstrate. A number with its source beside it is stronger copy anyway.

    When this rule is wrong Wrong whenever the figure IS measured and you can show the working, which is often. A bakery saying "we sell 400 loaves a week" has a till roll. What this rule cannot see is whether the evidence exists, so read it as a question about where the number came from rather than as an accusation. It also skips any sentence containing a negation, so a disclaimer saying you do not claim a figure is not reported.

  2. claims.guaranteed-rankinghighsubstantiationweight 9

    A promise about search rankings, which nobody is able to keep

    No agency controls a search engine's results, so a promise about position is a claim with no mechanism behind it. It is also one of the most commonly complained-about promises in the sector, which makes it a bad thing to have in writing on your own site.

    How to avoid it Promise the work rather than the outcome. "We fix what stops Google reading your site" is both honest and more concrete than a position nobody can hold.

    When this rule is wrong Skips any sentence containing a negation, so "we do not promise rankings" and "nobody can promise a ranking" are correctly ignored, which matters because those are the sentences a careful site WANTS. It is genuinely wrong where the promise is about something you do control, such as guaranteeing you will submit a sitemap, so read the sentence rather than the rule id.

  3. claims.guaranteed-outcomehighsubstantiationweight 7

    A guaranteed business result, which depends on things outside your control

    Guaranteed customers, revenue, leads or traffic all depend on a market, a season and a competitor's behaviour. A guarantee is a contractual promise, so it is the one form of marketing copy a customer can hold you to directly.

    How to avoid it Guarantee what you do, not what the market does. A refund promise is concrete, keepable and more persuasive than a projection.

    When this rule is wrong A guarantee you actually honour is not a false claim, it is a policy, and plenty of good businesses guarantee a refund or a redo. So this fires on guaranteed RESULTS rather than guaranteed service or a money-back promise, and a sentence with a negation is skipped. If you genuinely refund when the result does not come, say that instead: it is the same promise without the exposure.

  4. claims.health-benefithighregulatedweight 9

    A health or medical benefit, which is the most heavily regulated thing a business can say

    A claim that a product prevents, treats or improves a condition is a health claim, and for food and drink it is regulated well beyond ordinary advertising law. It is easy to make by accident: a juice bar writing that a drink boosts immunity has made one, and so has a candle shop writing that a scent relieves anxiety.

    How to avoid it Describe what it is and what is in it. Leave what it does to a body's health alone unless you have an authorised claim to quote.

    When this rule is wrong Wrong where the claim is authorised, substantiated or purely descriptive of an ingredient rather than an effect, so "contains vitamin C" is fine while "boosts your immune system" is the claim. It cannot tell an authorised claim from an invented one, and it does not know your jurisdiction. Treat every finding as a prompt to check with somebody who does, not as a verdict, and note that the safest wording is usually the most specific one.

  5. claims.natural-or-organic-unqualifiedmediumregulatedweight 5

    Organic or all-natural, used as a bare label

    Organic is a certification with a legal meaning in most markets and using it uncertified is a specific offence rather than mere puffery. All-natural has no legal definition, which is the opposite problem: it is a term regulators watch precisely because it means nothing and sounds like it means something.

    How to avoid it Name the certifier and the number, or describe the actual practice. "Certified organic by X" and "no cane sugar, ever" both beat a bare adjective.

    When this rule is wrong Wrong whenever you ARE certified, in which case the fix is to name the certifier rather than to drop the word, and that is stronger copy. Also wrong where natural describes a process everybody can verify, such as naturally leavened bread. This rule cannot see your paperwork.

  6. claims.free-with-stringsmediumpricingweight 6

    Something described as free in the same breath as a cost

    Free next to a price or a condition is the oldest complaint category in advertising regulation. If the thing is only free when you buy something else, the condition has to be as prominent as the word free, not in a footnote.

    How to avoid it Put the condition in the same sentence as the word free, in the same size. "Free draft, and you only pay if you keep it" is unambiguous.

    When this rule is wrong This project's own site says the draft is free and states a price for the finished site in the same region, and that is honest because the two are different things and the page says so. So a finding here is often correct copy about two separate offers, and the question to ask is whether a reader could think the priced thing is the free thing. If they could not, ignore it.

  7. claims.unqualified-superlativelowsubstantiationweight 2

    A bare superlative, which is only a problem if somebody reads it literally

    Best, number one and leading are objective claims when a reader could take them as fact, and puffery when nobody would. The line is genuinely blurry, and where it lands depends on how specific the claim is: best coffee in town invites a comparison, best day of your life does not.

    How to avoid it Where the superlative is doing real work, make it specific and checkable. Where it is just warmth, leave it alone.

    When this rule is wrong Weight 2 on purpose, and it will fire on copy that is completely fine. Obvious enthusiasm is protected as puffery almost everywhere, and this rule cannot tell enthusiasm from a comparative assertion. It exists so somebody looks once at the strongest sentence on the page, not so anybody edits it. If reading it aloud sounds like a boast rather than a measurement, ignore this finding.

  8. claims.deadline-already-passedmediumpricingweight 6

    An offer with a deadline that has already gone by

    A sale that ended last year is still on the page, which tells a visitor the site is not looked after and tells a regulator the urgency was never real. Perpetual urgency is a recognised deceptive pattern, and the accidental version looks identical to the deliberate one.

    How to avoid it Put dated offers behind a date you can update in one place, or take the deadline out and let the offer stand on its own.

    When this rule is wrong Only fires where a FULL date including a year is present and that date is in the past, so "ends Sunday" and "this weekend only" are never reported: they are almost always true and there is no way to tell from parsed text. It is wrong on a page describing a past event historically, such as a report on last year's market, where the date is the subject rather than a deadline.

Standards

Audits the other seven.

No published rulebook yet. This department's work is judgement rather than measurement, so nothing it does is claimed on this page.

What these rulebooks cannot see

Published because a gap you know about is worth more than a number you cannot check.

  • Anything needing an outside request. Whether a link resolves, what a competitor ranks for, whether a domain is about to lapse. These checks make no network requests at all, which is also why nothing about your business leaves your site to run them. Those questions belong to other departments and they ask permission first.
  • Anything about search results. No rule here claims a ranking effect, because nobody can substantiate one. A rule states a property of the page: the title is this long, this image has no alt attribute.
  • Whether the writing is any good. That is a judgement, it is done by a person reading it, and it is not on this list because it is not a measurement.
  • Anything rendered after the page settles. The check reads the page once it has loaded. Content that appears later, after a click or a delay, is not seen, and when too little of a page can be read the check says so and withholds the result rather than reporting a clean one.

Back to Proof  ·  Manage billing