---
title: "Lint rules (lint/*)"
description: "All 67 lint rules void reports, each with severity, why it matters and how to fix it."
canonical: https://void-design.vercel.app/rules/lint
lastModified: 2026-09-16
---

# Lint rules

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. Run `void lint` or `void audit`.

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