An ESLint plugin for React & TypeScript teams
Stop leaving the same comment on every pull request.
eslint-frontend-rules turns the review nitpicks — raw <p> tags,
hardcoded hex colors, unstable default props, missing alt text — into lint errors your team
catches before a human has to type them again. Twenty-eight rules, six with autofix, one plugin.
See it work
One component, six review comments you'll never write again.
This is what a single lint pass over one component actually flags — a deep import, a default export, a color literal, an unlabeled click handler, and a raw tag. Toggle to see the fix.
import Button from '../../../components/button'1; export default2 function Card({ items = []3 }) { return ( <div style={{ color: '#4285F4'4 }} onClick={onSelect}5> <p>6Total: {items.length}</p> </div> ); }
import { Button } from '@/components/button'; const EMPTY_ITEMS: CardItem[] = []; export function Card({ items = EMPTY_ITEMS }: CardProps) { return ( <div role="button" tabIndex={0} onClick={handleSelect} onKeyDown={handleSelectKeyDown} style={{ color: 'var(--accent)' }} > <TypographyP>Total: {items.length}</TypographyP> </div> ); }
- 1enforce-alias-import-paths — deep relative import; use an alias like
@/components/button. - 2no-default-export — default export found; use a named export instead.
- 3no-unstable-default-props — new array literal on every render; breaks memoization downstream.
- 4no-direct-colors — direct color value; use a CSS variable or theme token.
- 5no-focusable-non-interactive-elements — click handler on a non-interactive element with no
roleoronKeyDown. - 6enforce-typography-components — raw
<p>tag; use a Typography component likeTypographyP.
Why teams add it
The consistency your style guide already asks for, enforced automatically.
Design consistency
Raw <p>/<span>/<h1>–<h6> tags and hardcoded hex/rgb/hsl colors get flagged in both JSX and SVG attributes, so Typography components and design tokens stay the only way in.
Accessibility by default
Missing alt text, icon-only buttons with no accessible name, and click handlers on non-interactive elements are caught at lint time — not in a screen-reader audit three sprints later.
React correctness
Components nested inside components, unstable default props, unnecessary fragments and curlies — the patterns that quietly break memoization and re-render more than they should.
Naming, settled once
Boolean props, enum members, hook names, filenames, and CSS Module import aliases all follow one convention — decided in config, not re-litigated in every review thread.
Quick start
Install it, extend the recommended config, done.
Requires ESLint 9 or newer (flat config). Install as a dev dependency, add it to
eslint.config.js, and every rule in the recommended set turns on immediately —
override any individual rule's level or options underneath.
import frontendRules from "eslint-frontend-rules"; export default { extends: [frontendRules.configs.recommended], plugins: { "eslint-frontend-rules": frontendRules, }, rules: { // Optionally override rule levels or options here }, };
Autofix in action
Six rules rewrite the code for you.
Most naming and typing rules can't be autofixed safely — renaming a symbol without an AST-aware rename would break every call site. These can.
Every setter reads as [value, setValue] — the fix renames the binding and every reference to it in scope.
One local name for CSS Module imports across the whole codebase — grep for styles. and mean it.
Required fields first, always — an interface reads like a function signature, not a grab bag.
The rulebook
All 28 rules, in one place.
Every rule accepts an ignore option (glob patterns) to exempt files; several take their own additional options — see the README for the full list.
- enforce-typography-componentsEnforces Typography components over raw <p>, <span>, <h1>–<h6>, <blockquote> tags.
- no-direct-colorsDisallows hex, rgb(a), hsl(a), and named colors in style props and classNames.
- no-direct-colors-in-svg-attrsExtends the color rule to SVG fill, stroke, stopColor, and flood/lighting-color attributes.
- enforce-icon-button-aria-labelRequires an accessible name (aria-label, aria-labelledby, or title) on icon-only buttons.
- no-focusable-non-interactive-elementsFlags non-interactive elements with onClick but no role or onKeyDown.
- no-img-missing-altRequires an alt attribute on raw <img> elements — empty alt is fine, missing isn't.
- enforce-classname-utilityWarns when className is built from a template string instead of a class-merging utility like cn.
- enforce-event-handler-namingRequires on* prop names and handle* local function names for event handlers.
- enforce-no-empty-classname-utilityDisallows empty or whitespace-only className attributes.
- enforce-use-state-namingRequires the [value, setValue] naming convention for useState destructuring.
- no-inline-arrow-functions-in-jsxWarns on inline arrow functions passed as JSX props.
- no-nested-componentDisallows defining a component inside another component's body.
- no-unnecessary-curly-in-propsWarns on curly braces wrapping a string literal in a JSX prop.
- no-unnecessary-fragmentWarns when a React fragment wraps a single child or isn't needed for grouping.
- no-unstable-default-propsDisallows object/array/function literal defaults in component or hook parameters.
- enforce-boolean-prop-namingRequires boolean props to start with is, has, should, can, will, or did.
- enforce-css-module-import-nameEnforces one consistent local name (default styles) for CSS Module imports.
- enforce-interface-type-namingInterfaces start with I or end with Props; types start with T or end with Props.
- top-level-const-snakeRequires top-level const names in .tsx files to be ALL_CAPS.
- enforce-enum-member-namingEnforces PascalCase or UPPER_SNAKE_CASE for enum members.
- interface-type-required-firstRequires required fields before optional fields in interfaces and type literals.
- enforce-alias-import-pathsFlags deep ../../ relative imports in favor of configured alias prefixes.
- no-default-exportDisallows default exports; enforces named exports only.
- enforce-kebab-case-filenamesEnforces kebab-case file names for configured extensions.
- require-jsdoc-on-componentWarns if a root-level React component lacks a JSDoc comment.
- require-jsdoc-on-hookWarns if a root-level custom hook lacks a JSDoc comment.
- require-jsdoc-on-root-functionWarns if a root-level plain function lacks a JSDoc comment.
- enforce-testid-namingEnforces kebab-case values for data-testid (or a configured attribute).
No rules match that search.