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

@shayanthenerd/eslint-config

v0.28.0

Published

A modern, flexible ESLint configuration for enforcing best practices and maintaining a consistent coding style

Readme

@shayanthenerd/eslint-config   license-badge npm-version-badge

ESLint configuration for enforcing best practices and maintaining a consistent coding style. Inspect configurations!

Table of Contents

Plugin Support

Legend:

  • ✅ — Enabled by default
  • ⚙️ — Opt-in (requires manual configuration)
  • 🔍 — Automatically detected (based on project dependencies)

| Category | Activation | | :---------------------------------------------------------------------------------------------------- | :--------: | | Languages | | | JavaScript | ✅  | | TypeScript | 🔍  | | Markdown | ✅  | | HTML | ⚙️  | | CSS | ⚙️  | | Formatting | | | Stylistic | ✅  | | Perfectionist | ✅  | | Frameworks & Libraries | | | Astro (jsx-accessibility) | 🔍  | | React (jsx-accessibility, @html-eslint/react) | 🔍  | | Next | 🔍  | | Vue & Nuxt (vue-accessibility) | 🔍  | | Tailwind | ⚙️  | | Zod & Zod Mini | 🔍  | | Testing Tools | | | Storybook | 🔍  | | Vitest | 🔍  | | Cypress | 🔍  | | Playwright | 🔍  | | Miscellaneous | | | package.json | ✅  | | Node | ✅  | | Promises | ✅  | | Imports | ✅  | | Unicorn | ✅  | | Baseline | 🔍  |

Installation and Configuration

  1. Install the package and ESLint as dev dependencies:

    npm i -D @shayanthenerd/eslint-config eslint
  2. Create an ESLint configuration file (eslint.config.js) at the root of your project:

    import { defineConfig } from '@shayanthenerd/eslint-config';
    
    export default defineConfig();

    Note: TypeScript configuration files (eslint.config.ts) are also supported. Node.js versions below v22.18.0 may require additional setup.

  3. Add the following scripts to your package.json:

    {
      "scripts": {
        "lint:inspect": "npx @eslint/config-inspector",
        "lint": "eslint --fix --cache --cache-location='node_modules/.cache/.eslintcache'"
      }
    }

After installation:

  • Use npm run lint to lint and fix files.
  • Use npm run lint:inspect to see a visual breakdown of your configuration.
  • See IDE Support for editor integration.
  • See Customization for advanced configuration.
  • See Formatting for formatting options and Prettier integration.

Customization

defineConfig() supports both simple and advanced use cases:

import eslintPluginYaml from 'eslint-plugin-yaml';
import * as eslintPluginRegexp from 'eslint-plugin-regexp';

import { defineConfig } from '@shayanthenerd/eslint-config';

// Use the default configuration:
export default defineConfig();

// Customize the built-in configurations:
export default defineConfig({
  autoDetectDeps: false,
  configs: {
    stylistic: false,
    markdown: {
      language: 'commonmark',
    },
  },
});

// Use custom configuration objects:
export default defineConfig([
  {
    files: ['**/*.yaml', '**/*.yml'],
    ignores: ['**/*.schema.yaml', '**/*.schema.yml'],
    extends: [eslintPluginYaml.configs.recommended],
  },
  eslintPluginRegexp.configs['flat/recommended'],
]);

// Customize the built-in configurations and use custom configuration objects:
export default defineConfig(
  {
    autoDetectDeps: 'verbose',
    configs: {
      typescript: {
        typeDefinitionStyle: 'type',
        overrides: {
          rules: {
            '@typescript-eslint/explicit-module-boundary-types': 'off',
          },
        },
      },
    },
  },
  [
    {
      files: ['**/*.yaml', '**/*.yml'],
      ignores: ['**/*.schema.yaml', '**/*.schema.yml'],
      extends: [eslintPluginYaml.configs.recommended],
    },
    eslintPluginRegexp.configs['flat/recommended'],
  ],
);

Every built-in configuration accepts an overrides option. These values are merged into the generated configuration and take precedence over the defaults.

import type { ESLint, Linter } from 'eslint';

interface Overrides {
  name?: string,
  files?: (string | string[])[],
  ignores?: string[],
  plugins?: Record<string, ESLint.Plugin>,
  languageOptions?: Linter.Config['languageOptions'],
  settings?: Record<string, unknown>,
  rules?: ConfigRules, // The available rules in the current configuration object
}

Automatic Dependency Detection

local-pkg is used to detect installed dependencies and automatically enable the relevant integrations. Package managers that hoist transitive dependencies (such as NPM and Bun) may sometimes cause an integration to get enabled unexpectedly. This is because local-pkg scans node_modules instead of package.json.

PNPM's strict dependency resolution avoids this issue.

[!NOTE] For example, eslint-plugin-storybook depends on storybook, which is hoisted by NPM and Bun. As a result, the integration for storybook will be enabled automatically, even if you haven't explicitly installed it.

configs.useBaseline is automatically enabled when autoDetectDeps: true and env: 'browser' (both are the defaults).

To opt out of this behavior, either globally disable automatic dependency detection or manually disable the unwanted integrations that were enabled automatically.

Framework and Tool Integrations

Tailwind

To enable the Tailwind integration, provide the location of your Tailwind configuration or CSS entry point.

import { defineConfig } from '@shayanthenerd/eslint-config';

export default defineConfig({
  configs: {
    tailwind: {
      /* Either option is sufficient, but both can be provided. */
      config: './tailwind.config.js',
      entryPoint: './app/assets/styles/app.css',
    },
  },
});

For editor integration, to avoid inconsistent diagnostics from the Tailwind CSS IntelliSense VS Code extension, add the following settings to .vscode/settings.json:

{
  "tailwindCSS.lint.cssConflict": "ignore",
  "tailwindCSS.lint.recommendedVariantOrder": "ignore",
  "tailwindCSS.lint.suggestCanonicalClasses": "ignore",

  "eslint.rules.customizations": [
    { "rule": "better-tailwindcss/*", "severity": "off", "fixable": true },
    { "rule": "better-tailwindcss/no-restricted-classes", "severity": "warn", "fixable": true },
    { "rule": "better-tailwindcss/no-conflicting-classes", "severity": "error", "fixable": false },
    { "rule": "better-tailwindcss/no-unknown-classes", "severity": "warn", "fixable": false }
  ]
}

Nuxt

Nuxt support is already included through the Vue integration, so you don’t need to install or configure @nuxt/eslint manually. If you need additional rules tailored to Nuxt, refer to the plugin’s documentation for setup instructions.

[!TIP] configs.nuxt only includes options for Nuxt Image, Nuxt UI, and the <Icon> component. Vue's rules and overrides can be configured via configs.vue.

Markdown

Markdown linting is powered by @eslint/markdown.

By default, the plugin uses GitHub Flavored Markdown (GFM). You can switch to CommonMark if you prefer, but rules related to tables, label references, and other GFM syntax will be disabled because CommonMark doesn't support them.

[!NOTE] Fenced code blocks inside Markdown files are not linted by @eslint/markdown.

Node.js

Some rules depend on the specified Node.js version. Visit the documentation for version-resolution options and project-specific configurations.

IDE Support

Install the VS Code extensions for ESLint and Prettier.

[!TIP] In case you're using PNPM without shamefullyHoist: true and ESLint's VS Code extension isn't working as expected, add the following to your pnpm-workspace.yaml and run pnpm install --yes:

publicHoistPattern:
  - '*eslint*'

You can also add the following to your .vscode/settings.json:

{
  /* Enforce Unix-like line endings (LF). */
  "files.eol": "\n",

  /* Enforce 2 spaces for indentation. */
  "editor.tabSize": 2,
  "editor.insertSpaces": true,
  "editor.detectIndentation": false,

  "editor.codeActionsOnSave": {
    /* Imports are sorted and organized with eslint-plugin-perfectionist. */
    "source.sortImports": "never",
    "source.organizeImports": "never",
    "source.removeUnusedImports": "never",

    /* Apply ESLint fixes when saving files. */
    "source.fixAll.eslint": "explicit"
  },

  "eslint.run": "onSave",
  "eslint.format.enable": true,
  "eslint.validate": [
    "javascript",
    "typescript",
    "javascriptreact",
    "typescriptreact",
    "json",
    "markdown",
    "html",
    "css",
    "tailwindcss",
    "astro",
    "vue"
  ],

  /* Adjust these based on the features you're using to silently auto-fix the stylistic rules. */
  "eslint.rules.customizations": [
    { "rule": "*styl*", "severity": "off", "fixable": true },
    { "rule": "*sort*", "severity": "off", "fixable": true },
    { "rule": "*indent", "severity": "off", "fixable": true },
    { "rule": "*quotes", "severity": "off", "fixable": true },
    { "rule": "import*", "severity": "off", "fixable": true },
    { "rule": "*-spac*", "severity": "off", "fixable": true },
    { "rule": "*order-*", "severity": "off", "fixable": true },
    { "rule": "*newline*", "severity": "off", "fixable": true },
    { "rule": "*attribute*", "severity": "off", "fixable": true },
    { "rule": "package-json/order-properties", "severity": "off", "fixable": true },
    { "rule": "vue/max-len", "severity": "off", "fixable": true },
    { "rule": "vue/comma-dangle", "severity": "off", "fixable": true },
    { "rule": "vue/space-in-parens", "severity": "off", "fixable": true }
  ]
}

Formatting

This configuration uses ESLint Stylistic to format:

  • JavaScript and TypeScript (js, cjs, mjs, jsx, ts, cts, mts, tsx),
  • Astro (similar to jsx/tsx), and
  • Vue's <script> blocks.

HTML and Vue's <template> blocks are formatted with @html-eslint/eslint-plugin and eslint-plugin-vue, respectively.

For file types not handled by ESLint Stylistic—such as CSS, JSON, Yaml, and Markdown—you can use Prettier. To simplify the setup, this package also provides a customizable shared Prettier configuration.

  1. Install Prettier as a dev dependency:

    npm i -D prettier
  2. Create a Prettier config file in the root of your project (prettier.config.js):

    import prettierConfig from '@shayanthenerd/eslint-config/prettier';
    
    /** @type {import('prettier').Config} */
    export default {
      ...prettierConfig,
      semi: false, // Override `semi` from the shared config
    };

    Or if you prefer using TypeScript (prettier.config.ts):

    import type { Config } from 'prettier';
    
    import prettierConfig from '@shayanthenerd/eslint-config/prettier';
    
    export default {
      ...prettierConfig,
      semi: true, // Override `semi` from the shared config
    } satisfies Config;

Using ESLint Stylistic with Prettier

In this setup, Prettier formats everything that ESLint Stylistic doesn't. To prevent overlaps and potential race conditions, Prettier should only target files that ESLint Stylistic doesn't format.

package.json:

{
  "scripts": {
    "format": "prettier --write . '!**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx,html,vue,astro}' --cache"
  }
}

.vscode/settings.json:

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript][typescript][javascriptreact][typescriptreact][html][vue][astro][{**/package.json}]": {
  "editor.defaultFormatter": "dbaeumer.vscode-eslint"
  },
}

Using Prettier Alone

If you prefer to use Prettier as the only formatter, disable the stylistic configuration and let Prettier handle all formatting. Just make sure to avoid running lint and format scripts on the same files simultaneously.

package.json:

{
  "scripts": {
    "format": "prettier --write . --cache",
    "lint": "eslint --fix --cache --cache-location='node_modules/.cache/.eslintcache'"
  }
}

.vscode/settings.json:

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",

  /* On file save, code actions are run before format, so the following doesn't cause conflicts. */
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  }
}

API Reference

Some types are omitted or aliased for brevity.

import type { ESLint, Linter } from 'eslint';

type VueAttributeCategory =
  | 'SLOT'
  | 'EVENTS'
  | 'GLOBAL'
  | 'UNIQUE'
  | 'CONTENT'
  | 'DEFINITION'
  | 'OTHER_ATTR'
  | 'ATTR_STATIC'
  | 'ATTR_DYNAMIC'
  | 'CONDITIONALS'
  | 'LIST_RENDERING'
  | 'TWO_WAY_BINDING'
  | 'OTHER_DIRECTIVES'
  | 'RENDER_MODIFIERS'
  | 'ATTR_SHORTHAND_BOOL';

interface Overrides {
  name?: string,
  files?: (string | string[])[],
  ignores?: string[],
  plugins?: Record<string, ESLint.Plugin>,
  languageOptions?: Linter.Config['languageOptions'],
  settings?: Record<string, unknown>,
  rules?: ConfigRules, // The available rules in the current configuration object
}

interface Options {
  autoDetectDeps?: boolean | 'verbose',
  env?: 'browser' | 'bun' | 'deno' | 'node',
  gitignore?: false | string,
  packageDir?: string,
  tsConfig?: false | {
    filename?: string,
    rootDir: string,
  },

  project?: {
    basePath?: string,
    globals?: {
      astro?: boolean,
      audioWorklet?: boolean,
      browser?: boolean,
      bun?: boolean,
      commonjs?: boolean,
      deno?: boolean,
      node?: boolean,
      nodeBuiltin?: boolean,
      serviceworker?: boolean,
      sharedWorker?: boolean,
      vitest?: boolean,
      vue?: boolean,
      webextension?: boolean,
      worker?: boolean,
      custom?: Record<string, 'off' | boolean | 'readable' | 'readonly' | 'writable' | 'writeable'>,
    },
    ignores?: string[],
    linterOptions?: {
      noInlineConfig?: boolean,
      reportUnusedDisableDirectives?: 'off' | 'warn' | 'error',
      reportUnusedInlineConfigs?: 'off' | 'warn' | 'error',
    },
    name?: string,
    rules?: Linter.RulesRecord,
    settings?: Record<string, unknown>,
  },

  configs?: {
    astro?: boolean | {
      overrides?: Overrides,
    },
    base?: {
      functionStyle?: 'expression' | 'declaration',
      maxDepth?: number,
      maxNestedCallbacks?: number,
      overrides?: Overrides,
    },
    css?: boolean | {
      overrides?: Overrides,
    },
    html?: boolean | {
      idNamingConvention?: 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case',
      overrides?: Overrides,
    },
    importX?: boolean | {
      overrides?: Overrides,
    },
    markdown?: boolean | {
      allowedHtmlTags?: string[],
      frontmatter?: false | 'json' | 'toml' | 'yaml',
      language?: 'gfm' | 'commonmark',
      overrides?: Overrides,
    },
    next?: boolean | {
      overrides?: Overrides,
    },
    node?: boolean | {
      overrides?: Overrides,
    },
    nuxt?: boolean | {
      icon?: boolean | {
        component?: string,
      },
      image?: boolean,
      ui?: boolean | {
        prefix?: string,
      },
    },
    packageJson?: boolean | {
      overrides?: Overrides,
    },
    perfectionist?: boolean | {
      sortType?: 'custom' | 'natural' | 'unsorted' | 'line-length' | 'alphabetical' | 'subgroup-order',
      overrides?: Overrides,
    },
    promise?: boolean | {
      overrides?: Overrides,
    },
    react?: boolean | {
      accessibility?: boolean | {
        anchorComponents?: string[],
        headingComponents?: string[],
        imageComponents?: string[],
      },
      overrides?: Overrides,
    },
    stylistic?: boolean | {
      arrowParens?: 'always' | 'as-needed',
      indent?: number,
      jsxQuotes?: 'prefer-double' | 'prefer-single',
      maxAttributesPerLine?: number,
      maxConsecutiveEmptyLines?: number,
      maxLineLength?: number,
      memberDelimiterStyle?: 'semi' | 'comma',
      quotes?: 'double' | 'single' | 'backtick',
      selfCloseVoidHtmlElements?: 'never' | 'always',
      semi?: 'never' | 'always',
      trailingComma?: 'never' | 'always' | 'only-multiline' | 'always-multiline',
      overrides?: Overrides,
    },
    tailwind?: false | {
      config: string,
      cwd?: string,
      entryPoint?: string,
      ignoredUnknownClasses?: string[],
      multilineSort?: boolean,
      overrides?: Overrides,
    } | {
      config?: string,
      cwd?: string,
      entryPoint: string,
      ignoredUnknownClasses?: string[],
      multilineSort?: boolean,
      overrides?: Overrides,
    },
    test?: {
      maxNestedDescribe?: number,
      testFunction?: 'it' | 'test',
      cypress?: boolean | {
        overrides?: Overrides,
      },
      playwright?: boolean | {
        overrides?: Overrides,
      },
      storybook?: boolean | {
        overrides?: Overrides,
      },
      vitest?: boolean | {
        overrides?: Overrides,
      },
    },
    typescript?: boolean | {
      allowedDefaultProjects?: string[],
      methodSignatureStyle?: 'method' | 'property',
      removeUnusedImports?: boolean,
      typeDefinitionStyle?: 'type' | 'interface',
      overrides?: Overrides,
    },
    unicorn?: boolean | {
      overrides?: Overrides,
    },
    useBaseline?: boolean | {
      baseline?: number | 'newly' | 'widely',
      css?: {
        allowedAtRules?: AllowedAtRules,
        allowedFunctions?: AllowedFunctions,
        allowedMediaConditions?: AllowedMediaConditions,
        allowedProperties?: AllowedProperties,
        allowedPropertyValues?: AllowedPropertyValues,
        allowedSelectors?: AllowedSelectors,
        allowedUnits?: AllowedUnits,
      },
      javascript?: {
        ignoredFeatures?: string[],
        ignoredNodeTypes?: string[],
      },
      overrides?: Overrides,
    },
    vue?: boolean | {
      accessibility?: boolean | {
        accessibleChildComponents?: string[],
        anchorComponents?: string[],
        imageComponents?: string[],
      },
      allowedStyleAttributes?: ['plain' | 'module' | 'scoped', 'plain' | 'module' | 'scoped'],
      attributeHyphenation?: 'never' | 'always',
      attributesOrder?: (VueAttributeCategory | VueAttributeCategory[])[],
      blockLang?: {
        script?: 'js' | 'ts' | 'jsx' | 'tsx' | 'implicit',
        style?: 'css' | 'scss' | 'postcss' | 'implicit',
      },
      blocksOrder?: (
        | 'docs'
        | 'template'
        | 'script[setup]'
        | 'style[scoped]'
        | 'i18n[locale=en]'
        | 'script:not([setup])'
        | 'style:not([scoped])'
        | 'i18n:not([locale=en])'
      )[],
      componentNameCaseInTemplate?: 'kebab-case' | 'PascalCase',
      destructureProps?: 'never' | 'always' | 'only-when-assigned',
      ignoredUndefinedComponents?: string[],
      macrosOrder?: (
        | 'definePage'
        | 'defineEmits'
        | 'defineModel'
        | 'defineProps'
        | 'defineSlots'
        | 'defineCustom'
        | 'defineExpose'
        | 'defineOptions'
      )[],
      preferVBindSameNameShorthand?: 'never' | 'always',
      preferVBindTrueShorthand?: 'never' | 'always',
      restrictedElements?: (string | {
        element: string | string[],
        message: string,
      })[],
      restrictedStaticAttributes?: (string | {
        element: string,
        key: string,
        message: string,
        value: true | string,
      })[],
      vForDelimiterStyle?: 'in' | 'of',
      overrides?: Overrides,
    },
    zod?: boolean | {
      mini?: boolean,
      overrides?: Overrides,
    },
  },
}

Versioning Policy

This project adheres to The Semantic Versioning Standard. However, to facilitate rapid development and fast iteration, the following changes are considered non-breaking:

  • Updates to dependency versions
  • Modifications to rule options
  • Enabling or disabling rules and plugins

Under this policy, minor updates may introduce new linting errors, which could break your project's build pipeline. To prevent this, it's recommended to use an exact version. Alternatively, you can use a tilde (~) version range in your package.json file (e.g., "@shayanthenerd/eslint-config": "~1.2.3"), which will restrict updates to patches only, ensuring your project's build pipeline remains stable.

You can find a list of all available versions and their changelogs on the releases page.

Roadmap to v1.0.0

  • [x] Add integration for ESLint plugins such as eslint-plugin-n, eslint-plugin-unicorn, and more.
  • [x] Add support for React, Next, Astro, and Markdown.
  • [ ] Develop an interactive starter wizard to quickly scaffold the configurations for ESLint, Prettier, etc.

Contribution Guide

Contributions of all kinds are welcome. Please check out the CONTRIBUTING.md file.

Credits

This project was inspired by the work of Anthony Fu, whose generous contributions to the JavaScript and the ESLint ecosystem were instrumental in making it possible.

License

MIT License © 2025-PRESENT — Shayan Zamani