---
title: "The bar for generated pages"
url: /interfaces/standards
---

# The bar for generated pages

> Every page a product generates meets one bar: WCAG 2.2 AA in both schemes, search-engine metadata, a speed budget on a phone, and every locale.

Every page a product generates meets one bar: accessible, found by search engines, fast on a phone, and written in every locale.

**From a deploy to a page that passes.** A deploy.

1. Render: every locale.
2. The bar (before it serves): checked per page. Or refused: page_below_bar.
3. Served (passes): cached, no script.
4. Measured (by the Verifier): every day. Or an alert: and the status.

Your own pages meet the same bar as the generated ones.

## The numbers
The bar is part of the generator, so a generated page meets it by construction. These are the lines it holds.

| Measure | The bar |
| --- | --- |
| Text contrast | 4.5:1; 3:1 for large text, outlines and focus rings |
| Targets | 24 by 24 pixels, or spaced as far apart |
| Largest paint | 2.5 seconds at the 75th percentile on a phone |
| Response to input | 200 milliseconds at the 75th percentile |
| Layout shift | 0.1 or less |
| Automated accessibility checks | 0 violations at 390 and 1440 pixels wide, in both schemes |

Speed is measured on a phone over a slow network, with every page readable before any script runs.

## Five readers, one page
Each row is a reader the generator builds for, and what it does for them.

| The reader | What they get | How the page is built for it |
| --- | --- | --- |
| Someone on a screen reader or a keyboard | Every page, form and drawing in words, in order, with focus always visible | WCAG 2.2 AA in both schemes: contrast, a label on every control, heading order, keyboard paths, motion only when allowed. Each form from a Verb names every field, marks the required ones, and shows a refusal's cure beside its field |
| Someone searching | A page per Noun and task whose title, description and card say what it answers | Metadata from the file's comments, a canonical URL on the [Host](/reference/platform.md#host), structured data for the product and each price, `sitemap.xml` and `robots.txt` |
| Someone on a phone and a slow network | The price and the first answer in under three seconds, nothing moving under their thumb | No framework runtime on a static page, images at the size the screen needs, fonts cut to the characters used |
| Someone in another language | Their [locale](/interfaces/locales.md), dates, numbers and money in their format, right-to-left where the script runs that way | A path per locale and `hreflang` between them |
| An agent | The same words, at the same address, in the locale it asks for | A Markdown twin per page, `llms.txt` in reading order, and JSON at `/` |

Each image and drawing carries words from the file's descriptions, and each chart has its table. Test, every branch and every review link answer `noindex`. A superseded [Version](/reference/platform.md#version)'s pages point to the live one, and a renamed Noun or Verb answers its old path with 301 while its Version answers.

## Your own pages and your brand
`site.pages` and `docs.guides` meet the same bar.

Check refuses a page that fails with `page_below_bar` and the line that fails, and nothing of that deploy serves.

`brand.color` holds 4.5:1 on the light page, and the dark scheme takes the nearest lighter step that holds there. Check refuses a colour that fails with `brand_contrast`.

### Example
The [Dataset API](/patterns/dataset-api.md) adds German and a page of its own, and keeps its brand colour.

```ts title="definition.ts"
import { define, secret } from 'apis.dev'

/** Any company by its domain, with its headcount and sector, in English and German. */
export default define({
  name: 'companyfacts', domain: 'companyfacts.apis.dev',
  brand: { logo: './logo.svg', color: '#0b6e4f' },        // 6.3:1 on white; lighter in dark
  site: { pages: { '/coverage': './coverage.mdx' } },     // checked against the bar
  docs: { guides: ['./guides/enrich-a-crm.mdx'] },
  locales: ['en', 'de'],                                  // a path, a sitemap and hreflang each
  sources: {
    firms: { origin: 'https://firms.example.com', key: secret('FIRMS_KEY') },
  },
  Company: {
    domain:    { type: 'url!#',   from: 'firms', map: 'website' },
    employees: { type: 'number?', from: 'firms', map: 'headcount' },
    sector:    { type: 'string?', from: 'firms', map: 'industry' },
  },
  offer: { 'Company.get': 0.002, 'Company.search': 0.005 },
})
```

Change the colour to a light green, and check names the one key that fails and the nearest colour that holds.

```http title="Response"
HTTP/1.1 200 OK
Content-Type: application/json
X-Cost: price=0, currency="USD"
X-Request-Id: req_93f1a6c0e2d7b4581c9a

{
  "ok": false,
  "problems": [
    {
      "code": "brand_contrast",
      "at": "/brand/color",
      "message": "#7fd1ae reads 1.8:1 on the light page; #2b7a5c holds 4.5:1"
    }
  ]
}
```

Run it as `npx apis.dev check`, `POST https://apis.dev/v1/products/companyfacts/checks`, or `check` on the docs MCP.

### Measured from outside
The [Verifier](/trust/verifier.md) runs the accessibility checks and the speed budget at each deploy and each day, and the status page prints them.

Each deploy renders and checks every page in every locale before any of it serves; the next Version supersedes a page, which stays readable. A failed measurement writes an Event, reaches your [alert route](/trust/alerts.md), and shows on the status page.