---
title: "Rules: every check void can report"
description: "All 273 void rule ids for performance, smoothness, accessibility, SEO, AI search, design and lint, with severity, why it matters and how to fix it."
canonical: https://void-design.vercel.app/rules
lastModified: 2026-09-16
---

# void rules

273 rules. Every finding the CLI prints carries one of these ids; `void rules <id>` explains it in a terminal.

Every id below is a page at https://void-design.vercel.app/rules/<id>; add `.md` for the Markdown version.

## Performance 25

`void perf`

Core Web Vitals and bytes: LCP, CLS, TBT, TTFB, JavaScript, CSS, fonts, images, caching and compression, measured as the median of several runs on a throttled mobile and a desktop profile.

- `perf/lcp-slow` Largest Contentful Paint is slow — error
- `perf/cls-high` Cumulative Layout Shift is high — error
- `perf/ttfb-slow` Server response (TTFB) is slow — warn
- `perf/fcp-slow` First Contentful Paint is slow — warn
- `perf/tbt-high` Main thread is blocked by long tasks during load — warn
- `perf/js-budget` First-load JavaScript exceeds the budget — warn
- `perf/css-budget` CSS transfer exceeds the budget — warn
- `perf/font-budget` Web font transfer exceeds the budget — warn
- `perf/too-many-fonts` Too many font files are downloaded — warn
- `perf/image-budget` Image transfer exceeds the budget — warn
- `perf/too-many-requests` Page makes many requests — info
- `perf/lcp-image-lazy` LCP image is lazy-loaded — error
- `perf/lcp-image-no-priority` LCP image has no fetchpriority="high" — warn
- `perf/lcp-background-image` LCP element is a CSS background image — warn
- `perf/image-oversized` Image is much larger than its rendered size — warn
- `perf/image-legacy-format` Image served in a legacy format — warn
- `perf/images-missing-dimensions` Images without width/height or aspect-ratio — warn
- `perf/render-blocking` Render-blocking resources delay first paint — warn
- `perf/no-text-compression` Text resources are served uncompressed — warn
- `perf/cache-headers` Hashed static assets are not cached long-term — warn
- `perf/third-party-heavy` Heavy third-party footprint — warn
- `perf/dom-size` DOM is very large — warn
- `perf/bfcache-blocked` Page is not eligible for the back/forward cache — warn
- `perf/lighthouse-score-low` Lighthouse performance score is low — warn
- `perf/lighthouse-audit` Lighthouse flagged an opportunity — info

## Smoothness 13

`void smooth`

How the page feels after it loads: dropped frames during a scripted scroll, long animation frames, INP of real clicks, shifts after input, animated layout properties, idle rAF loops and reduced-motion handling.

- `smooth/scroll-jank` Frames are dropped while scrolling — warn
- `smooth/long-frames-during-scroll` Long animation frames block scrolling — warn
- `smooth/scroll-listener-nonpassive` Non-passive wheel/touch listener — warn
- `smooth/inp-slow` Interactions are slow to respond (INP) — warn
- `smooth/layout-shift-after-input` Layout shifts long after an interaction — warn
- `smooth/layout-shift-on-scroll` Layout shifts while scrolling — warn
- `smooth/animate-layout-property` Animation of a layout-triggering property — warn
- `smooth/transition-all` transition: all — warn
- `smooth/will-change-overuse` will-change is applied too broadly — info
- `smooth/reduced-motion-ignored` Animations keep running with prefers-reduced-motion — warn
- `smooth/raf-loop-idle` requestAnimationFrame loop runs while idle — warn
- `smooth/scroll-timeline-no-fallback` Content is invisible without scroll-driven-animation support — error
- `smooth/multiple-webgl-contexts` Multiple WebGL contexts — warn

## Accessibility 48

`void a11y`

WCAG 2.2 AA through axe-core, plus checks axe can't do alone: visible keyboard focus, 24/44px target size on mobile, 320px reflow and disabled zoom.

- `a11y/color-contrast` Text has insufficient color contrast — error
- `a11y/image-alt` Image has no text alternative — error
- `a11y/button-name` Button has no accessible name — error
- `a11y/link-name` Link has no accessible name — error
- `a11y/label` Form field has no label — error
- `a11y/select-name` Select element has no accessible name — error
- `a11y/input-button-name` Input button has no accessible name — error
- `a11y/input-image-alt` Image button has no alt text — error
- `a11y/html-has-lang` <html> has no lang attribute — error
- `a11y/html-lang-valid` <html lang> is not a valid language tag — error
- `a11y/document-title` Page has no <title> — error
- `a11y/landmark-one-main` Page has no <main> landmark — warn
- `a11y/region` Content is outside landmark regions — warn
- `a11y/landmark-unique` Landmarks are not uniquely identifiable — info
- `a11y/page-has-heading-one` Page has no level-one heading — warn
- `a11y/heading-order` Heading levels skip — warn
- `a11y/empty-heading` Heading is empty — warn
- `a11y/list` List contains invalid children — warn
- `a11y/listitem` <li> is not inside a list — warn
- `a11y/aria-allowed-attr` ARIA attribute is not allowed on this role — error
- `a11y/aria-required-attr` Required ARIA attribute is missing — error
- `a11y/aria-required-children` ARIA role is missing required children — error
- `a11y/aria-required-parent` ARIA role is missing its required parent — error
- `a11y/aria-roles` Invalid ARIA role — error
- `a11y/aria-valid-attr` Invalid ARIA attribute name — error
- `a11y/aria-valid-attr-value` Invalid ARIA attribute value — error
- `a11y/aria-hidden-focus` Focusable element inside aria-hidden — error
- `a11y/aria-hidden-body` aria-hidden on <body> — error
- `a11y/aria-command-name` ARIA button/link/menuitem has no accessible name — error
- `a11y/aria-input-field-name` ARIA input field has no accessible name — error
- `a11y/aria-toggle-field-name` ARIA toggle has no accessible name — error
- `a11y/aria-prohibited-attr` ARIA attribute is prohibited for this role — warn
- `a11y/nested-interactive` Interactive controls are nested — error
- `a11y/duplicate-id-aria` Duplicate id referenced by ARIA — error
- `a11y/frame-title` <iframe> has no title — warn
- `a11y/svg-img-alt` SVG image has no text alternative — error
- `a11y/role-img-alt` role="img" element has no text alternative — error
- `a11y/tabindex` Positive tabindex — warn
- `a11y/scrollable-region-focusable` Scrollable region is not keyboard accessible — warn
- `a11y/link-in-text-block` Links in text are distinguished only by color — warn
- `a11y/autocomplete-valid` Invalid autocomplete value — warn
- `a11y/meta-viewport` Zooming is disabled — error
- `a11y/bypass` No way to skip repeated content — warn
- `a11y/video-caption` Video has no captions — error
- `a11y/axe-other` Other axe-core accessibility violation — warn
- `a11y/target-size` Touch target is smaller than 24×24 px — warn
- `a11y/focus-not-visible` Keyboard focus is not visible — error
- `a11y/reflow-overflow` Content overflows horizontally at 320px — warn

## SEO 60

`void seo`

What search engines need from the raw response: status codes, titles and descriptions in <head>, self-referencing canonicals, one h1, crawlable links, Open Graph, icons, robots.txt and a sitemap with real dates.

- `seo/http-status` Page does not return HTTP 200 — error
- `seo/redirect-chain` URL reaches its content through more than one redirect — warn
- `seo/redirect-temporary` Temporary redirect (302/303/307) used — warn
- `seo/client-redirect` Redirect done with meta refresh or JavaScript — warn
- `seo/noindex` Indexable page carries noindex — error
- `seo/robots-snippet-restricted` nosnippet or a very low max-snippet limits previews — info
- `seo/soft-404` Missing page returns HTTP 200 (soft 404) — error
- `seo/html-too-large` Uncompressed HTML is over Googlebot's 2 MB fetch limit — error
- `seo/rsc-payload-ratio` Inline RSC flight payload dominates the HTML — info
- `seo/not-https` Page served over plain HTTP — warn
- `seo/origin-redirects` http:// or www/apex variant doesn't redirect to the canonical origin in one hop — error
- `seo/html-lang` <html lang> missing or not a valid BCP 47 tag — error
- `seo/viewport` Viewport meta missing, not device-width, or blocks zoom — error
- `seo/charset` Character encoding not declared early — warn
- `seo/title-missing` No single non-empty <title> in <head> — error
- `seo/title-length` Title length outside 10–60 characters, or keyword-stuffed — warn
- `seo/meta-description-missing` No meta description in <head> — error
- `seo/meta-description-length` Meta description length outside 50–160 characters — warn
- `seo/metadata-in-body` Crawler receives <title>/canonical/description inside <body> (Next.js streaming metadata) — error
- `seo/canonical-missing` No single rel=canonical in <head> — error
- `seo/canonical-invalid` Canonical URL is relative or contains a fragment — error
- `seo/canonical-broken` Canonical points to a URL that returns 4xx/5xx — error
- `seo/canonical-conflict` Canonical target redirects, is noindex, or canonicalises elsewhere — error
- `seo/canonical-not-self` Canonical points to a different URL — info
- `seo/head-js-mutation` JavaScript changes canonical, robots, title or description after load — error
- `seo/h1-count` Page doesn't have exactly one visible <h1> in the server HTML — error
- `seo/h1-title-mismatch` <h1> and <title> share little wording — info
- `seo/heading-skip` Heading levels skip (h2 → h4) or headings are empty — warn
- `seo/img-alt-missing` <img> without an alt attribute — error
- `seo/img-alt-quality` Alt text is a filename, a generic word, or overly long — warn
- `seo/links-not-crawlable` Navigation uses elements crawlers can't follow — error
- `seo/link-text` Links with generic or empty text — warn
- `seo/broken-links` Internal links return 4xx/5xx (or go through redirects) — warn
- `seo/trailing-slash` Both trailing-slash and non-slash URLs serve 200 — error
- `seo/hreflang-invalid` hreflang uses invalid codes or relative URLs — error
- `seo/hreflang-not-reciprocal` hreflang set isn't self-referencing and reciprocal — error
- `seo/og-missing` Required Open Graph tags missing (og:title, og:type, og:image, og:url) — error
- `seo/og-incomplete` og:description / og:site_name missing, or og:url ≠ canonical — warn
- `seo/og-image-invalid` og:image isn't a reachable, absolute, raster image within size limits — error
- `seo/og-image-size` og:image is smaller than 1200×630 or far from 1.91:1 — warn
- `seo/og-image-meta` og:image:width/height missing or wrong, or og:image:alt missing — warn
- `seo/twitter-card` twitter:card missing — warn
- `seo/favicon` No crawlable favicon in a Google-supported format — error
- `seo/apple-touch-icon` apple-touch-icon missing or not a 180×180 PNG — warn
- `seo/manifest` Web app manifest missing or incomplete — info
- `seo/theme-color` No theme-color meta — info
- `seo/robots-txt-unreachable` robots.txt returns 5xx/429 or times out — error
- `seo/robots-txt-missing` No robots.txt — warn
- `seo/robots-txt-invalid` robots.txt serves HTML, exceeds 500 KiB, or has unparseable lines — warn
- `seo/robots-blocks-all` robots.txt disallows the site for * / Googlebot / Bingbot — error
- `seo/robots-blocks-resources` robots.txt blocks JS/CSS needed to render — error
- `seo/robots-sitemap-directive` robots.txt has no absolute Sitemap: line — warn
- `seo/sitemap-missing` No reachable sitemap — error
- `seo/sitemap-invalid` Sitemap isn't valid sitemap XML or exceeds protocol limits — error
- `seo/sitemap-url-invalid` Sitemap <loc> is relative or on another host — error
- `seo/sitemap-lastmod` <lastmod> invalid, in the future, or identical across entries — warn
- `seo/sitemap-url-status` Sitemap lists URLs that don't return 200 directly — error
- `seo/sitemap-url-not-indexable` Sitemap lists noindex, robots-blocked or non-canonical URLs — error
- `seo/duplicate-title` Multiple pages share the same <title> — warn
- `seo/duplicate-description` Multiple pages share the same meta description — warn

## AI search 41

`void geo`

Generative-engine optimisation: whether AI crawlers that don't run JavaScript see the same content, valid and visible JSON-LD, an explicit AI robots policy, llms.txt, Markdown mirrors and answer-first writing.

- `geo/js-dependency` Main content only appears after JavaScript runs — error
- `geo/hidden-streamed-content` Crawlers receive main content only inside hidden streaming containers — warn
- `geo/ai-bot-blocked` AI crawler user agents get 401/403/429/503 or a challenge page — error
- `geo/ai-content-differs` AI crawlers get materially different HTML than browsers — warn
- `geo/jsonld-parse-error` JSON-LD block is not valid JSON — error
- `geo/jsonld-unsafe` JSON-LD contains a literal </script or <!-- — error
- `geo/jsonld-missing` Page has no JSON-LD — info
- `geo/jsonld-not-in-raw` JSON-LD is injected by JavaScript — error
- `geo/home-entity-missing` Home page lacks Organization + a single WebSite node — warn
- `geo/jsonld-schema` JSON-LD @context isn't schema.org or @type isn't a schema.org type — error
- `geo/jsonld-required` Structured data type is missing Google-required properties — error
- `geo/jsonld-recommended` Structured data missing recommended properties — info
- `geo/jsonld-dates` Structured data dates are invalid, lack a timezone, or modified < published — warn
- `geo/breadcrumb-invalid` BreadcrumbList positions, names or item URLs are invalid — warn
- `geo/faq-deprecated` FAQPage/HowTo/SearchAction markup no longer produces rich results — info
- `geo/jsonld-id-unresolved` JSON-LD @id references don't resolve — warn
- `geo/jsonld-not-visible` Structured data values don't appear on the page — warn
- `geo/entity-name-mismatch` Brand name differs across og:site_name, JSON-LD and title — info
- `geo/article-date-missing` Article has no machine-readable or visible date — warn
- `geo/article-author-missing` Article author missing or malformed — warn
- `geo/landmarks` Missing <main>, <nav> or <article> landmarks — warn
- `geo/thin-content` Article main content under 150 words — info
- `geo/answer-first` Opening paragraph doesn't state the answer concisely — info
- `geo/no-citations` Long content with no outbound links to sources — info
- `geo/no-statistics` Long content with no quantified facts — info
- `geo/unstructured-content` Long content with no lists, tables or subheadings — info
- `geo/wall-of-text` Paragraphs longer than 150 words — info
- `geo/vague-headings` Generic section headings ("Overview", "More", "Details") — info
- `geo/markdown-alternate` No working Markdown alternate for this content page — info
- `geo/markdown-mirror-indexable` Markdown mirror can compete with the HTML page, or diverges from it — warn
- `geo/markdown-negotiation-vary` Accept: text/markdown negotiation without Vary: Accept — warn
- `geo/markdown-negotiation` No Markdown response for Accept: text/markdown — info
- `geo/llms-txt-missing` No /llms.txt — info
- `geo/llms-txt-error` /llms.txt returns a server error — error
- `geo/llms-txt-invalid` /llms.txt doesn't follow the llms.txt format — warn
- `geo/llms-txt-broken-links` /llms.txt links to URLs that fail — warn
- `geo/llms-txt-not-linked` llms.txt exists but isn't linked from the home page — info
- `geo/robots-ai-policy` AI crawler policy (allow/deny matrix from robots.txt) — info
- `geo/search-bots-blocked` robots.txt blocks AI search or user-triggered fetchers — error
- `geo/training-bots-blocked` robots.txt blocks AI training crawlers — info
- `geo/content-signal` No Content-Signal usage preferences — info

## Design 19

`void design`

Measured design heuristics from computed styles: font families, type scale, line length, palette and accent sprawl, spacing grid, radius sprawl, dark mode and the tells of generated UI.

- `design/too-many-font-families` Too many font families — warn
- `design/no-type-scale` Too many distinct font sizes (no type scale) — warn
- `design/line-length-long` Body text lines are too long — warn
- `design/line-length-short` Body text lines are very short on desktop — info
- `design/body-text-small` Body text is smaller than 15px on mobile — warn
- `design/body-line-height-tight` Body line-height is too tight — warn
- `design/heading-tracking` Large headings use default letter-spacing — info
- `design/heading-no-balance` Headings do not use text-wrap: balance — info
- `design/palette-sprawl` Too many distinct colors — warn
- `design/multiple-accents` More than one accent hue — warn
- `design/gradient-text` Gradient-filled heading text — info
- `design/purple-gradient` Purple/indigo gradient background — info
- `design/emoji-icons` Emoji used as icons — warn
- `design/spacing-off-grid` Spacing values are off the 4px grid — info
- `design/radius-sprawl` Too many different corner radii — info
- `design/no-dark-mode` No dark mode support — info
- `design/color-scheme-missing` color-scheme is not declared — info
- `design/selection-unstyled` ::selection is not styled — info
- `design/horizontal-overflow` Page scrolls horizontally at 375px — warn

## Lint 67

`void lint`

Static source checks with no browser: Tailwind v4 silent failures, Next.js 16 API traps, React render-body bugs, accessibility markup, SEO files and motion hygiene.

- `lint/tw-unknown-class` Class looks like a Tailwind utility but compiles to no CSS — error
- `lint/tw-v3-arbitrary-var` `utility-[--var]` is Tailwind v3 syntax; v4 needs `utility-(--var)` — error
- `lint/tw-js-config-ignored` `tailwind.config.*` exists but the CSS entry has no `@config` — warn
- `lint/tw-dark-variant-os` `dark:` follows the OS, but the app toggles themes with a class/attribute — warn
- `lint/tw-v3-renamed` Utility whose meaning changed between Tailwind v3 and v4 — info
- `lint/tw-important-soup` Many `!important` utilities in one file — warn
- `lint/tw-arbitrary-sprawl` Many arbitrary colors / pixel values bypass the design tokens — info
- `lint/page-level-use-client` `"use client"` at the top of a page or layout — warn
- `lint/heavy-import` Heavy dependency imported into client code — warn
- `lint/img-raw` Raw `<img>` in a Next.js app — warn
- `lint/img-missing-dimensions` Image without width/height (or fill / aspect-ratio) — warn
- `lint/image-priority-deprecated` `priority` on `next/image` is deprecated in Next 16 — warn
- `lint/missing-alt` Image without `alt` — error
- `lint/google-fonts-link` Google Fonts loaded via `<link>` / `@import` — warn
- `lint/font-display` `@font-face` without `font-display` — warn
- `lint/too-many-font-families` More than 3 font families loaded — warn
- `lint/script-strategy` Third-party script loads too early — warn
- `lint/browser-global-in-render` Browser global (`window`, `document`, `navigator`, `localStorage`) at module scope or during render — error
- `lint/timer-in-render` `setTimeout` / `setInterval` called in a component body — error
- `lint/motionvalue-subscribe-in-render` MotionValue `.on("change")` subscription in a component body — error
- `lint/webgl-in-map` WebGL canvas rendered inside `.map()` — error
- `lint/raf-without-cancel` `requestAnimationFrame` loop in an effect without `cancelAnimationFrame` — warn
- `lint/listener-without-cleanup` Event listener or interval added in an effect without cleanup — warn
- `lint/scroll-listener-nonpassive` `wheel` / `touchstart` / `touchmove` listener without `{ passive: true }` — warn
- `lint/unload-listener` `unload` event listener — warn
- `lint/useeffect-fetch` Page fetches its data in `useEffect` — warn
- `lint/div-button` Clickable `<div>`/`<span>` without button semantics — warn
- `lint/dangerously-set-jsonld-unescaped` JSON-LD injected with `JSON.stringify` without escaping `<` — error
- `lint/transition-all` `transition: all` / `transition-all` — warn
- `lint/animate-layout-prop` Animation of layout properties (width/height/top/left/margin/padding) — warn
- `lint/outline-none-no-replacement` Focus outline removed without a visible replacement — warn
- `lint/scale-zero-entry` Element enters from `scale(0)` — warn
- `lint/scroll-timeline-no-fallback` Base rule hides an element; only an `@supports (animation-timeline: …)` block undoes it — warn
- `lint/ease-in-enter` `ease-in` used for an entrance or hover — info
- `lint/long-ui-duration` UI feedback transition longer than 500ms — info
- `lint/no-reduced-motion` Animations present but no `prefers-reduced-motion` handling anywhere — warn
- `lint/overflow-x-hidden-sticky` `overflow-x: hidden` on html/body/root wrapper breaks `position: sticky` — warn
- `lint/vh-hero` Hero sized with `100vh` / `h-screen` — info
- `lint/will-change-static` `will-change` applied statically to many elements — warn
- `lint/z-index-soup` Many distinct z-index values — info
- `lint/hardcoded-colors` Hard-coded colors in components instead of tokens — warn
- `lint/missing-metadata` Page has no `metadata` / `generateMetadata` and inherits only the root title — warn
- `lint/missing-metadata-base` Root layout metadata has no `metadataBase` — error
- `lint/canonical-in-root-layout` Canonical URL set in the root layout — error
- `lint/og-merge-drops-parent` Page `openGraph` replaces the layout's (shallow merge drops images/siteName) — warn
- `lint/not-found-in-suspense` `notFound()`/`redirect()` inside a component rendered under `<Suspense>` — warn
- `lint/missing-sitemap` No sitemap — warn
- `lint/missing-robots` No robots.txt — warn
- `lint/missing-og-image` No Open Graph image anywhere — warn
- `lint/missing-icon` No favicon / app icon — warn
- `lint/missing-llms-txt` No `/llms.txt` — info
- `lint/dynamic-metadata-streaming` Request-time `generateMetadata` without `htmlLimitedBots` — warn
- `lint/missing-lang` Root `<html>` without `lang` — error
- `lint/jsonld-missing` No JSON-LD structured data — info
- `lint/sitemap-lastmod-now` Sitemap uses `new Date()` for `lastModified` — warn
- `lint/multiple-h1` More than one `<h1>` in a route's page + layouts — warn
- `lint/jsonld-deprecated-type` JSON-LD type that no longer produces rich results — info
- `lint/route-js-budget` Route first-load JS over budget (from `.next` build output) — warn
- `lint/animate-undefined` `animate-*` class with no matching `--animate-*` theme variable or `@keyframes` — error
- `lint/random-in-render` `Math.random()` / `Date.now()` / `new Date()` evaluated in a component render body — error
- `lint/duplicate-children-not-hidden` Children repeated for a marquee/loop without `aria-hidden` or `inert` on the copies — warn
- `lint/conditional-hook` Hook called inside a JSX expression, `&&`, ternary or callback — error
- `lint/svg-global-id` Hardcoded `id` on an SVG `<filter>`, gradient, `<clipPath>` or `<mask>` inside a reusable component — warn
- `lint/allocation-per-frame` `new Intl.NumberFormat` / `Intl.NumberFormat(` / `new Color(` inside an animation-frame callback — warn
- `lint/setstate-per-pointer-move` React state set inside a `mousemove` / `pointermove` / `scroll` handler — warn
- `lint/hardcoded-device-pixel-ratio` Canvas/WebGL renders with a literal `devicePixelRatio: 2` (or `width * 2`) — info
- `lint/button-missing-type` `<button>` (or `as="button"` default) without an explicit `type` — info
