Rules · 273 checks
Every check void can report
273 rules across 7 categories. Each finding the CLI prints carries one of these ids and links here. In a terminal, void rules <id> prints the same page.
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-slowLargest Contentful Paint is slowperf/cls-highCumulative Layout Shift is highperf/ttfb-slowServer response (TTFB) is slowperf/fcp-slowFirst Contentful Paint is slowperf/tbt-highMain thread is blocked by long tasks during loadperf/js-budgetFirst-load JavaScript exceeds the budgetperf/css-budgetCSS transfer exceeds the budgetperf/font-budgetWeb font transfer exceeds the budgetperf/too-many-fontsToo many font files are downloadedperf/image-budgetImage transfer exceeds the budgetperf/too-many-requestsPage makes many requestsperf/lcp-image-lazyLCP image is lazy-loadedperf/lcp-image-no-priorityLCP image has no fetchpriority="high"perf/lcp-background-imageLCP element is a CSS background imageperf/image-oversizedImage is much larger than its rendered sizeperf/image-legacy-formatImage served in a legacy formatperf/images-missing-dimensionsImages without width/height or aspect-ratioperf/render-blockingRender-blocking resources delay first paintperf/no-text-compressionText resources are served uncompressedperf/cache-headersHashed static assets are not cached long-termperf/third-party-heavyHeavy third-party footprintperf/dom-sizeDOM is very largeperf/bfcache-blockedPage is not eligible for the back/forward cacheperf/lighthouse-score-lowLighthouse performance score is lowperf/lighthouse-auditLighthouse flagged an opportunity
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-jankFrames are dropped while scrollingsmooth/long-frames-during-scrollLong animation frames block scrollingsmooth/scroll-listener-nonpassiveNon-passive wheel/touch listenersmooth/inp-slowInteractions are slow to respond (INP)smooth/layout-shift-after-inputLayout shifts long after an interactionsmooth/layout-shift-on-scrollLayout shifts while scrollingsmooth/animate-layout-propertyAnimation of a layout-triggering propertysmooth/transition-alltransition: allsmooth/will-change-overusewill-change is applied too broadlysmooth/reduced-motion-ignoredAnimations keep running with prefers-reduced-motionsmooth/raf-loop-idlerequestAnimationFrame loop runs while idlesmooth/scroll-timeline-no-fallbackContent is invisible without scroll-driven-animation supportsmooth/multiple-webgl-contextsMultiple WebGL contexts
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-contrastText has insufficient color contrasta11y/image-altImage has no text alternativea11y/button-nameButton has no accessible namea11y/link-nameLink has no accessible namea11y/labelForm field has no labela11y/select-nameSelect element has no accessible namea11y/input-button-nameInput button has no accessible namea11y/input-image-altImage button has no alt texta11y/html-has-lang<html> has no lang attributea11y/html-lang-valid<html lang> is not a valid language taga11y/document-titlePage has no <title>a11y/landmark-one-mainPage has no <main> landmarka11y/regionContent is outside landmark regionsa11y/landmark-uniqueLandmarks are not uniquely identifiablea11y/page-has-heading-onePage has no level-one headinga11y/heading-orderHeading levels skipa11y/empty-headingHeading is emptya11y/listList contains invalid childrena11y/listitem<li> is not inside a lista11y/aria-allowed-attrARIA attribute is not allowed on this rolea11y/aria-required-attrRequired ARIA attribute is missinga11y/aria-required-childrenARIA role is missing required childrena11y/aria-required-parentARIA role is missing its required parenta11y/aria-rolesInvalid ARIA rolea11y/aria-valid-attrInvalid ARIA attribute namea11y/aria-valid-attr-valueInvalid ARIA attribute valuea11y/aria-hidden-focusFocusable element inside aria-hiddena11y/aria-hidden-bodyaria-hidden on <body>a11y/aria-command-nameARIA button/link/menuitem has no accessible namea11y/aria-input-field-nameARIA input field has no accessible namea11y/aria-toggle-field-nameARIA toggle has no accessible namea11y/aria-prohibited-attrARIA attribute is prohibited for this rolea11y/nested-interactiveInteractive controls are nesteda11y/duplicate-id-ariaDuplicate id referenced by ARIAa11y/frame-title<iframe> has no titlea11y/svg-img-altSVG image has no text alternativea11y/role-img-altrole="img" element has no text alternativea11y/tabindexPositive tabindexa11y/scrollable-region-focusableScrollable region is not keyboard accessiblea11y/link-in-text-blockLinks in text are distinguished only by colora11y/autocomplete-validInvalid autocomplete valuea11y/meta-viewportZooming is disableda11y/bypassNo way to skip repeated contenta11y/video-captionVideo has no captionsa11y/axe-otherOther axe-core accessibility violationa11y/target-sizeTouch target is smaller than 24×24 pxa11y/focus-not-visibleKeyboard focus is not visiblea11y/reflow-overflowContent overflows horizontally at 320px
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-statusPage does not return HTTP 200seo/redirect-chainURL reaches its content through more than one redirectseo/redirect-temporaryTemporary redirect (302/303/307) usedseo/client-redirectRedirect done with meta refresh or JavaScriptseo/noindexIndexable page carries noindexseo/robots-snippet-restrictednosnippet or a very low max-snippet limits previewsseo/soft-404Missing page returns HTTP 200 (soft 404)seo/html-too-largeUncompressed HTML is over Googlebot's 2 MB fetch limitseo/rsc-payload-ratioInline RSC flight payload dominates the HTMLseo/not-httpsPage served over plain HTTPseo/origin-redirectshttp:// or www/apex variant doesn't redirect to the canonical origin in one hopseo/html-lang<html lang> missing or not a valid BCP 47 tagseo/viewportViewport meta missing, not device-width, or blocks zoomseo/charsetCharacter encoding not declared earlyseo/title-missingNo single non-empty <title> in <head>seo/title-lengthTitle length outside 10–60 characters, or keyword-stuffedseo/meta-description-missingNo meta description in <head>seo/meta-description-lengthMeta description length outside 50–160 charactersseo/metadata-in-bodyCrawler receives <title>/canonical/description inside <body> (Next.js streaming metadata)seo/canonical-missingNo single rel=canonical in <head>seo/canonical-invalidCanonical URL is relative or contains a fragmentseo/canonical-brokenCanonical points to a URL that returns 4xx/5xxseo/canonical-conflictCanonical target redirects, is noindex, or canonicalises elsewhereseo/canonical-not-selfCanonical points to a different URLseo/head-js-mutationJavaScript changes canonical, robots, title or description after loadseo/h1-countPage doesn't have exactly one visible <h1> in the server HTMLseo/h1-title-mismatch<h1> and <title> share little wordingseo/heading-skipHeading levels skip (h2 → h4) or headings are emptyseo/img-alt-missing<img> without an alt attributeseo/img-alt-qualityAlt text is a filename, a generic word, or overly longseo/links-not-crawlableNavigation uses elements crawlers can't followseo/link-textLinks with generic or empty textseo/broken-linksInternal links return 4xx/5xx (or go through redirects)seo/trailing-slashBoth trailing-slash and non-slash URLs serve 200seo/hreflang-invalidhreflang uses invalid codes or relative URLsseo/hreflang-not-reciprocalhreflang set isn't self-referencing and reciprocalseo/og-missingRequired Open Graph tags missing (og:title, og:type, og:image, og:url)seo/og-incompleteog:description / og:site_name missing, or og:url ≠ canonicalseo/og-image-invalidog:image isn't a reachable, absolute, raster image within size limitsseo/og-image-sizeog:image is smaller than 1200×630 or far from 1.91:1seo/og-image-metaog:image:width/height missing or wrong, or og:image:alt missingseo/twitter-cardtwitter:card missingseo/faviconNo crawlable favicon in a Google-supported formatseo/apple-touch-iconapple-touch-icon missing or not a 180×180 PNGseo/manifestWeb app manifest missing or incompleteseo/theme-colorNo theme-color metaseo/robots-txt-unreachablerobots.txt returns 5xx/429 or times outseo/robots-txt-missingNo robots.txtseo/robots-txt-invalidrobots.txt serves HTML, exceeds 500 KiB, or has unparseable linesseo/robots-blocks-allrobots.txt disallows the site for * / Googlebot / Bingbotseo/robots-blocks-resourcesrobots.txt blocks JS/CSS needed to renderseo/robots-sitemap-directiverobots.txt has no absolute Sitemap: lineseo/sitemap-missingNo reachable sitemapseo/sitemap-invalidSitemap isn't valid sitemap XML or exceeds protocol limitsseo/sitemap-url-invalidSitemap <loc> is relative or on another hostseo/sitemap-lastmod<lastmod> invalid, in the future, or identical across entriesseo/sitemap-url-statusSitemap lists URLs that don't return 200 directlyseo/sitemap-url-not-indexableSitemap lists noindex, robots-blocked or non-canonical URLsseo/duplicate-titleMultiple pages share the same <title>seo/duplicate-descriptionMultiple pages share the same meta description
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-dependencyMain content only appears after JavaScript runsgeo/hidden-streamed-contentCrawlers receive main content only inside hidden streaming containersgeo/ai-bot-blockedAI crawler user agents get 401/403/429/503 or a challenge pagegeo/ai-content-differsAI crawlers get materially different HTML than browsersgeo/jsonld-parse-errorJSON-LD block is not valid JSONgeo/jsonld-unsafeJSON-LD contains a literal </script or <!--geo/jsonld-missingPage has no JSON-LDgeo/jsonld-not-in-rawJSON-LD is injected by JavaScriptgeo/home-entity-missingHome page lacks Organization + a single WebSite nodegeo/jsonld-schemaJSON-LD @context isn't schema.org or @type isn't a schema.org typegeo/jsonld-requiredStructured data type is missing Google-required propertiesgeo/jsonld-recommendedStructured data missing recommended propertiesgeo/jsonld-datesStructured data dates are invalid, lack a timezone, or modified < publishedgeo/breadcrumb-invalidBreadcrumbList positions, names or item URLs are invalidgeo/faq-deprecatedFAQPage/HowTo/SearchAction markup no longer produces rich resultsgeo/jsonld-id-unresolvedJSON-LD @id references don't resolvegeo/jsonld-not-visibleStructured data values don't appear on the pagegeo/entity-name-mismatchBrand name differs across og:site_name, JSON-LD and titlegeo/article-date-missingArticle has no machine-readable or visible dategeo/article-author-missingArticle author missing or malformedgeo/landmarksMissing <main>, <nav> or <article> landmarksgeo/thin-contentArticle main content under 150 wordsgeo/answer-firstOpening paragraph doesn't state the answer conciselygeo/no-citationsLong content with no outbound links to sourcesgeo/no-statisticsLong content with no quantified factsgeo/unstructured-contentLong content with no lists, tables or subheadingsgeo/wall-of-textParagraphs longer than 150 wordsgeo/vague-headingsGeneric section headings ("Overview", "More", "Details")geo/markdown-alternateNo working Markdown alternate for this content pagegeo/markdown-mirror-indexableMarkdown mirror can compete with the HTML page, or diverges from itgeo/markdown-negotiation-varyAccept: text/markdown negotiation without Vary: Acceptgeo/markdown-negotiationNo Markdown response for Accept: text/markdowngeo/llms-txt-missingNo /llms.txtgeo/llms-txt-error/llms.txt returns a server errorgeo/llms-txt-invalid/llms.txt doesn't follow the llms.txt formatgeo/llms-txt-broken-links/llms.txt links to URLs that failgeo/llms-txt-not-linkedllms.txt exists but isn't linked from the home pagegeo/robots-ai-policyAI crawler policy (allow/deny matrix from robots.txt)geo/search-bots-blockedrobots.txt blocks AI search or user-triggered fetchersgeo/training-bots-blockedrobots.txt blocks AI training crawlersgeo/content-signalNo Content-Signal usage preferences
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-familiesToo many font familiesdesign/no-type-scaleToo many distinct font sizes (no type scale)design/line-length-longBody text lines are too longdesign/line-length-shortBody text lines are very short on desktopdesign/body-text-smallBody text is smaller than 15px on mobiledesign/body-line-height-tightBody line-height is too tightdesign/heading-trackingLarge headings use default letter-spacingdesign/heading-no-balanceHeadings do not use text-wrap: balancedesign/palette-sprawlToo many distinct colorsdesign/multiple-accentsMore than one accent huedesign/gradient-textGradient-filled heading textdesign/purple-gradientPurple/indigo gradient backgrounddesign/emoji-iconsEmoji used as iconsdesign/spacing-off-gridSpacing values are off the 4px griddesign/radius-sprawlToo many different corner radiidesign/no-dark-modeNo dark mode supportdesign/color-scheme-missingcolor-scheme is not declareddesign/selection-unstyled::selection is not styleddesign/horizontal-overflowPage scrolls horizontally at 375px
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-classClass looks like a Tailwind utility but compiles to no CSSlint/tw-v3-arbitrary-var`utility-[--var]` is Tailwind v3 syntax; v4 needs `utility-(--var)`lint/tw-js-config-ignored`tailwind.config.*` exists but the CSS entry has no `@config`lint/tw-dark-variant-os`dark:` follows the OS, but the app toggles themes with a class/attributelint/tw-v3-renamedUtility whose meaning changed between Tailwind v3 and v4lint/tw-important-soupMany `!important` utilities in one filelint/tw-arbitrary-sprawlMany arbitrary colors / pixel values bypass the design tokenslint/page-level-use-client`"use client"` at the top of a page or layoutlint/heavy-importHeavy dependency imported into client codelint/img-rawRaw `<img>` in a Next.js applint/img-missing-dimensionsImage without width/height (or fill / aspect-ratio)lint/image-priority-deprecated`priority` on `next/image` is deprecated in Next 16lint/missing-altImage without `alt`lint/google-fonts-linkGoogle Fonts loaded via `<link>` / `@import`lint/font-display`@font-face` without `font-display`lint/too-many-font-familiesMore than 3 font families loadedlint/script-strategyThird-party script loads too earlylint/browser-global-in-renderBrowser global (`window`, `document`, `navigator`, `localStorage`) at module scope or during renderlint/timer-in-render`setTimeout` / `setInterval` called in a component bodylint/motionvalue-subscribe-in-renderMotionValue `.on("change")` subscription in a component bodylint/webgl-in-mapWebGL canvas rendered inside `.map()`lint/raf-without-cancel`requestAnimationFrame` loop in an effect without `cancelAnimationFrame`lint/listener-without-cleanupEvent listener or interval added in an effect without cleanuplint/scroll-listener-nonpassive`wheel` / `touchstart` / `touchmove` listener without `{ passive: true }`lint/unload-listener`unload` event listenerlint/useeffect-fetchPage fetches its data in `useEffect`lint/div-buttonClickable `<div>`/`<span>` without button semanticslint/dangerously-set-jsonld-unescapedJSON-LD injected with `JSON.stringify` without escaping `<`lint/transition-all`transition: all` / `transition-all`lint/animate-layout-propAnimation of layout properties (width/height/top/left/margin/padding)lint/outline-none-no-replacementFocus outline removed without a visible replacementlint/scale-zero-entryElement enters from `scale(0)`lint/scroll-timeline-no-fallbackBase rule hides an element; only an `@supports (animation-timeline: …)` block undoes itlint/ease-in-enter`ease-in` used for an entrance or hoverlint/long-ui-durationUI feedback transition longer than 500mslint/no-reduced-motionAnimations present but no `prefers-reduced-motion` handling anywherelint/overflow-x-hidden-sticky`overflow-x: hidden` on html/body/root wrapper breaks `position: sticky`lint/vh-heroHero sized with `100vh` / `h-screen`lint/will-change-static`will-change` applied statically to many elementslint/z-index-soupMany distinct z-index valueslint/hardcoded-colorsHard-coded colors in components instead of tokenslint/missing-metadataPage has no `metadata` / `generateMetadata` and inherits only the root titlelint/missing-metadata-baseRoot layout metadata has no `metadataBase`lint/canonical-in-root-layoutCanonical URL set in the root layoutlint/og-merge-drops-parentPage `openGraph` replaces the layout's (shallow merge drops images/siteName)lint/not-found-in-suspense`notFound()`/`redirect()` inside a component rendered under `<Suspense>`lint/missing-sitemapNo sitemaplint/missing-robotsNo robots.txtlint/missing-og-imageNo Open Graph image anywherelint/missing-iconNo favicon / app iconlint/missing-llms-txtNo `/llms.txt`lint/dynamic-metadata-streamingRequest-time `generateMetadata` without `htmlLimitedBots`lint/missing-langRoot `<html>` without `lang`lint/jsonld-missingNo JSON-LD structured datalint/sitemap-lastmod-nowSitemap uses `new Date()` for `lastModified`lint/multiple-h1More than one `<h1>` in a route's page + layoutslint/jsonld-deprecated-typeJSON-LD type that no longer produces rich resultslint/route-js-budgetRoute first-load JS over budget (from `.next` build output)lint/animate-undefined`animate-*` class with no matching `--animate-*` theme variable or `@keyframes`lint/random-in-render`Math.random()` / `Date.now()` / `new Date()` evaluated in a component render bodylint/duplicate-children-not-hiddenChildren repeated for a marquee/loop without `aria-hidden` or `inert` on the copieslint/conditional-hookHook called inside a JSX expression, `&&`, ternary or callbacklint/svg-global-idHardcoded `id` on an SVG `<filter>`, gradient, `<clipPath>` or `<mask>` inside a reusable componentlint/allocation-per-frame`new Intl.NumberFormat` / `Intl.NumberFormat(` / `new Color(` inside an animation-frame callbacklint/setstate-per-pointer-moveReact state set inside a `mousemove` / `pointermove` / `scroll` handlerlint/hardcoded-device-pixel-ratioCanvas/WebGL renders with a literal `devicePixelRatio: 2` (or `width * 2`)lint/button-missing-type`<button>` (or `as="button"` default) without an explicit `type`