The rule dictionary
Every rule has a stable id such as a11y/img-alt, one sentence, a severity and at least one source in a public standard or the vendor's documentation. People, agents, the report and explain all use the same id.
275 rules.
No rule matches these filters.
Accessibility 58 rules
a11y/accordion-headerEach accordion header is a button with aria-expanded inside a heading of the right level; or use details and summary.a11y/aria-hidden-focusablearia-hidden="true" is never on a focusable element or on an element that contains one.a11y/aria-label-genericaria-label and aria-labelledby go on controls, landmarks and named roles, never on a plain div, span or p.a11y/aria-ref-existsIds named in aria-labelledby, aria-describedby and aria-errormessage exist in the page and are unique.a11y/aria-validEvery role value is a real WAI-ARIA role and every aria-* attribute name exists (aria-labelledby, not aria-labeledby).a11y/autocomplete-validAn autocomplete attribute holds only tokens the HTML standard defines (email, given-name, new-password…).a11y/carousel-controlsA carousel has named previous and next buttons, a pause button first in its Tab order when it rotates, and stops on hover and focus.a11y/color-contrastText has a contrast ratio of at least 4.5:1 with its background; large text (24px, or 18.5px bold) at least 3:1.a11y/combobox-keyboardA combobox keeps focus in the input, marks the highlighted option with aria-activedescendant and supports Down, Up, Enter and Escape.a11y/combobox-on-inputrole="combobox" sits on the input itself (ARIA 1.2), not on a wrapper element that contains the input.a11y/control-nameEvery button and link has an accessible name; icon-only controls get visually hidden text or aria-label.a11y/dialog-focusA modal moves focus inside on open, keeps Tab inside, closes on Escape, and returns focus to the control that opened it.a11y/dialog-nameEvery dialog has an accessible name, usually aria-labelledby pointing at its visible heading.a11y/dialog-nativeBuild dialogs on the native <dialog> element opened with showModal() rather than a div with role="dialog".a11y/focus-not-obscuredSticky headers, footers, cookie banners and chat widgets never fully cover the element that has keyboard focus.a11y/focus-route-changeAfter a client-side route change, focus moves to the new page's heading or main region and the new title is announced.a11y/focus-visibleNever remove the focus outline without drawing a visible replacement for keyboard focus.a11y/font-size-relativeThe root font size is not set in pixels, so text follows the reader's default size; use rem for type.a11y/form-errorsAn input error is shown in text next to the field, tied to it with aria-describedby, and says how to fix it.a11y/hover-content-dismissibleTooltips and popups shown on hover or focus close with Escape, stay while the pointer moves onto them, and do not vanish on a timer.a11y/html-langThe html element names the language of the page in a non-empty lang attribute.a11y/iframe-titleEvery visible iframe has a title that says what it contains (map, video, payment form).a11y/img-altEvery img and area element has an alt attribute; a decorative image has alt="".a11y/input-labelEvery form field has a programmatic label (label element, aria-label or aria-labelledby); a placeholder is not a label.a11y/landmarksEach page has one main landmark, all content sits inside landmarks, and repeated landmarks of a kind have distinct labels.a11y/lang-validEvery lang value is a valid BCP 47 language tag ("en", "en-GB", "pl"), never "en_US" or a language name.a11y/link-is-linkAn <a> is a real link with an href; an action is a <button>, never <a> without href, href="#" or javascript:.a11y/media-alternativeRecorded audio has a transcript; recorded video has audio description of what is only shown, or a full text alternative.a11y/menu-keyboardMenus implement their keys - arrows move between items, Enter activates, Escape closes and returns focus to the button.a11y/menu-not-for-site-navSite navigation is a nav with a list of links and disclosure buttons, not role="menu" or role="menubar".a11y/no-autoplay-audioNo audio plays automatically; autoplaying video is muted, and any sound longer than 3 seconds can be paused or stopped.a11y/no-click-on-staticA click handler belongs on a button or link; a static element with one also needs a role, tabindex and a key handler.a11y/no-color-onlyColour is never the only cue; errors, states, links in text and chart series also differ by text, icon, shape or underline.a11y/no-flashNothing flashes more than three times in any one second, unless the flash is small and below the general and red thresholds.a11y/no-keyboard-trapKeyboard focus can always leave a component with Tab, Shift+Tab or Escape; only an open modal keeps focus inside.a11y/no-meta-refreshNo meta refresh reloads or redirects the page after a delay; redirect on the server or let people choose.a11y/non-text-contrastBorders of fields, checkboxes, toggles, focus rings and meaningful icons have at least 3:1 contrast with their neighbours.a11y/option-no-interactiveOptions in a listbox or combobox popup hold only text and images, never links, buttons or fields.a11y/page-titleEvery HTML document has a title element that says what the page is about.a11y/pause-auto-motionAnything that moves, blinks, scrolls or updates by itself for more than 5 seconds has a visible pause or stop control.a11y/popup-button-expandedA control that opens a popup (aria-haspopup or role="combobox") has aria-expanded that follows the open state.a11y/positive-tabindexNo element has a tabindex greater than 0; focus order follows the source order.a11y/prefer-native-elementUse the native element instead of a role it already provides (button, a href, input, h1–h6, nav, main, ul, li).a11y/reflowAt 320 CSS pixels wide (400% zoom) content reflows into one column with no horizontal scrolling, except tables, maps and similar.a11y/scrollable-region-focusableA scrolling region with no focusable content inside can itself take focus (tabindex="0") and has a name.a11y/skip-linkPages with repeated header or navigation start with a "Skip to main content" link whose target exists and takes focus.a11y/state-exposedCustom toggles, disclosures and selections expose their state (aria-expanded, aria-pressed, aria-checked, aria-selected).a11y/status-message-liveMessages that appear without moving focus (saved, results, errors, progress) are announced through a live region.a11y/table-captionA data table has a name, from a caption element or aria-labelledby pointing at the heading above it.a11y/table-headersData tables mark their header cells with th (with scope when needed); layout tables carry role="presentation".a11y/table-sortSortable columns use a button inside the th, and aria-sort only on the header of the column that is sorted.a11y/tabs-keyboardA tab list is one Tab stop; Left and Right Arrow (Up and Down when vertical), Home and End move between tabs.a11y/tabs-selectedEvery role="tab" carries aria-selected, true on the active tab and false on the others.a11y/target-sizePointer targets are at least 24 by 24 CSS pixels, or spaced so a 24px circle on each does not touch another target.a11y/text-spacingText survives line height 1.5, paragraph spacing 2em, letter spacing 0.12em and word spacing 0.16em without being cut off.a11y/toast-timingToasts with actions or information the user needs do not disappear on a timer; others stay long enough and pause on hover and focus.a11y/video-captionsRecorded video with speech or meaningful sound has synchronised captions, for example a <track kind="captions">.a11y/zoom-not-blockedThe viewport never disables zoom (no user-scalable=no, no maximum-scale below 2).
Performance 26 rules
perf/bfcache-no-unloadNo code listens to the unload event; use pagehide or visibilitychange so the page can use the back/forward cache.perf/cache-static-assetsContent-hashed assets are cached for a year as immutable; HTML is revalidated (no-cache), never cached long.perf/code-splitJavaScript is split by route, and features that most visits do not use load on demand with dynamic import().perf/css-no-importShipped CSS does not chain stylesheets with @import; link them in the HTML or combine them in a build.perf/field-data-p75Claims about Core Web Vitals come from field data at the 75th percentile, not from a Lighthouse score.perf/font-displayEvery @font-face and Google Fonts URL sets font-display (swap or optional) so text is visible while the font loads.perf/font-fallback-metricsThe fallback font is adjusted to the web font's metrics (size-adjust and overrides) so the swap does not shift the layout.perf/font-preconnectFonts are self-hosted, or the font CDN's file origin (fonts.gstatic.com) is preconnected with crossorigin.perf/font-preload-crossoriginA font preload has crossorigin (and type font/woff2); without it the preload is wasted and the font is fetched twice.perf/font-woff2Web fonts are served as WOFF2; TTF, OTF, EOT and plain WOFF files are dead weight for current browsers.perf/gif-to-videoAnimations longer than a small icon are shipped as a muted, looping MP4 or WebM video, not as a GIF over 100 KiB.perf/img-dimensionsEvery img has width and height attributes (or a CSS aspect ratio) so the browser reserves its space.perf/img-modern-formatPhotos and large raster images are served as AVIF or WebP at the size they are shown, not as large JPEG or PNG files.perf/large-imageAn image file in the source stays under the image budget (250 KB by default); larger ones are resized or re-encoded.perf/layout-shiftNothing moves after it appears - space is reserved for images, embeds, ads and late content (CLS 0.1 or less).perf/lcp-imageThe LCP image is in the initial HTML, not lazy, and has fetchpriority="high"; a CSS background LCP image is preloaded.perf/lcp-image-not-lazyThe image most likely to be the Largest Contentful Paint (hero, high priority) is never loading="lazy".perf/lcp-image-priorityThe image that is likely the LCP element loads eagerly with fetchpriority high; it is never lazy-loaded.perf/lighthouse-ciLighthouse in CI runs each URL several times, asserts on metrics and categories with realistic thresholds, and never demands a perfect 100.perf/no-legacy-javascriptDo not ship polyfills and down-level transforms for features your target browsers already support; declare targets once.perf/no-whole-library-importClient code does not import all of lodash or moment; use lodash-es, per-method imports, Intl or the platform.perf/render-blocking-scriptA script in the head loads with defer, async or type="module", never as a plain blocking script.perf/srcset-sizesA srcset with width descriptors (400w, 800w) comes with a sizes attribute that matches the image's displayed width.perf/third-party-asyncThird-party scripts load with async or defer (or a framework strategy such as lazyOnload), never as parser-blocking tags.perf/third-party-costEach third-party script earns its place, loads late, and heavy embeds (video, maps, chat) start as a lightweight facade.perf/yield-long-tasksMain-thread work over 50 ms is split and yields, and event handlers do the visible update first (INP 200 ms or less).
HTML 11 rules
html/button-typeEvery button states its type (button, submit or reset); the default is submit.html/constraint-attr-typeValidation attributes match the input type (pattern and maxlength on text-like types, min, max and step on number and date types).html/doctypeEvery HTML document starts with <!doctype html> so the browser renders it in standards mode.html/form-submitFields sit in a form that is handled on its submit event with a submit button, so Enter, autofill and native validation work.html/icon-from-setWhen the project has an icon set (sprite, icon classes or an Icon component), markup uses it, never a hard-coded path to an icon file.html/input-typeAn input for an email address, phone number or URL uses type email, tel or url, not text.html/no-number-for-codesCodes made of digits (postal codes, card numbers, one-time codes, PINs) use type text with inputmode numeric, not type number.html/password-autocompletePassword fields say current-password (sign-in) or new-password (sign-up, change) in autocomplete, and never use autocomplete off.html/picture-imgEvery picture element ends with an img that has src, alt, width and height; source elements alone render nothing.html/project-iconsIcons use the project's generated icon classes (its icon classes), not separate SVG image files.html/required-select-placeholderA required single-choice select starts with an empty placeholder option (value=""), so the required check can fail.
CSS 23 rules
css/animate-compositedAnimations and transitions change transform and opacity, not layout properties such as width, height, top, left or margin.css/asset-urlIn a bundled project, url() to images and fonts is a relative path or alias the bundler resolves, not a hard-coded root or public/ path.css/baseline-checkBefore using a newer CSS feature, check its Baseline status against the project's browser targets and plan the fallback.css/container-type-sizeQuery containers use container-type inline-size; size is only for elements that get their height from elsewhere.css/design-tokensColours and spacing come from the project's tokens (custom properties or variables); a literal that has a token, or repeats, is a finding.css/gap-over-marginsSpace the children of a flex or grid container with gap, not with margins on the children and last-child exceptions.css/icon-from-setWith a generated icon set, CSS uses its icon classes or mixin, never a url() hard-coded to an icon file.css/light-dark-color-schemeA project that uses light-dark() declares color-scheme (light dark) on :root or in a meta tag, or the dark values never apply.css/limited-needs-supportsCSS that is not Baseline yet (anchor positioning) or only just became Baseline (scroll-driven animations) sits in @supports with a fallback.css/logical-propertiesSites that run or may run right to left use logical properties (margin-inline-start, inset-inline-end), not left and right.css/nesting-no-concatIn native CSS nesting, never build class names from "&" plus a suffix (&__title); write the full selector.css/no-fixed-height-textBoxes that hold text do not combine a fixed height with hidden overflow; use min-height so enlarged or spaced text is not clipped.css/no-float-layoutPage and component layout uses grid or flexbox; float is only for wrapping text around media, and clearfix hacks are gone.css/no-id-selectorsStyle elements by class, not by id; an id selector outweighs any number of classes and makes the rule hard to override.css/no-important!important appears only in utility classes, user-preference overrides and third-party overrides, never to win a specificity fight.css/no-magic-numbersOffsets and spacing use the project's spacing scale or come from layout; hand-tuned one-off values such as top -7px are a finding.css/no-stale-prefixesNo hand-written vendor prefixes for features browsers support unprefixed (border-radius, transitions, transforms, flexbox, gradients).css/pause-auto-motionAnything that moves, blinks or scrolls on its own for more than five seconds (carousels, marquees, background video) can be paused.css/reduced-motionNon-essential motion (entrance animations, parallax, smooth scrolling) respects prefers-reduced-motion.css/rem-helperFont sizes and spacing use the project's px-to-rem helper (its rem function), not raw px.css/rem-unitsFont sizes are written in rem (or em), never px, in projects without a rem helper; with one, css/rem-helper applies.css/unitless-line-heightline-height is a unitless number (for example 1.5), never px, em or a percentage, so it scales with each element's font size.css/z-index-scalez-index values come from a named scale (custom properties or Sass variables), not ad hoc numbers such as 999 or 9999.
Responsive layout 7 rules
responsive/dynamic-viewport-unitsA full-height layout uses 100dvh (or svh) instead of 100vh, which is taller than the visible area on mobile.responsive/no-fixed-wide-widthNo layout box gets a fixed width wider than 320 CSS px; cap it with max-width or min(100%, …) so the page reflows on narrow screens.responsive/no-hover-onlyNothing is revealed on hover alone; content and controls shown on hover also show on keyboard focus and are reachable on touch.responsive/no-viewport-font-sizeText is never sized with viewport units alone (font-size 5vw); fluid type mixes in rem, as in clamp(1rem, 0.9rem + 1vw, 1.5rem).responsive/orientationThe page works in portrait and landscape; it never locks the orientation or asks people to rotate unless one orientation is essential.responsive/reflowThe page reads without sideways scrolling at 320 CSS px and 400% zoom; only tables, maps and similar may scroll both ways.responsive/viewport-metaEvery HTML document has <meta name="viewport" content="width=device-width, initial-scale=1">.
JavaScript 13 rules
js/asset-importIn a bundled project, images, fonts and media in component markup are imported or use the base-path helper, not hard-coded root paths.js/document-writeNever insert content with document.write; create elements or load scripts with the DOM.js/feature-detectTest for the feature you need (in, CSS.supports, matchMedia), not the browser name in navigator.userAgent.js/fetch-check-okAfter fetch, check response.ok (or the status) before reading the body; fetch does not reject on 404 or 500.js/listener-cleanupListeners, timers and observers a view starts are removed when it goes away; one AbortController signal can end them all.js/no-empty-catchA catch block never swallows an error silently; handle it, report it, or say in a comment why it is safe to ignore.js/no-evalNever run strings as code - no eval, new Function, or a string as the first argument of setTimeout or setInterval.js/no-floating-promisesEvery promise is awaited, returned or given a .catch(); an async call is never left floating.js/no-inline-handlersAttach event handlers from script with addEventListener, not with onclick="..." attributes in HTML.js/no-varDeclare variables with const, or let when they are reassigned; never with var.js/passive-listenersMark touchstart, touchmove and wheel listeners { passive true } unless the handler really calls preventDefault().js/strict-equalityCompare with === and !==; loose == and != convert types first (x == null as a deliberate null-or-undefined test is the exception).js/throw-error-objectsThrow and reject with Error objects (or subclasses), never with strings or plain objects.
TypeScript 6 rules
ts/isolated-modulesA Vite project's tsconfig sets isolatedModules or verbatimModuleSyntax, so code the bundler compiles file by file type-checks the same way.ts/no-deprecated-optionsA TypeScript 6 or 7 tsconfig uses no option deprecated in 6.0 and removed in 7.0 (baseUrl, moduleResolution node, target es5).ts/no-explicit-anyType values with a real type or unknown (then narrow), never any.ts/no-ts-ignoreSilence a type error with @ts-expect-error and a reason, never @ts-ignore or @ts-nocheck.ts/strictTypeScript runs with strict on; no config turns strict or one of its checks (strictNullChecks, noImplicitAny...) off.ts/validate-untrustedData from outside the program (fetch, JSON.parse, storage) is validated at run time, not given a type with as or an annotation.
React 3 rules
react/no-array-index-keyA list item's key comes from the data (an id), not from the array index.react/no-derived-state-effectValues computed from props or state are calculated during render, not copied into state by an effect.react/no-nested-componentComponents are declared at module level, never inside another component's body.
Next.js 9 rules
next/async-paramsAwait params and searchParams in App Router pages, layouts and generateMetadata; Next.js 16 removed synchronous access.next/image-fill-sizesA next/image with fill (or sized by CSS) sets sizes, so the browser does not download a viewport-wide file for a small slot.next/no-async-client-componentA Client Component is never an async function; fetch in a Server Component parent or read a promise with use().next/public-env-secretNever give a secret the NEXT_PUBLIC_ prefix; those values are copied into the JavaScript every visitor downloads.next/redirect-outside-tryCall redirect(), notFound() and their kin outside try blocks; they work by throwing, and a catch swallows them.next/server-action-checksEvery Server Action checks who is calling and what they may change, validates its input and returns only what the UI needs.next/use-client-at-leavesKeep App Router pages and layouts Server Components; put 'use client' on the small interactive component they render.next/use-next-fontLoad web fonts with next/font (google or local), not a link or @import to the Google Fonts stylesheet.next/use-next-imageIn a Next.js app, render images with next/image instead of a plain img element (a picture element with its own sources is fine).
Vue 10 rules
vue/computed-pureA computed getter only derives a value - no async work, no requests and no changes to other state.vue/detailed-propsDeclare props with their types (an object with type, or a TypeScript type), not as a bare array of names.vue/link-for-navigationNavigate with RouterLink or NuxtLink, not a button whose click handler only calls router.push or navigateTo.vue/multi-word-namesName components with two or more words (TodoItem, not Item); App, pages and layouts are exempt.vue/no-index-keyA v-for key comes from the item's data, not from the loop index, when the list can change.vue/no-mutating-propsA component never changes its props (or objects and arrays inside them); it emits an event or keeps a local copy.vue/no-reactivity-lossNever destructure or reassign a reactive() object; use toRefs, a ref, or change its properties.vue/no-v-if-with-v-forNever put v-if and v-for on the same element; filter in a computed list or move v-if to a wrapper.vue/ref-value-in-scriptIn script code, read and compare a ref or computed through .value; the ref object itself is always truthy.vue/require-v-for-keyEvery v-for gives its items a :key from the data; on a template v-for the key goes on the template tag.
Nuxt 8 rules
nuxt/async-data-keyA composable that wraps useAsyncData passes an explicit, unique key built from its inputs.nuxt/fetch-composable-in-handlerCall useFetch and useAsyncData only in setup, plugins or composables; in event handlers use $fetch.nuxt/no-bare-fetch-in-setupLoad a page's initial data with useFetch or useAsyncData, not a bare $fetch at the top of script setup.nuxt/no-browser-api-in-setupNever touch window, document, localStorage or navigator at the top of script setup; it also runs on the server.nuxt/no-module-scope-stateNever create ref() or reactive() state at module scope in a Nuxt app; use useState or a Pinia store.nuxt/public-runtime-configSecrets live in the top level of runtimeConfig, never under runtimeConfig.public, which every page sends to the browser.nuxt/route-announcerThe app renders NuxtRouteAnnouncer (in app.vue or a layout) so client-side page changes are announced.nuxt/runtime-config-not-process-envApp code reads configuration with useRuntimeConfig() and tests the side with import.meta.client, not process.env or process.client.
Angular 9 rules
angular/banana-in-boxTwo-way binding is written [(prop)], brackets outside; ([prop]) silently binds an event named [prop] instead.angular/computed-not-effectDerive state with computed() (or linkedSignal()); never copy one signal into another inside effect().angular/control-flowTemplates use the built-in @if, @for and @switch blocks, not the *ngIf, *ngFor and *ngSwitch directives deprecated in v20.angular/for-track-idAn @for block tracks a stable unique field of the item (track item.id), not the item object itself.angular/ng-optimized-imageStatic images in Angular templates use NgOptimizedImage (ngSrc with width and height, or fill), not a plain src.angular/no-bypass-security-trustDomSanitizer bypassSecurityTrust* calls never receive user or server data; each one is a reviewed exception.angular/router-aria-currentA routerLinkActive link also sets ariaCurrentWhenActive, so the current page is announced, not only styled.angular/ssr-browser-globalsCode that runs on the server never touches window, document, navigator or localStorage directly; use DI or afterNextRender.angular/table-tbodyTable rows in Angular templates sit inside an explicit tbody (or thead/tfoot); the browser inserts one and hydration fails.
Svelte 10 rules
svelte/derived-not-effectA value computed from state is a $derived, not a $state that an $effect keeps in sync.svelte/each-keyAn each block whose items hold state (inputs, bindings, components, transitions) is keyed by a unique id, never by the index.svelte/kit-app-stateRead page, navigating and updated from $app/state, not the $app/stores module that SvelteKit 3 removed.svelte/kit-form-actionsA SvelteKit form posts to a form action (method and action) and is enhanced with use:enhance, not handled only by onsubmit.svelte/kit-load-waterfallA load function starts independent requests together and awaits parent() only after them, so data does not load in a chain.svelte/kit-page-titleEvery SvelteKit page sets its own descriptive title in svelte:head, or its layout renders a title from page data.svelte/kit-shared-server-stateServer code of a SvelteKit app never keeps per-request data in module-level variables; the module is shared by every user.svelte/no-legacy-syntaxNew Svelte 5 code uses runes and the new syntax, not export let, $:, on:event, slot or svelte:component.svelte/props-derivedA top-level value computed from props is a $derived; a plain let or const keeps only the first value.svelte/window-eventsListen to window and document events with svelte:window and svelte:document, not addEventListener in $effect or onMount.
Astro 6 rules
astro/client-only-frameworkGive client:only the framework of the component (client:only="react"), and a fallback slot for the space it fills.astro/directive-on-framework-componentPut client:* directives only on UI framework components imported into the .astro file, never on Astro components.astro/public-env-secretNever give a secret the PUBLIC_ prefix; in Astro those variables are built into client code. Declare secrets in astro:env as server secrets.astro/script-processedAstro script tags carry no attribute but src so they are bundled; is:inline only on purpose, data-* instead of define:vars.astro/server-defer-fallbackA server island (server:defer) has slot="fallback" content of the same size, and receives only small serializable props.astro/static-by-defaultRender framework components without a client directive unless they need interactivity, and pick the latest directive that works.
Web Components 9 rules
webc/aria-idrefs-same-treeid references (for, aria-labelledby, aria-describedby, aria-controls) point at an element in the same DOM tree, not across a shadow root.webc/constructor-no-attributesA custom element constructor never reads or adds attributes or children; do that in connectedCallback.webc/form-associatedA custom form control is form-associated (formAssociated and attachInternals) and reports its value and validity to the form.webc/host-hiddenShadow styles that give :host a display also add :host([hidden]) { display none }, or the hidden attribute stops working.webc/lit-class-fieldsIn JavaScript, a Lit reactive property is never also declared as a class field; the field hides Lit's accessor and updates stop.webc/lit-immutable-updatesA Lit reactive array is replaced, not mutated in place (push, splice, sort), unless requestUpdate() follows.webc/lit-unsafe-htmlLit's unsafeHTML, unsafeSVG and unsafeStatic only receive strings the developer wrote or sanitized, never data.webc/no-customized-builtinsBuild autonomous custom elements that wrap a native element; do not extend built-ins with is=, which Safari does not support.webc/valid-nameA custom element name is lowercase, starts with a letter, contains a hyphen and is not a reserved name, or define() throws.
Tailwind CSS 9 rules
tailwind/components-over-applyRemove repeated class lists with a component, a loop or a partial; keep @apply for small cases and third-party markup.tailwind/conflicting-utilitiesOne element never carries two utilities that set the same property under the same variant (p-2 p-4, flex grid).tailwind/hover-only-revealContent or controls revealed with hover: are also revealed on focus (focus-within:, group-focus-within:), never on hover alone.tailwind/motion-safeDecorative Tailwind animations (animate-bounce, animate-ping, animate-pulse) run under motion-safe: or stop under motion-reduce:.tailwind/no-dynamic-classWrite every Tailwind class as a complete string; never build one from fragments such as bg-${color}-500.tailwind/no-preprocessorDo not run Tailwind CSS 4 through Sass, Less or Stylus; use plain CSS with native nesting and variables.tailwind/sr-only-labelThe only text of a button or link is never hidden with hidden; use sr-only (with md:not-sr-only to show it on wider screens).tailwind/theme-tokensAn arbitrary value used again and again (bg-[#0a7a3e], p-[18px]) becomes a theme variable in @theme with its own utility.tailwind/v4-entryIn Tailwind CSS 4, load Tailwind with @import "tailwindcss" and a legacy JS config with @config; @tailwind directives do nothing.
Sass 8 rules
sass/custom-properties-for-themesValues that change at run time (themes, dark mode, user settings) are CSS custom properties; Sass variables are for build-time constants.sass/math-divDivide with math.div() in Sass; a slash between Sass values is becoming a list separator, as it is in CSS.sass/no-extend-classExtend only placeholder selectors (%name), or use a mixin; @extend of a real class copies selectors everywhere it appears.sass/no-global-builtinsCall Sass built-in functions through their module (map.get, math.div, string.index) after @use "sass:...", not as globals.sass/no-importLoad Sass files with @use and @forward, not @import, which is deprecated and will be removed in Dart Sass 3.sass/no-legacy-color-functionsAdjust colours with color.scale or color.adjust (and read channels with color.channel), not darken, lighten, fade-out and the like.sass/no-node-sassCompile Sass with Dart Sass (the sass or sass-embedded package), never node-sass, which is deprecated LibSass.sass/rem-helperSizes are converted with the project's px-to-rem function, never by dividing by a hard-coded base by hand.
State 13 rules
state/fetch-effect-cleanupAn effect that fetches into state returns a cleanup that ignores or aborts stale responses.state/immer-mutate-or-returnA Redux Toolkit or Immer reducer either mutates the draft or returns a new state, never both; no arrow that returns a mutation.state/invalidate-after-mutationAfter a mutation succeeds, invalidate or update every cached query it changed, so lists and details show the new data.state/no-server-state-in-storeData fetched from a server lives in the framework's loader or a server-state cache, not copied into a client store.state/pinia-store-to-refsState and getters taken out of a Pinia store go through storeToRefs(); plain destructuring freezes them at their first value.state/query-client-stableThe QueryClient is created once - in useState(() => new QueryClient()) or a module helper - never in a component's render.state/query-error-stateA component that reads a query (TanStack Query, SWR) also reads its error and shows it; it never renders only data.state/query-key-depsA TanStack Query key holds every variable its query function reads, so a new id fetches new data instead of showing the old.state/query-object-syntaxTanStack Query 5 hooks take one options object; the v4 positional form useQuery(key, fn) and useMutation(fn) no longer works.state/redux-toolkitNew and maintained Redux code uses Redux Toolkit's configureStore and createSlice, not the legacy createStore.state/selector-stable-resultA store selector returns existing values, not a new object or array on every call; pick fields one by one or use useShallow or shallowEqual.state/server-state-cacheServer data comes from the framework loader or a server-state library, not fetch plus useState and loading flags in each component.state/url-stateState a person would share, bookmark or restore with Back (filters, sort, page, selected tab, search) lives in the URL.
SEO 9 rules
seo/canonical-validA page has at most one rel="canonical", in the head, with an absolute URL of an indexable page and no fragment.seo/hreflang-validhreflang values are ISO 639-1 language codes with an optional script and ISO 3166-1 region (or x-default), linking absolute URLs.seo/http-statusMissing pages return 404 or 410, moved pages 301 or 308, and a client-rendered error view is not served with 200.seo/indexablePages meant to rank are crawlable and carry no noindex (meta robots or X-Robots-Tag) in production.seo/jsonld-type-supportedStructured data for rich results uses types in Google's current search gallery, not retired ones such as FAQPage or HowTo.seo/jsonld-validEvery JSON-LD block is valid JSON with "@context" "https://schema.org" and a schema.org @type.seo/meta-descriptionEach indexable page has its own meta description that summarises that page.seo/og-absolute-urlOpen Graph and social image and URL tags (og:image, og:url, twitter:image) use absolute https URLs.seo/title-uniqueEach page has its own descriptive title, page topic first and site name last, in the page's language.
Internationalisation 7 rules
i18n/dir-rtlPages in right-to-left languages (Arabic, Hebrew, Persian, Urdu) set dir="rtl" on html together with lang.i18n/explicit-localeIntl formatters and toLocale*String() receive the app's active locale instead of relying on the runtime default.i18n/intl-formatDates, times, numbers and prices are formatted with Intl for the user's locale, never assembled from parts.i18n/logical-propertiesIn projects with a right-to-left locale, layout uses logical properties (margin-inline-start, inset-inline-end, text-align start).i18n/no-string-concatUser-facing sentences are one message with placeholders, never joined from translated fragments and values.i18n/plural-rulesPlurals use the locale's plural rules (ICU plural or Intl.PluralRules), never count === 1 ? singular : plural.i18n/same-keysEvery locale file has the same keys and placeholders as the source locale, and the plural forms its language needs.
Security 9 rules
security/csp-meta-ignoredframe-ancestors, report-uri, sandbox and X-Frame-Options are sent as HTTP headers; in a meta tag they do nothing.security/csp-strictProduction pages send a strict Content Security Policy (nonces or hashes with strict-dynamic, object-src none, base-uri none).security/https-onlyEvery page and every resource it loads use HTTPS; no mixed content and no http:// links to your own site.security/no-evalFront-end code never runs strings as code - no eval, new Function or string arguments to setTimeout and setInterval.security/no-secrets-in-client-envSecrets never use a client-exposed environment prefix (NEXT_PUBLIC_, VITE_, PUBLIC_, NUXT_PUBLIC_); they stay on the server.security/postmessage-originpostMessage names an exact target origin, and every window message listener checks event.origin against an allowlist.security/raw-html-sinkNever pass user or remote data to a raw HTML sink (innerHTML, dangerouslySetInnerHTML, v-html, {@html}) without sanitizing it.security/sri-cdnVersioned files from a public CDN carry Subresource Integrity (integrity plus crossorigin), or are self-hosted.security/window-open-noopenerwindow.open for another site passes "noopener" (and "noreferrer" when needed) so the new page cannot control yours.
Testing 5 rules
test/axe-in-e2eEnd-to-end tests run an axe scan with the WCAG tags you target on each key page and state, and fail on violations.test/no-fixed-waitTests wait for conditions, never for a fixed time (no waitForTimeout, cy.wait(ms) or setTimeout sleeps).test/no-focused-testsNo focused tests (.only, fit, fdescribe) are committed; they silently skip the rest of the suite.test/query-by-roleTests find elements the way users do - getByRole with a name first, then label and text; test ids only as a last resort.test/web-first-assertionsPlaywright tests assert with auto-retrying web-first matchers (toBeVisible, toHaveText), not on values read once.
Tooling 7 rules
tooling/a11y-lintThe linter runs the framework's accessibility rules (jsx-a11y, vuejs-accessibility, angular-eslint template, Svelte a11y warnings).tooling/console-cleanPages log no errors and use no deprecated browser APIs, as seen in the console on load and in the main flows.tooling/eslint-flat-configESLint is configured in eslint.config.* (flat config); .eslintrc files and .eslintignore are migrated, not kept beside it.tooling/ignore-with-reasonEvery fs-ignore comment names the rule id it silences and says why; a bare fs-ignore silences every rule on the line.tooling/one-lockfileA project has exactly one committed lockfile, and CI installs from it with the frozen command (npm ci, pnpm install --frozen-lockfile).tooling/typecheck-separatelyA TypeScript project runs a separate type check (tsc --noEmit, vue-tsc, svelte-check, astro check) in CI; the bundler does not.tooling/use-project-scriptsRun checks through the project's own scripts and package manager; never install tools globally or switch package managers.
In a terminal: npx github:dawidolko/frontend-skills explain <id>.