Sharable ESLint configuration preset for Poupe UI projects with TypeScript, Vue.js, and Tailwind CSS support.
✅ Includes:
- eslint recommended rules
- stylistic recommended formatting rules
- typescript-eslint integration
- unicorn rules
- perfectionist rules for sorting imports, exports, types, and more
- vue recommended rules with TypeScript support
- tsdoc rules for TypeScript documentation
- markdownlint rules for Markdown files, including link and table-consistency checks
- jsonc rules for JSON and package.json files
- css rules for CSS files with Tailwind CSS support
- Poupe UI recommended rules
Note
This preset uses the new ESLint flat config format and supports ESLint v9 or v10 on Node.js 20.19+, 22.13+, or 24+.
ESLint parses your TypeScript with the compiler the preset brings, currently TypeScript 6 — not the one your project builds with, which is your own choice. See TypeScript for the tested versions.
eslint and @eslint/js are peer dependencies — the preset does not
bundle them, so you must install them yourself alongside it:
pnpm install -D eslint @eslint/js @poupe/eslint-configCreate eslint.config.mjs in your project root:
// @ts-check
import poupeConfig from '@poupe/eslint-config';
export default poupeConfig;For Nuxt.js applications (with @nuxt/eslint):
// @ts-check
import { withPoupe } from '@poupe/eslint-config';
import withNuxt from './.nuxt/eslint.config.mjs';
export default withPoupe(withNuxt(), {
rules: {
// custom rule overrides
},
});For Nuxt modules (with @nuxt/eslint-config):
// @ts-check
import { createConfigForNuxt } from '@nuxt/eslint-config/flat';
import { withPoupe } from '@poupe/eslint-config';
export default withPoupe(createConfigForNuxt({
features: {
tooling: true, // Enables rules for module authors
stylistic: true, // Enables formatting rules
},
}));Custom configuration:
// @ts-check
import { defineConfig } from '@poupe/eslint-config';
export default defineConfig({
rules: {
// rule overrides
},
});The preset installs its own TypeScript, >=6.0.3 <6.1.0, and ESLint
parses your sources through it. The version your project declares is the
one your build and your editor use; it does not change what ESLint
parses with. Nuxt is the exception: @nuxt/eslint and
@nuxt/eslint-config bring their own @typescript-eslint/parser, which
resolves TypeScript from your project rather than from this preset.
| Your TypeScript | ESLint | Tested |
|---|---|---|
| 5.9 | 9 | Yes |
| 6.0 | 10 | Yes, including Nuxt applications and modules |
| 7.0 | 10 | Yes, with pnpm, from 0.10.2 |
Versions below 5.9 and combinations not listed are untested.
Caveats with TypeScript 7:
- Use 0.10.2 or later. Earlier releases took TypeScript as a peer dependency, so the package manager handed them your TypeScript 7, and ESLint stops while loading the configuration with "typescript-eslint does not support TS 7.0".
- Syntax is read through the TypeScript 6 API. Anything TypeScript 7 accepts that TypeScript 6 does not will not parse.
- Nuxt is untested on TypeScript 7. Its parser would receive your TypeScript 7, not the preset's TypeScript 6.
- npm and Yarn are untested. pnpm gives each package its own
dependencies, so the preset's TypeScript 6 cannot be confused with
yours. npm and Yarn hoist packages into a shared tree, and whether
typescript-eslintthen finds TypeScript 6 or 7 has not been checked.
This configuration includes advanced CSS linting with Tailwind CSS v4 syntax support. The CSS configuration uses an intelligent filtering system that:
- Applies CSS-specific rules from
@eslint/css - Supports Tailwind CSS v4 syntax including theme functions and modifiers
- Automatically disables JavaScript-specific rules for CSS files
- Keeps relevant cross-language rules (like
filename-casefrom unicorn)
The filtering system categorizes plugins and rules to ensure only appropriate rules apply to CSS files. See AGENTS.md for implementation details.
This configuration automatically lints the following file types:
- JavaScript:
.js,.mjs,.cjs - TypeScript:
.ts,.tsx - Vue.js:
.vue(Single File Components) - JSON/JSONC:
.json,.jsonc,package.json - Markdown:
.md - CSS:
.css,.postcss, Vue<style>blocks (with Tailwind CSS v4 syntax support)
This package uses its own ESLint configuration for validation, ensuring quality and serving as a real-world test case.
This configuration includes eslint-plugin-perfectionist which automatically
organizes your imports and exports. The sorting happens automatically when you
run eslint --fix or save files with ESLint auto-fix enabled in your editor:
// Before
import { z } from 'zod';
import React from 'react';
import { useState, useEffect } from 'react';
import { Button } from './components/Button';
import axios from 'axios';
import type { User } from '../types';
import { config } from './config';
// After (automatically fixed by ESLint)
import type { User } from '../types';
import axios from 'axios';
import React from 'react';
import { z } from 'zod';
import { config } from './config';
import { Button } from './components/Button';
import { useEffect, useState } from 'react';The configuration respects empty lines as block separators, allowing you to organize imports into logical groups:
// Core React imports
import React from 'react';
import { useState, useEffect } from 'react';
// External libraries
import axios from 'axios';
import { z } from 'zod';
// Internal modules
import { config } from './config';
import { Button } from './components/Button';Each group separated by empty lines is sorted independently, preserving your intended organization while maintaining consistent ordering within each group.
Union and intersection types are sorted in natural order. Use comments to create logical groups:
// Before
type Status = 'error' | 'loading' | 'success' | 'idle';
// After (automatically fixed by ESLint)
type Status = 'error' | 'idle' | 'loading' | 'success';
// With comment-based grouping
type Status =
// Active states
| 'loading'
| 'processing'
// Inactive states
| 'error'
| 'idle'
| 'success';The configuration also sorts these constructs in natural alphabetical order,
all auto-fixable via eslint --fix:
- Interfaces and object types — sorted by property name, with blank-line grouping support
- Enums — sorted by member name (numeric enums sorted by value)
- Classes — sorted by member type and visibility (static first, then instance, public before private), with blank-line grouping
- Heritage clauses —
extendsandimplementslists - Variable declarations — destructured bindings
- Import/export attributes —
import ... with { ... }keys
// @ts-check
import { defineConfig } from '@poupe/eslint-config';
export default defineConfig({
rules: {
// Disable specific rules
'unicorn/filename-case': 'off',
// Customize rule severity and options
'@typescript-eslint/no-unused-vars': ['warn', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
},
});// @ts-check
import { configs, withConfig } from '@poupe/eslint-config';
// Import only specific configurations
export default withConfig(
configs.javascript,
configs.typescript,
configs.stylistic,
configs.vue,
// Add custom overrides
{
rules: {
'vue/multi-word-component-names': 'off',
},
},
);When composing configs from different packages, use withPoupe to
layer Poupe configs on top of an upstream config. It handles async
resolution and deduplicates plugin instances automatically:
// @ts-check
import { withPoupe } from '@poupe/eslint-config';
import someOtherConfig from 'some-other-eslint-config';
export default withPoupe(someOtherConfig);For lower-level control, reconcilePlugins deduplicates plugin
instances in a flat config array using a first-wins strategy:
// @ts-check
import { defineConfig, reconcilePlugins } from '@poupe/eslint-config';
import someOtherConfig from 'some-other-eslint-config';
export default reconcilePlugins([
...someOtherConfig,
...defineConfig(),
]);In addition to the rules from included plugins, this configuration adds these custom rules:
- Enforces explicit return types on exported functions
- Requires consistent type imports (
import type) - Disables
@ts-comment directives
- Enforces multi-word component names
- Requires
defineEmitsanddefinePropstype declarations - Ensures proper
v-modelusage patterns
- Enforces camelCase naming convention
- Allows
i,j,kas loop counters and array indices - Requires explicit radix in
parseInt - Prevents console statements in production code
- Single quotes for strings
- Semicolons required
- 1tbs (one true brace style)
- 2-space indentation for web files
- 80-character line limit for Markdown (tables exempt)
If you're migrating from a legacy .eslintrc configuration:
-
Remove old config files: Delete
.eslintrc,.eslintrc.js,.eslintrc.json, etc. -
Update dependencies:
pnpm remove eslint-config-* eslint-plugin-* pnpm install -D eslint @eslint/js @poupe/eslint-config
-
Create new config: Add
eslint.config.mjsas shown in Getting Started -
Update scripts: ESLint automatically finds
eslint.config.mjs{ "scripts": { "lint": "eslint --fix .", "lint:check": "eslint ." } } -
Handle breaking changes:
- Some rules have been renamed or moved to different plugins
- The flat config uses different property names (
filesinstead ofoverrides) - Plugin names are now used as object keys instead of strings
The examples/ directory contains working examples demonstrating how to use
this ESLint configuration in different scenarios:
- playground-standard - Basic JavaScript/TypeScript projects (inherits the workspace ESLint, currently v10; pins TypeScript 6, the major the preset declares for its own parsing)
- playground-eslint9 - Basic JavaScript/TypeScript projects (pinned to ESLint 9, the lower bound of the peer range, and to TypeScript 5.9)
- playground-ts7 - Basic JavaScript/TypeScript projects (TypeScript 7 as the only compiler)
- playground-nuxt - Nuxt.js applications
- playground-nuxt-module - Nuxt module development
This project uses pnpm workspaces. You can run commands on all examples:
# Install dependencies for all workspaces
pnpm install
# Check all examples without fixing
pnpm -r --filter "./examples/*" lint:check
# Lint and fix all examples
pnpm -r --filter "./examples/*" lint
# Run a specific example
pnpm --filter "playground-standard" lintEnsure you've installed the package and are using the correct import path:
pnpm install -D @poupe/eslint-configFor Nuxt applications, use withPoupe from the root entry point:
- Nuxt apps:
withPoupe(withNuxt()) - Nuxt modules:
withPoupe(createConfigForNuxt(...))
This should not happen with the default configuration. If it does, ensure you're using the latest version and report an issue.
These warnings help the CSS filtering system learn about new rules. They're informational and don't affect functionality.
eslint-plugin-tsdoc pins @typescript-eslint/utils to ~8.56.0, whose
typescript peer range stops below 6.0.0, and the preset supplies
TypeScript 6 to its own dependency chain. It is an install-time warning
about a range, not a failure. To silence it, override the package in your
project root:
npm and Yarn users can use the equivalent overrides / resolutions field.
To debug ESLint configuration issues:
# Show resolved configuration for a specific file
eslint --print-config path/to/file.js
# Verbose per-file linting trace
env DEBUG=eslint:linter pnpm lint:check
# Debug config resolution
env DEBUG=eslint:eslint pnpm lintFor AI assistants working with this codebase, refer to AGENTS.md for detailed guidance and conventions.
src/
├── configs/ # Individual ESLint rule configurations
├── core/ # Core utilities and type definitions
├── config.ts # Main configuration builder
├── configs.ts # Configuration presets
└── index.ts # Main entry point
- Make changes to configuration files in
src/configs/ - Run
pnpm buildto compile the package - Test with
pnpm lint(self-linting) - Test in examples:
pnpm -r --filter "./examples/*" lint