npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@uxf/eslint-config

v14.1.0

Published

Shared ESLint 9 flat config for UXF frontend projects (TypeScript + React + Tailwind CSS).

Readme

@uxf/eslint-config

Shared ESLint 9 flat config for UXF frontend projects (TypeScript + React + Tailwind CSS).

When to use

Use it as the base ESLint configuration in any UXF frontend project (Next.js apps, React libraries, this monorepo's own root). It bundles the plugin set and the opinionated rule choices shared across UXF, so a project only adds its tsconfig wiring and any project-specific overrides.

Requires ESLint 9+ flat config (eslint.config.mjs). The project's own TypeScript version is irrelevant to linting — this package ships the TypeScript build that typescript-eslint needs (see TypeScript 7). It does not support the legacy .eslintrc / .eslintignore format. Migrating an ESLint 8 project? Follow MIGRATION.md.

Installation

yarn add -D @uxf/eslint-config @uxf/browserslist-config eslint @eslint/js eslint-import-resolver-typescript

eslint, @eslint/js, eslint-import-resolver-typescript and @uxf/browserslist-config are peer dependencies (yarn dependencies:fix installs them in a project with @uxf/scripts; without it, add them by hand, or ESLint fails to load the config). All linting plugins (eslint-plugin-import, -react, -react-hooks, -jsx-a11y, -tailwindcss, -compat, -n, typescript-eslint, globals) plus the typescript build used by typescript-eslint ship as direct dependencies of this package — do not install them yourself.

eslint-import-resolver-typescript in particular must live in the project root's node_modules (hence a peer dependency), because eslint-plugin-import resolves it from the linted file's context, not from this package's own dependencies.

| Peer dependency | Version | | ----------------------------------- | -------- | | eslint | ^9.0.0 | | @eslint/js | ^9.0.0 | | eslint-import-resolver-typescript | ^4.4.5 | | @uxf/browserslist-config | exact |

@uxf/browserslist-config is the company list of supported browsers that compat/compat checks against; yarn dependencies:fix installs it.

Quick start

The package is ESM-only ("type": "module"). Create eslint.config.mjs in your project root (or eslint.config.js if your project itself is "type": "module") and spread the default export:

import uxfConfig from "@uxf/eslint-config";

/** @type {import("eslint").Linter.Config[]} */
export default [
    // Global ignores (replaces .eslintignore)
    {
        ignores: ["**/node_modules/", "**/*.js", "**/*.jsx", "**/*.d.ts"],
    },

    ...uxfConfig,

    // Project-specific config
    {
        languageOptions: {
            parserOptions: {
                project: "tsconfig.json",
                tsconfigRootDir: import.meta.dirname,
            },
        },
    },
];

ESLint 9 auto-discovers eslint.config.mjs, so scripts are just eslint . / eslint . --fix.

With Next.js (eslint-config-next)

For Next.js 15+ with native flat config support:

import uxfConfig from "@uxf/eslint-config";
import nextPlugin from "@next/eslint-plugin-next";

/** @type {import("eslint").Linter.Config[]} */
export default [
    { ignores: ["**/node_modules/", ".next/", "**/*.js", "**/*.d.ts"] },

    ...uxfConfig,

    // Next.js rules
    {
        plugins: { "@next/next": nextPlugin },
        rules: {
            ...nextPlugin.configs.recommended.rules,
            ...nextPlugin.configs["core-web-vitals"].rules,
        },
    },

    // Project config
    {
        languageOptions: {
            parserOptions: {
                project: "tsconfig.json",
                tsconfigRootDir: import.meta.dirname,
            },
        },
    },
];

For Next.js 14 using the legacy eslint-config-next, use @eslint/eslintrc's FlatCompat:

import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
import uxfConfig from "@uxf/eslint-config";

const __dirname = dirname(fileURLToPath(import.meta.url));
const compat = new FlatCompat({ baseDirectory: __dirname });

/** @type {import("eslint").Linter.Config[]} */
export default [
    { ignores: ["**/node_modules/", ".next/", "**/*.js", "**/*.d.ts"] },

    ...uxfConfig,
    ...compat.extends("next/core-web-vitals"),

    {
        languageOptions: {
            parserOptions: {
                project: "tsconfig.json",
                tsconfigRootDir: __dirname,
            },
        },
    },
];

What's included

The config composes these base sets (in order), then applies a project rule layer and two file-scoped overrides:

  • @eslint/js recommended + typescript-eslint recommended
  • eslint-plugin-import (flatConfigs.recommended)
  • eslint-plugin-react (configs.flat.recommended, registered with the ref exception in destructuring-assignment described below)
  • eslint-plugin-jsx-a11y (flatConfigs.strict)
  • eslint-plugin-tailwindcss (configs["flat/recommended"], with classnames-order and no-custom-classname turned off)
  • eslint-plugin-react-hooks (plugin registered; rules-of-hooks and exhaustive-deps set explicitly, plus the React Compiler rules of recommended-latest as warnings — no recommended spread)
  • eslint-plugin-compat – compat/compat as an error: a web API (URLPattern, requestIdleCallback, ClipboardItem …) or an ES built-in on a global (Intl.Segmenter) that a supported browser lacks. The targets come from @uxf/browserslist-config, even in a project whose package.json has no browserslist; a project's own browserslist can add older browsers but not raise that floor. A green lint is no proof of support: the plugin's API data is frozen at about the end of 2024 (Promise.try, URL.parse, Iterator.from pass unnoticed), instance methods (arr.toSorted()) are not checked, and only an if counts as a feature check (typeof x === "function" ? x() : … and window.x?.() are still reported). The plugin matches identifiers by name, so CookieStore is listed in settings.polyfills (a cookieStore from next/headers runs on the server); a project that sets its own polyfills replaces that list and has to repeat "CookieStore". Turn the rule off for code that runs on Node.js only (CLIs, build scripts) if it reports a Node API.
  • eslint-plugin-n – only n/no-deprecated-api as an error: url.parse, new Buffer(), fs.exists and the like. Node.js runs on the latest LTS everywhere, so version-range checks are not enabled.

It also sets languageOptions.globals to browser + node + jest (no env needed in consumers), settings.react.version to "detect", and the TypeScript import/resolver.

Notable custom rules

Opinionated rules that shape how conforming code must be written:

  • React components are function declarations — react/function-component-definition: { namedComponents: "function-declaration" }; react/react-in-jsx-scope: off; react/prop-types: off.
  • No prop destructuring — react/destructuring-assignment: "never" (use props.x), with one exception: a declaration that destructures nothing but ref props, const { ref } = props; or const { ref: forwardedRef, inputRef } = props; (ref or a name ending in Ref). The React Compiler treats props as a ref once props.ref (or props.xRef passed to ref={…}) is read, and then skips the whole component, so ref props are read this way. Signature destructuring and rest patterns stay errors.
  • React Compiler rules as warnings — react-hooks/config, error-boundaries, gating, globals, immutability, incompatible-library, preserve-manual-memoization, purity, refs, set-state-in-effect, set-state-in-render, static-components, unsupported-syntax, use-memo, void-use-memo. A warning marks code the compiler skips (or would compile unsafely); with reactCompiler off they are advice, with it on they explain why a component stayed unoptimised.
  • Components must be memoized — react/require-optimization: error.
  • Sorted JSX props — react/jsx-sort-props; plus jsx-boolean-value: never, self-closing-comp, jsx-curly-brace-presence, jsx-no-useless-fragment.
  • No FC / PropsWithChildren from react — via no-restricted-imports (see RESTRICTED_IMPORT_PATHS below).
  • no-restricted-syntax blocks: numeric index access (arr[0] → use .at()); bare Record<string, …> (wrap in Partial<Record<…>> or narrow the key); Partial<Record<string, any>> (use AnyObject from @uxf/core/types).
  • Function members as properties, not method shorthand — @typescript-eslint/method-signature-style: "property", so an interface member is written getItem: (key: string) => void, never getItem(key: string): void. Method shorthand is bivariant in its parameters — strictFunctionTypes deliberately exempts method signatures for compatibility with lib types like Array.push — so it silently accepts an implementation whose parameter type is incompatible. The property form is checked contravariantly. Auto-fixable.
  • Boolean naming — @typescript-eslint/naming-convention requires boolean type-properties and variables to be prefixed with is/has/can/should/will/did/show/hide.
  • Stricter TS/core — @typescript-eslint/no-unnecessary-condition, no-use-before-define, no-shadow; no-console, complexity, eqeqeq, curly, radix, no-await-in-loop, and more, all as errors.

Overrides relax rules where appropriate: **/*.d.ts turns off naming-convention; test/spec/story files and tests/__tests__ dirs turn off import/no-extraneous-dependencies.

API

Single entry point — the exports map exposes only ., so import everything from @uxf/eslint-config:

import uxfConfig, { RESTRICTED_IMPORT_PATHS } from "@uxf/eslint-config";

| Export | Type | Description | | ------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | | default | Linter.Config[] | The full flat-config array. Spread it into your config (...uxfConfig). | | RESTRICTED_IMPORT_PATHS | { name: string; importNames: string[]; message: string }[] | The no-restricted-imports paths list used by the shared config (currently blocks FC and PropsWithChildren from react). |

Because flat-config rule values replace rather than merge, re-declaring no-restricted-imports in a project override drops the shared paths unless you spread RESTRICTED_IMPORT_PATHS back in:

import uxfConfig, { RESTRICTED_IMPORT_PATHS } from "@uxf/eslint-config";

export default [
    ...uxfConfig,
    {
        rules: {
            "no-restricted-imports": [
                "warn",
                { paths: [...RESTRICTED_IMPORT_PATHS, { name: "…", importNames: ["…"], message: "…" }] },
            ],
        },
    },
];

CI behavior

Some rules are demoted from a hard error to a local-only warning: they are set to warn normally and off when the CI environment variable is set (process.env.CI is truthy). This gives feedback during local development without blocking CI pipelines.

Affected rules: no-restricted-imports, no-restricted-syntax, @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion, import/no-deprecated, and the React Compiler rules of react-hooks listed above.

linterOptions.reportUnusedDisableDirectives uses the same value — unused eslint-disable comments are warned locally but ignored in CI.

TypeScript 7

TypeScript 7 (the Go rewrite) ships no JavaScript compiler API yet — typescript@7 only exports lib/version.cjs plus the unstable/* entry points. typescript-eslint needs the old API, so on a TS 7 project it crashes on startup:

TypeError: Cannot read properties of undefined (reading 'Cjs')
    at .../@typescript-eslint/typescript-estree/dist/create-program/shared.js

This package solves it for every consumer: typescript is a direct dependency pinned to the last JS-API release (6.0.3), not a peer. The package manager therefore installs it into node_modules/@uxf/eslint-config/node_modules/typescript and — because the peer requirement of typescript-eslint is met from here, not from the project root — nests the whole @typescript-eslint/* tree next to it. Linting (including type-aware rules) runs on TS 6, while the project root keeps whatever typescript it wants and tsc / next build run on TS 7.

Consequences to be aware of:

  • A project needs no resolutions / overrides entry — and none would help anyway: typescript is a peer dependency of typescript-eslint, and Yarn resolutions cannot override how a peer is provided.
  • Type-aware rules always evaluate types with TS 6.0.3, independent of the project's own TypeScript. On a project whose tsconfig.json uses compiler options that TS 6 removed, parserOptions.project will report them.
  • Every consumer installs a second TypeScript copy (~6 MB download).
  • Yarn only. Yarn's peer-aware linker nests the whole chain (typescript-eslint, @typescript-eslint/*, ts-api-utils) next to the bundled TypeScript. npm hoists ts-api-utils to the project root, where it picks up the root TypeScript again and crashes with Cannot read properties of undefined (reading 'Intrinsic'); an overrides entry does not move it. An npm project therefore still needs typescript 5.x/6.x at its root.

Once typescript-eslint supports TS 7 (its peer range is still >=4.8.4 <6.1.0), drop the typescript dependency and restore it as a peer.

Gotchas

  • ESLint 9 flat config only. No .eslintrc / .eslintignore; put ignores in an { ignores: [...] } config object.
  • ESM package. Consume it from eslint.config.mjs (or eslint.config.js in an ESM project).
  • Keep eslint-import-resolver-typescript at the project root as a direct devDependency, or eslint-plugin-import fails to resolve it (see MIGRATION.md troubleshooting).
  • Do not register eslint-plugin-react again (e.g. reactPlugin.configs.flat["jsx-runtime"] or a config that bundles it) next to this config. The shared config registers its own copy of the plugin under react, extended by the ref exception, and ESLint refuses two different objects under one plugin name (Cannot redefine plugin "react"). Copy the rules you need instead.
  • Do not add importPlugin.flatConfigs.typescript on top of this config — its node resolver overrides the TypeScript resolver and causes import/no-extraneous-dependencies false positives for workspace packages.
  • Do not re-add settings["import/parsers"] in a project override. It names the parser as a string, which eslint-module-utils resolves from the project root, where @typescript-eslint/parser no longer lives; every file then reports Parse errors in imported module … Cannot find module '@typescript-eslint/parser'. Without the setting eslint-plugin-import falls back to the parser object from languageOptions.

Links

  • MIGRATION.md — ESLint 8 → 9 flat-config migration guide (breaking changes, step-by-step, troubleshooting).
  • Repository