ESLint, Prettier and Stylelint, set up as one
import core from "ultracite/eslint/core";918 rules
import react from "ultracite/eslint/react";549 rules
import next from "ultracite/eslint/next";47 rules
export default [
...core,
...react,
...next,
];import config from "ultracite/prettier";
export default {
...config,80 columns, double quotes
plugins: ["prettier-plugin-tailwindcss"],
};export { default } from "ultracite/stylelint";standard + property order726 rules recommended. 918 with Ultracite.
ESLint runs nothing until you configure it, and every plugin’s recommended config is a separate thing to install and keep up. Ultracite’s core preset turns on most of the rules in ten plugins, and an opt-in preset adds the TypeScript rules that need your types.
- Rules in ESLint 10 and the eight plugins drawn here
- 1,190
- In their recommended configs, if you set each one up yourself
- 726
- On in Ultracite’s core preset, before any framework rules
- 918
unicornModern, idiomatic JavaScript
346 / 374
sonarjsBugs and code smells, from SonarQube
234 / 282
eslintESLint’s own rules
164 / 199
@typescript-eslintTypeScript, with 62 more in type-aware
57 / 127
jsdocJSDoc comments
19 / 78
import-xImports and exports
28 / 46
nNode.js modules and APIs
33 / 43
githubGitHub’s own conventions
21 / 24
promisePromises
14 / 17
Errors, never warnings
All 918 are errors, because an agent takes a zero exit code as done, and a warning doesn’t change it. That includes compat/compat, which flags browser APIs your browserslist targets don’t support, or Baseline’s when you set none.
132 rules that read your types
Core works from the syntax alone, without a tsconfig.json. The type-aware preset adds 100 typescript-eslint, SonarJS and Unicorn rules that need TypeScript’s type checker, such as no-floating-promises, and hands it to 32 SonarJS rules in core that use it when it’s there. It’s opt-in, with init --type-aware.
eslint --print-config on a TypeScript file. Plugins are drawn to one scale. Not drawn: unused-imports/no-unused-imports and compat/compat, one rule each.Fifteen framework presets, templates included
Each preset sets up the framework’s own ESLint plugin. For Vue, Svelte and Astro that includes its parser, so ESLint checks the markup, not just the script block. Six also have a type-aware add-on for their plugin’s rules that read types.
Rules per preset
Add the ones you usereact· React, hooks, jsx-a11y, React Doctor549vue· eslint-plugin-vuetemplates208vitest· @vitest/eslint-plugin70+1svelte· eslint-plugin-sveltetemplates69jest· eslint-plugin-jest63+4astro· eslint-plugin-astrotemplates53next· @next/eslint-plugin-next, React Doctor47angular· @angular-eslint45+6solid· eslint-plugin-solid34tanstack· Query, Router, React Doctor34+2nestjs· eslint-plugin-nestjs-typed22+3storybook· eslint-plugin-storybook16cypress· eslint-plugin-cypress12qwik· eslint-plugin-qwik11+1remix· eslint-plugin-remix2
+n rules in the preset’s type-aware add-on, which init --type-aware adds after it.
<script setup lang="ts">
defineProps<{ tasks: { id: string; title: string; notes: string }[] }>();
</script>
<template>
<ul>
<li v-for="task in tasks">
<span v-html="task.notes"></span>
{{ task.title }}
</li>
</ul>
</template>Terminal
$ npx ultracite check components/task-list.vue
All matched files use Prettier code style!
2:15error The "tasks" property should have a JSDoc comment vue/require-prop-comment
7:5error Elements in iteration expect to have 'v-bind:key' directives vue/require-v-for-key
8:13error 'v-html' directive can lead to XSS attack vue/no-v-html
Prettier formats it. ESLint finds one more.
The same component as on the other engines’ pages. ESLint’s autofix sorts the imports, then Prettier settles the formatting and class order. One rule finds a problem no autofix can settle, and that goes back to the agent.
As written
import { cn } from '@/lib/utils'
import { Slot } from '@radix-ui/react-slot'
import type { ReactNode } from 'react'
export function Button({ asChild, children }: { asChild?: boolean, children: ReactNode }) {
const Comp = asChild ? Slot : 'button'
return <Comp className={cn('text-sm px-4 bg-primary hover:bg-primary/90 rounded-md h-10 font-medium')}>{children}</Comp>
}After ultracite fix
import { Slot } from "@radix-ui/react-slot";
import { cn } from "@/lib/utils";
import type { ReactNode } from "react";
export function Button({
asChild,
children,
}: {
asChild?: boolean;
children: ReactNode;
}) {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(
"bg-primary hover:bg-primary/90 h-10 rounded-md px-4 text-sm font-medium"
)}
>
{children}
</Comp>
);
}Terminal
$ npx ultracite fix components/button.tsx
5:8error Expected a function expression func-style
✖ 1 problem (1 error, 0 warnings)
components/button.tsx 58ms
- prettier-plugin-tailwindcss
- Tailwind classes in the plugin’s order, in
classNameand incn,clsx,cva,tw,twMerge,twJoinandtvcalls. - import-x/order
- Imports grouped by ESLint’s autofix: built-ins, packages, your own modules, then type-only imports.
- eslint-config-prettier
- Turns off the ESLint rules that would fight Prettier.
fixruns ESLint’s fixes first and Prettier last, so Prettier settles the layout. - ultracite/eslint/type-aware
- Opt-in rules that read your types. With it,
strict-boolean-expressionsalso flags the optionalasChildin the condition.
The trade-off is time
ESLint and its plugins run in Node, and Prettier and Stylelint each run as a step of their own. That’s seconds where the native engines take a third of one. In an agent’s loop it’s less, because the hook only fixes the file that changed. The type-aware preset adds TypeScript’s type checker, which took the 420 files to 19.7 s.
42 files
ESLint + Prettier + Stylelint
Oxlint + Oxfmt
Biome
420 files
ESLint + Prettier + Stylelint
Oxlint + Oxfmt
Biome
- To check the one file an agent just edited
- 2.2s
- To check 42 files
- 3.91s
- To check 420 files
- 15.5s
ultracite check on an Apple M1 Pro, through each toolchain’s core preset: ESLint 10.12.0, Prettier 3.9.10 and Stylelint 17.16.0; Oxlint 1.87.0 and Oxfmt 0.71.0; and Biome 2.5.15, on the repo’s benchmark fixture copied to 42 and 420 files. Times include starting Node and the CLI. The same rules in your editor, your agent and CI
init points your editor at ESLint and Prettier and tells your agents how to run them, so a file passes or fails the same way wherever it’s checked.
{ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.organizeImports": "explicit" }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }VS Code, Cursor, Devin Desktop
Fix and format on save
ESLint applies its fixes and sorts imports when you save, and Prettier formats the file, through the editors’ own ESLint and Prettier extensions.
"TSX": { "code_actions_on_format": { "source.fixAll.eslint": true, "source.organizeImports.eslint": true }, "formatter": "prettier" }Zed
Fix as you format
Zed applies ESLint’s fixes and sorts imports, then formats with its Prettier integration, using your Prettier config and plugins.
Terminal
$ npx ultracite check
[warn] src/data.ts
src/api.ts
13:9 error Prefer `AbortSignal.timeout()` over manually aborting an `AbortController` with `setTimeout()` unicorn/prefer-abort-signal-timeout
src/data.ts
20:14 error Use `Array#toSorted()` instead of `Array#sort()` unicorn/no-array-sort
Coding agents
A count of what fix will settle
ESLint ends its report with how many of the problems
--fixcan settle, so an agent knows to runultracite fixbefore working through the rest.initwrites the rules intoAGENTS.md, which all 40 agents read.
ESLint questions, answered
- What does Ultracite add on top of ESLint?
- ESLint runs no rules until you configure it. Ultracite’s core preset turns on 918 across ESLint and ten plugins, among them typescript-eslint, Unicorn, SonarJS and import-x. Framework presets add more: 549 rules for React, 47 for Next.js and 208 for Vue. The opt-in type-aware preset adds 100 rules that read your types.
initalso writes Prettier and Stylelint configs, editor settings for VS Code and Zed,AGENTS.mdfor coding agents, and optional post-edit hooks. - How do I turn off a rule?
- Add a config object after the presets in
eslint.config.mjs, such as{ rules: { "no-console": "off" } }. In a flat config, later objects win. For one line, use// eslint-disable-next-line no-console. - Why is it slower than Oxlint and Biome?
- ESLint and its plugins run in Node, and Prettier and Stylelint run as steps of their own. In our benchmark the toolchain checked 42 files in 3.91 s and 420 files in 15.5 s, where Oxlint took 340 ms and 344 ms. Checking the single file an agent edited took 2.2 s. The type-aware preset adds TypeScript’s type checker, which took the 420 files to 19.7 s.
- When should I pick ESLint over Oxlint or Biome?
- When you depend on something only ESLint has: a plugin with no native port, a Prettier plugin, or template linting for Vue, Svelte and Astro through each framework’s own parser. Otherwise start with Oxlint, which keeps ESLint’s rule names and runs ESLint plugins through its JS plugin API.
- Do I need a tsconfig.json?
- Not for the core preset, which lints TypeScript from its syntax alone. The type-aware preset does: its rules use
projectService, which finds thetsconfig.jsonthat includes each file, and a.tsor.tsxfile that notsconfig.jsonincludes fails with a parsing error.init --type-awareadds the preset and offers to turn onstrictNullChecks, which some of its rules need. - Which files does it lint?
- ESLint lints JavaScript and TypeScript modules (
.js,.jsx,.mjs,.cjs,.ts,.tsx,.mts,.cts) and inline scripts in HTML files, and the Vue, Svelte and Astro presets add their components. It doesn’t lint JSON, which Prettier formats. Stylelint lints.css,.scssand.less, extendingstylelint-config-standardandstylelint-config-idiomatic-order, reads SCSS and Less through thepostcss-scssandpostcss-lesssyntaxesinitinstalls, and accepts Tailwind v4’s at-rules, functions and theme variables. - Can I use Prettier plugins?
- Yes.
prettier.config.mjsis an ordinary Prettier config that spreads Ultracite’s settings, so add plugins to itspluginsarray.initputsprettier-plugin-tailwindcssthere, andprettier-plugin-svelteorprettier-plugin-astrobefore it when you pick those frameworks. Keep the Tailwind plugin last, as its docs require.
Keep going
- ReferenceConfigurationFramework presets, type-aware rules and overriding rules.
- ProviderUltracite for OxlintThe same standard on a native engine, with ESLint’s rule names.
- GuideAgent hooksRun
ultracite fixon just the file your agent edited, in 8 agents and editors. - ProvidersCompare the enginesOxlint, Biome and ESLint side by side: rules, speed, tradeoffs.
Ship less slop
One command sets up your linter, formatter, editor and agents. Works with Oxlint, Biome and ESLint.