Skip to content

Rules · lint/*

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.

$ void lint67 rules14 error · 42 warn · 11 infoupdated 16 Sept 2026
  1. lint/tw-unknown-classerrorClass looks like a Tailwind utility but compiles to no CSSTailwind v4 never errors on unknown classes: `bg-primary` without `--color-primary`, `rounded-card` without `--radius-card`, or `ease-smooth` without `--ease-smooth` silently render nothing. The component looks almost right, so the bug ships. void compiles your real CSS entry and checks every class string against it.
  2. lint/tw-v3-arbitrary-varerror`utility-[--var]` is Tailwind v3 syntax; v4 needs `utility-(--var)`In v4 square brackets are literal arbitrary values, so `duration-[--duration-fast]` emits `transition-duration: --duration-fast` — invalid CSS that the browser drops. The transition, color or size silently falls back to the default.
  3. lint/tw-js-config-ignoredwarn`tailwind.config.*` exists but the CSS entry has no `@config`Tailwind v4 is configured in CSS. A JavaScript config is only loaded when a stylesheet references it with `@config`; otherwise its theme, plugins and `darkMode` are ignored and every class it defined produces no CSS.
  4. lint/tw-dark-variant-oswarn`dark:` follows the OS, but the app toggles themes with a class/attributeBy default v4's `dark:` variant is `@media (prefers-color-scheme: dark)`. If a theme toggle (next-themes, `classList.toggle('dark')`, `data-theme`) is used, `dark:` utilities ignore it: users pick light mode and still get dark styles (or the reverse).
  5. lint/tw-v3-renamedinfoUtility whose meaning changed between Tailwind v3 and v4v4 shifted the scales: v3 `shadow-sm` is v4 `shadow-xs`, `shadow` → `shadow-sm`, `rounded-sm` → `rounded-xs`, `blur-sm` → `blur-xs`, `ring` is 1px instead of 3px, and `outline-none` became `outline-hidden`. Code pasted from v3 docs or registries renders one step off without any error.
  6. lint/tw-important-soupwarnMany `!important` utilities in one fileStacking `!` modifiers means the cascade is being fought rather than designed. It breaks variants (hover/dark can no longer override), makes components unthemeable and usually hides a conflicting global style.
  7. lint/tw-arbitrary-sprawlinfoMany arbitrary colors / pixel values bypass the design tokens`text-[#a1a1aa]`, `p-[13px]` and `rounded-[7px]` scattered through components drift from the token scale: dark mode can't retheme them, spacing stops aligning to the grid and the palette grows one-off shades.
  8. lint/page-level-use-clientwarn`"use client"` at the top of a page or layoutMarking a route file as a client component ships the whole route tree (and every import) as JavaScript, disables server-only data fetching and `metadata` exports, and delays text LCP until hydration. It is the single biggest first-load JS regression in App Router apps.
  9. lint/heavy-importwarnHeavy dependency imported into client codeLibraries like `moment` (~70 KB gz), whole `lodash`, syntax highlighters, markdown parsers, full `motion` components across many files, or three.js/GSAP in a root layout add tens of KB to every page's first-load JS and parse/compile time on mobile.
  10. lint/img-rawwarnRaw `<img>` in a Next.js app`<img>` skips Next's image pipeline: no responsive `srcset`, no AVIF/WebP, no lazy loading by default and no intrinsic size reservation, so it is usually the largest LCP and CLS cost on the page.
  11. lint/img-missing-dimensionswarnImage without width/height (or fill / aspect-ratio)The browser can't reserve space for an unsized image, so content jumps when it loads (CLS). `next/image` with a string `src` and no dimensions fails at runtime.
  12. lint/image-priority-deprecatedwarn`priority` on `next/image` is deprecated in Next 16Next 16 replaced `priority` with `preload`; the docs recommend `loading="eager"` or `fetchPriority="high"` for the LCP image in most cases. Deprecated props are removed in later majors and agents copy them forward.
  13. lint/missing-alterrorImage without `alt`Screen readers announce the file name (or nothing), image search can't index it and Lighthouse/axe fail the page. `next/image` also warns at runtime.
  14. lint/google-fonts-linkwarnGoogle Fonts loaded via `<link>` / `@import`A stylesheet from fonts.googleapis.com is render-blocking, costs two extra origins (DNS+TLS) before text can paint, can't be preloaded reliably and causes font-swap CLS. It also leaks visitor IPs to a third party.
  15. lint/font-displaywarn`@font-face` without `font-display`Without `font-display` browsers hide text for up to 3 seconds while the font downloads (FOIT), delaying text LCP.
  16. lint/too-many-font-familieswarnMore than 3 font families loadedEach family is at least one more font request (often 20–50 KB each), and more than two or three typefaces reads as unfocused design. Font bytes compete with the LCP image.
  17. lint/script-strategywarnThird-party script loads too earlyAnalytics, chat and tag-manager scripts loaded with the default `afterInteractive` (or as a blocking `<script src>`) compete with hydration and inflate TBT/INP. `beforeInteractive` only works in the root layout.
  18. lint/browser-global-in-rendererrorBrowser global (`window`, `document`, `navigator`, `localStorage`) at module scope or during renderClient components are still rendered on the server. Touching browser globals at import time or in the render body crashes SSR (`window is not defined`) or renders different markup on server and client (hydration mismatch, e.g. ⌘ vs Ctrl labels from `navigator.platform`).
  19. lint/timer-in-rendererror`setTimeout` / `setInterval` called in a component bodyThe render body runs on every render (and twice in Strict Mode), so each render schedules another timer that is never cleared: leaks, duplicated state updates and render storms.
  20. lint/motionvalue-subscribe-in-rendererrorMotionValue `.on("change")` subscription in a component bodySubscribing during render adds a new listener on every render and never unsubscribes, so callbacks multiply and memory grows while the animation runs.
  21. lint/webgl-in-maperrorWebGL canvas rendered inside `.map()`Browsers cap live WebGL contexts at roughly 8–16 per page and silently evict the oldest one, so lists of shader avatars/cards go blank. Each context also costs GPU memory and a render loop.
  22. lint/raf-without-cancelwarn`requestAnimationFrame` loop in an effect without `cancelAnimationFrame`The loop keeps running after unmount (and duplicates on remount in Strict Mode), burning CPU/battery and calling setState on unmounted components.
  23. lint/listener-without-cleanupwarnEvent listener or interval added in an effect without cleanupListeners/intervals added in `useEffect` without removal stack up on every re-run and remount: duplicated handlers, leaked components and degraded INP over a session.
  24. lint/scroll-listener-nonpassivewarn`wheel` / `touchstart` / `touchmove` listener without `{ passive: true }`Non-passive wheel/touch listeners force the browser to wait for JavaScript before scrolling, which causes visible scroll jank on mobile.
  25. lint/unload-listenerwarn`unload` event listenerAn `unload` handler makes the page ineligible for the back/forward cache in most browsers, so every back navigation reloads the page instead of restoring it instantly. The event is also unreliable on mobile.
  26. lint/useeffect-fetchwarnPage fetches its data in `useEffect`Fetching on mount in a client page renders an empty shell first, then waterfalls the request after JS downloads and hydrates: slower LCP, layout shift when data arrives, and crawlers see no content.
  27. lint/div-buttonwarnClickable `<div>`/`<span>` without button semanticsA `div` with `onClick` is invisible to keyboards and screen readers: it can't be focused with Tab, doesn't fire on Enter/Space and isn't announced as a button.
  28. lint/dangerously-set-jsonld-unescapederrorJSON-LD injected with `JSON.stringify` without escaping `<``JSON.stringify` doesn't escape `<`, so any string containing `</script>` (a user name, a CMS title) closes the script tag and injects HTML — a stored XSS vector — or breaks the structured data.
  29. lint/transition-allwarn`transition: all` / `transition-all`Transitioning `all` animates properties you never meant to (layout, colors on theme switch, box-shadow), triggers layout/paint work on every change and makes motion feel mushy.
  30. lint/animate-layout-propwarnAnimation of layout properties (width/height/top/left/margin/padding)Animating layout properties forces layout + paint on every frame on the main thread, so the animation drops frames under load and can shift surrounding content.
  31. lint/outline-none-no-replacementwarnFocus outline removed without a visible replacement`outline: none` / `outline-none` hides the keyboard focus indicator. Keyboard users lose track of where they are, and it fails WCAG 2.4.7 (Focus Visible).
  32. lint/scale-zero-entrywarnElement enters from `scale(0)`Nothing in the physical world appears from zero size; `scale(0)` entrances look like a pop and draw the eye too hard. Starting from ~0.95 with opacity reads as the element arriving.
  33. lint/scroll-timeline-no-fallbackwarnBase rule hides an element; only an `@supports (animation-timeline: …)` block undoes it`animation-timeline: view()/scroll()`, `view-timeline` and `animation-range` have no fallback: a browser that doesn't implement them (Firefox, at every version through 151) evaluates the `@supports` condition as false and drops the whole block, keyframes included. If the resting rule for the same selector sets `opacity: 0`, `visibility: hidden`, a zero scale or a large translate and nothing outside that `@supports` block ever undoes it, the element is invisible forever in that browser — not degraded, gone.
  34. lint/ease-in-enterinfo`ease-in` used for an entrance or hover`ease-in` starts slowly, so the UI feels laggy right when the user expects a response. Entrances and interactions should start fast and settle (ease-out).
  35. lint/long-ui-durationinfoUI feedback transition longer than 500msHover, press and focus feedback over ~300ms feels sluggish; above 500ms users perceive the interface as slow and repeated interactions queue up.
  36. lint/no-reduced-motionwarnAnimations present but no `prefers-reduced-motion` handling anywherePeople with vestibular disorders can get nauseous from motion; the OS setting exists for them. Without handling it, all entrances, parallax and ambient loops still run.
  37. lint/overflow-x-hidden-stickywarn`overflow-x: hidden` on html/body/root wrapper breaks `position: sticky``overflow-x: hidden` turns the element into a scroll container, so every `sticky` descendant sticks to it instead of the viewport — sticky headers and TOCs silently stop sticking.
  38. lint/vh-heroinfoHero sized with `100vh` / `h-screen`On mobile `100vh` is the largest viewport (URL bar hidden), so full-height heroes are cut off behind the browser UI and jump when the bar collapses.
  39. lint/will-change-staticwarn`will-change` applied statically to many elementsEach `will-change` layer costs GPU memory; applied statically (or to html/body) it can make rendering slower, blur text and exhaust memory on low-end phones.
  40. lint/z-index-soupinfoMany distinct z-index valuesA dozen ad-hoc z-index values (10, 20, 50, 999, 9999…) means stacking is managed by escalation; the next overlay needs a bigger number and popovers end up under headers.
  41. lint/hardcoded-colorswarnHard-coded colors in components instead of tokensRaw hex/rgb values and default palette classes (`bg-indigo-500`) in components bypass the semantic tokens, so dark mode, theming and contrast fixes have to be done file by file and the palette drifts.
  42. lint/missing-metadatawarnPage has no `metadata` / `generateMetadata` and inherits only the root titleWithout page-level metadata every route shares the root layout's title and description: duplicate titles in search results, weak link previews, and AI answers can't tell pages apart.
  43. lint/missing-metadata-baseerrorRoot layout metadata has no `metadataBase`Relative URLs in `openGraph.images`, `alternates.canonical` and file-based `opengraph-image` resolve against `metadataBase`. Without it Next falls back to localhost/VERCEL_URL, so previews and canonicals point to the wrong host.
  44. lint/canonical-in-root-layouterrorCanonical URL set in the root layoutMetadata is inherited, so a canonical in the root layout makes every page that doesn't override it declare itself a duplicate of that URL. Google then drops those pages from the index.
  45. lint/og-merge-drops-parentwarnPage `openGraph` replaces the layout's (shallow merge drops images/siteName)Next merges metadata shallowly: when a page sets `openGraph`, the parent's whole `openGraph` object is replaced. Images, `siteName`, `locale` defined in the layout silently disappear from that page's link previews. Same for `alternates` (`types`, `languages`).
  46. lint/not-found-in-suspensewarn`notFound()`/`redirect()` inside a component rendered under `<Suspense>`Once streaming starts the HTTP status is already sent. `notFound()` thrown inside a Suspense boundary returns 200 with a noindex meta (a soft 404), and `redirect()` becomes a client-side redirect crawlers may not follow.
  47. lint/missing-sitemapwarnNo sitemapWithout a sitemap crawlers discover pages only through links, new or deep pages are indexed slowly and `lastmod` hints for recrawling are lost.
  48. lint/missing-robotswarnNo robots.txtWithout robots.txt crawlers get a 404, you can't point them at the sitemap, and there is no explicit policy for AI crawlers (GPTBot, ClaudeBot, PerplexityBot).
  49. lint/missing-og-imagewarnNo Open Graph image anywhereLinks shared in Slack, X, LinkedIn and iMessage render as a bare URL without an image, which sharply lowers click-through.
  50. lint/missing-iconwarnNo favicon / app iconBrowsers request /favicon.ico anyway (a 404 on every page), tabs and bookmarks show a blank icon and search results show a generic globe. An SVG-only icon isn't supported by Safari and some crawlers.
  51. lint/missing-llms-txtinfoNo `/llms.txt`llms.txt is an emerging convention that gives AI agents a curated Markdown map of the site. It's cheap to add and helps coding agents and answer engines find the right pages.
  52. lint/dynamic-metadata-streamingwarnRequest-time `generateMetadata` without `htmlLimitedBots`Since Next 15.2 metadata of dynamic routes is streamed into `<body>`. Next's default bot list doesn't include AI crawlers (GPTBot, ClaudeBot, PerplexityBot) or plain Googlebot, so they receive a `<head>` without title/canonical.
  53. lint/missing-langerrorRoot `<html>` without `lang`Screen readers pick the wrong pronunciation, browsers offer bogus translation, and search engines have to guess the page language.
  54. lint/jsonld-missinginfoNo JSON-LD structured dataStructured data (Organization, WebSite, Article, Product, BreadcrumbList) makes pages eligible for rich results and gives search and AI systems unambiguous facts about the entity behind the site.
  55. lint/sitemap-lastmod-nowwarnSitemap uses `new Date()` for `lastModified`A lastmod that changes on every build tells crawlers every page changed every time. Google learns to ignore the site's lastmod entirely, so real updates stop getting priority recrawls.
  56. lint/multiple-h1warnMore than one `<h1>` in a route's page + layoutsMultiple h1 elements blur the page topic for search engines and make heading navigation confusing for screen-reader users.
  57. lint/jsonld-deprecated-typeinfoJSON-LD type that no longer produces rich resultsGoogle retired FAQPage rich results for most sites (2023), HowTo (2023) and the sitelinks SearchAction box (2024-11). The markup is harmless but wasted bytes and misleads agents into thinking it helps.
  58. lint/route-js-budgetwarnRoute first-load JS over budget (from `.next` build output)First-load JS is downloaded, parsed and executed before the page becomes interactive. Above ~170 KB gzip on marketing routes, mid-range phones on slow networks see multi-second TBT/INP; the Next 16 framework alone is ~133 KB.
  59. lint/animate-undefinederror`animate-*` class with no matching `--animate-*` theme variable or `@keyframes`Registry components (Aceternity spotlight, React Bits StarBorder, meteors) ship `animate-spotlight`-style classes whose keyframes lived in a v3 `tailwind.config.js` or a comment. On Tailwind v4 the class compiles to nothing; when the base state is `opacity-0` the element is invisible forever, with no error.
  60. lint/random-in-rendererror`Math.random()` / `Date.now()` / `new Date()` evaluated in a component render bodyServer and client render different values, so React reports a hydration mismatch and patches the DOM (Aceternity background-beams randomises durations and SVG coordinates this way). Every re-render also re-randomises, restarting animations.
  61. lint/duplicate-children-not-hiddenwarnChildren repeated for a marquee/loop without `aria-hidden` or `inert` on the copies`Array(repeat).fill(0).map(() => <div>{children}</div>)` (Magic UI marquee, Skiper TextRoll) makes screen readers announce the content N times and adds N× tab stops for every link inside. Crawlers also see duplicated text.
  62. lint/conditional-hookerrorHook called inside a JSX expression, `&&`, ternary or callback`{mode === 'gradient' && <motion.div style={{ background: useMotionTemplate`…` }} />}` (Magic UI magic-card) or `style={{ y: useTransform(…) }}` (Skiper19) changes hook order when the condition flips; React throws "Rendered more hooks than during the previous render".
  63. lint/svg-global-idwarnHardcoded `id` on an SVG `<filter>`, gradient, `<clipPath>` or `<mask>` inside a reusable componentIds are document-global. Two instances (or any other SVG using `#filter`/`#gradient`) resolve `url(#…)` to whichever element came first, so the second instance silently renders with the wrong filter or none (Aceternity spotlight uses `id="filter"`).
  64. lint/allocation-per-framewarn`new Intl.NumberFormat` / `Intl.NumberFormat(` / `new Color(` inside an animation-frame callbackNumber tickers (Magic UI NumberTicker, React Bits CountUp) build a formatter on every frame of a spring; WebGL backgrounds (React Bits Aurora) allocate colours per frame. Formatter construction costs ~50–100× a `format()` call and the garbage causes GC pauses mid-animation.
  65. lint/setstate-per-pointer-movewarnReact state set inside a `mousemove` / `pointermove` / `scroll` handlerReact Bits Magnet and SpotlightCard call `setPosition` on every pointer event (and Magnet attaches one window listener per instance), so the component and its children re-render up to 120×/s and INP degrades with the number of instances on the page.
  66. lint/hardcoded-device-pixel-ratioinfoCanvas/WebGL renders with a literal `devicePixelRatio: 2` (or `width * 2`)Magic UI globe hardcodes DPR 2: on DPR-1 screens that is 4× the pixels to shade every frame, and on DPR-3 phones it is blurry. GPU cost scales with pixel count.
  67. lint/button-missing-typeinfo`<button>` (or `as="button"` default) without an explicit `type`A button's default type is `submit`. Decorative or toggle buttons copied from registries (React Bits StarBorder) submit the enclosing form when clicked or when Enter is pressed in a field.