Optional Presets
Opt-in Ultracite presets for ESLint parity, TSDoc, anti-slop rules, shadcn design systems and gdp-ts authorization proofs, and how to set each one up.
Ultracite’s core and framework presets are on when you run init. The presets on this page are opt-in: each adds rules that suit some projects and not others, and most run through Oxlint’s JS plugin API, which is slower than Oxlint’s native rules. The JS plugin, TSDoc, anti-slop and @shadcn/lint presets are for Oxlint; the gdp-ts preset works with Oxlint and ESLint.
ESLint Parity
The core preset — and the react, next, and tanstack framework presets — run entirely on Oxlint’s native, Rust-powered rules for maximum speed. If you want to close the remaining gap with the ESLint preset, Ultracite ships an opt-in js-plugins preset that runs eslint-plugin-github, eslint-plugin-sonarjs, and oxlint-plugin-react-doctor through Oxlint’s JS plugin support. TSDoc syntax and public API documentation checks are available as separate, explicitly selected plugins below; they are not added to the full preset, so existing users do not gain new dependencies when upgrading.
This is off by default because it adds dependencies and runs a slower JavaScript lint pass instead of the native Rust one. You can enable it during setup: interactive ultracite init lets you choose individual JS plugins when you choose Oxlint, and non-interactive setup accepts package names via --js-plugins.
npx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctorpnpm dlx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctoryarn dlx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctorbunx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctornubx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctoraube dlx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctorReact Doctor’s framework-specific rules are split into per-framework add-on presets so they only run where they apply — nextjs-* rules like nextjs-no-img-element fire on plain <img>/<a> JSX and would false-positive in non-Next.js apps. init wires these up automatically when you pick the matching framework together with oxlint-plugin-react-doctor:
ultracite/oxlint/next/js-plugins— React Doctor’s Next.js rulesultracite/oxlint/tanstack/js-plugins— React Doctor’s TanStack Query/Router/Start rules
If you’re adding the preset manually instead of through init, install the plugins yourself:
npm install -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctorpnpm add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctoryarn add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctorbun add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctornub add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctoraube add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctorimport { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";
import jsPlugins, { jsPluginSettings } from "ultracite/oxlint/js-plugins";
export default defineConfig({
extends: [core, jsPlugins],
ignorePatterns: core.ignorePatterns,
jsPlugins: jsPlugins.jsPlugins,
settings: jsPluginSettings,
});
In a Next.js or TanStack app, add the matching add-on preset as well:
import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";
import next from "ultracite/oxlint/next";
import jsPlugins, { jsPluginSettings } from "ultracite/oxlint/js-plugins";
import nextJsPlugins from "ultracite/oxlint/next/js-plugins";
export default defineConfig({
extends: [core, next, jsPlugins, nextJsPlugins],
ignorePatterns: core.ignorePatterns,
jsPlugins: jsPlugins.jsPlugins,
settings: jsPluginSettings,
});
TSDoc and Public API Documentation
Two more JS plugins check the documentation in TypeScript files (.ts, .tsx, .mts, and .cts). They aren’t part of the full js-plugins preset, so they only run when you select them:
eslint-plugin-tsdoc, registered astsdoc, validates TSDoc syntax.eslint-plugin-jsdoc, registered asjsdoc-jsbecause Oxlint reservesjsdocfor its native plugin, requires a doc comment on every exported function, class, interface, type alias, and enum. It also covers the public methods and getters of exported classes, and exports that wrap a function, such asexport const Button = forwardRef(...).
Selecting eslint-plugin-jsdoc also turns on Oxlint’s native jsdoc/require-param and jsdoc/require-returns, so every JSDoc comment on a function, exported or not, must describe its parameters and return value. Parameter and return types stay off, so TypeScript types aren’t repeated in comments.
Selecting eslint-plugin-tsdoc adjusts core’s native jsdoc/* rules to fit TSDoc: tags such as @remarks and @typeParam are allowed, and generators don’t need @yields. With both plugins selected, a destructured parameter is documented by its own name (@param props - ...) rather than property by property. TSDoc has no {Type} syntax, so write the type on a @throws tag as a link: @throws {@link RangeError} When the input is empty.
npx ultracite init --linter oxlint --js-plugins eslint-plugin-tsdoc eslint-plugin-jsdocpnpm dlx ultracite init --linter oxlint --js-plugins eslint-plugin-tsdoc eslint-plugin-jsdocyarn dlx ultracite init --linter oxlint --js-plugins eslint-plugin-tsdoc eslint-plugin-jsdocbunx ultracite init --linter oxlint --js-plugins eslint-plugin-tsdoc eslint-plugin-jsdocnubx ultracite init --linter oxlint --js-plugins eslint-plugin-tsdoc eslint-plugin-jsdocaube dlx ultracite init --linter oxlint --js-plugins eslint-plugin-tsdoc eslint-plugin-jsdoc--js-plugins sets the whole selection, so list any plugins you already use, such as eslint-plugin-github. A config that extends the full js-plugins preset keeps its plugins.
If you’re configuring Oxlint manually, install the plugins and select them:
npm install -D eslint-plugin-tsdoc eslint-plugin-jsdocpnpm add -D eslint-plugin-tsdoc eslint-plugin-jsdocyarn add -D eslint-plugin-tsdoc eslint-plugin-jsdocbun add -D eslint-plugin-tsdoc eslint-plugin-jsdocnub add -D eslint-plugin-tsdoc eslint-plugin-jsdocaube add -D eslint-plugin-tsdoc eslint-plugin-jsdocimport { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";
import { selectJsPlugins } from "ultracite/oxlint/js-plugins";
const jsPlugins = selectJsPlugins(["tsdoc", "jsdoc-js"]);
export default defineConfig({
extends: [core, jsPlugins],
ignorePatterns: core.ignorePatterns,
jsPlugins: jsPlugins.jsPlugins,
});
Anti-Slop Rules
Ultracite also ships an opt-in anti-slop preset built on anti-slop, Dillon Mulroy’s set of opinionated Oxlint rules that reject low-evidence, low-signal TypeScript and JavaScript patterns — unjustified type assertions, unknown leaking through function signatures, Reflect-based property access, module mocking, and similar escape hatches.
Upstream is deliberately not published to npm (its distribution model is “copy the source into your repo”), so Ultracite vendors a self-contained bundled build of the plugin — there is nothing extra to install. Like js-plugins, this preset is off by default because it is strongly opinionated and runs through Oxlint’s slower JS-plugin pass.
You can enable it during setup: it appears as anti-slop in the same JS-plugins prompt as the packages above, and non-interactive setup accepts it via --js-plugins:
npx ultracite init --linter oxlint --js-plugins anti-sloppnpm dlx ultracite init --linter oxlint --js-plugins anti-slopyarn dlx ultracite init --linter oxlint --js-plugins anti-slopbunx ultracite init --linter oxlint --js-plugins anti-slopnubx ultracite init --linter oxlint --js-plugins anti-slopaube dlx ultracite init --linter oxlint --js-plugins anti-slopOr add it to your config manually:
import { defineConfig } from "oxlint";
import antiSlop from "ultracite/oxlint/anti-slop";
import core from "ultracite/oxlint/core";
export default defineConfig({
extends: [core, antiSlop],
ignorePatterns: core.ignorePatterns,
});
All fifteen rules are enabled at the error level, including require-safety-comment-for-type-assertion (every as assertion needs a SAFETY: comment stating the checked invariant), no-unknown-returns / no-unknown-parameters (parse values into named domain types at boundaries), and no-module-mocking (inject dependencies instead of mocking modules). If a rule doesn’t fit your codebase — no-module-mocking is a common example in test-heavy projects — turn it off in your own config’s rules block.
Design System Rules with @shadcn/lint
Ultracite also ships an opt-in shadcn preset built on @shadcn/lint, shadcn’s agent-first linter for Tailwind v4 design systems. It reads your components, variants, and theme, and when a class breaks the design system the error explains what is wrong and what to use instead — the component’s variants, sizes, theme tokens, and the file to edit. shadcn/ui is not required: it works with any Tailwind v4 component directory and theme.
Like js-plugins, this preset is off by default because it adds a dependency and runs through Oxlint’s slower JS-plugin pass. You can enable it during setup: it appears as @shadcn/lint in the same JS-plugins prompt as the packages above, and non-interactive setup accepts it via --js-plugins:
npx ultracite init --linter oxlint --js-plugins @shadcn/lintpnpm dlx ultracite init --linter oxlint --js-plugins @shadcn/lintyarn dlx ultracite init --linter oxlint --js-plugins @shadcn/lintbunx ultracite init --linter oxlint --js-plugins @shadcn/lintnubx ultracite init --linter oxlint --js-plugins @shadcn/lintaube dlx ultracite init --linter oxlint --js-plugins @shadcn/lintOr add it to your config manually:
npm install -D @shadcn/lintpnpm add -D @shadcn/lintyarn add -D @shadcn/lintbun add -D @shadcn/lintnub add -D @shadcn/lintaube add -D @shadcn/lintimport { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";
import shadcn from "ultracite/oxlint/shadcn";
export default defineConfig({
extends: [core, shadcn],
ignorePatterns: core.ignorePatterns,
jsPlugins: shadcn.jsPlugins,
});
All six rules are enabled at the error level:
| Rule | What it catches |
|---|---|
no-restyle |
Restyling a design-system component through className. |
no-raw-colors |
Raw palette colors such as bg-pink-500 and undeclared tokens. |
no-arbitrary-values |
Arbitrary values such as p-[13px] when a scale value exists. |
no-inline-styles |
Inline style props and <style> elements. |
no-unknown-classes |
Classes your Tailwind cannot generate, such as rounded-huge. |
require-static-classes |
Component class values the linter cannot read, such as `bg-${color}`. |
no-restyle and no-arbitrary-values use the upstream recommended allow: ["layout"] policy: pages may place a component (mt-4, w-full, flex-1, absolute) but its padding, colors, typography, and shape stay in the component’s variants. The preset also turns no-restyle, no-arbitrary-values, and require-static-classes off inside **/components/ui/**, where components style themselves and call their own variant functions; no-raw-colors, no-inline-styles, and no-unknown-classes stay on there.
Components and the theme are discovered from components.json, or from components/ui / src/components/ui next to the nearest package.json. If your design system lives elsewhere, point the plugin at it with settings.shadcn on your root config — Oxlint does not merge settings from extended configs — and add a matching override for that directory:
export default defineConfig({
extends: [core, shadcn],
ignorePatterns: core.ignorePatterns,
jsPlugins: shadcn.jsPlugins,
settings: {
shadcn: { ui: "@workspace/ui/components" },
},
overrides: [
{
files: ["packages/ui/src/components/**"],
rules: {
"shadcn/no-arbitrary-values": "off",
"shadcn/no-restyle": "off",
"shadcn/require-static-classes": "off",
},
},
],
});
Per-component policies (contracts), custom messages, and exceptions for external stylesheets are configured on the rules themselves — see the @shadcn/lint documentation. no-unknown-classes asks your installed Tailwind v4 which classes exist; when Tailwind or the theme cannot be loaded it falls back to a bundled grammar and warns once.
Authorization Proofs with gdp-ts
Ultracite also ships an opt-in gdp preset for gdp-ts, a TypeScript take on Ghosts of Departed Proofs. Sensitive functions demand a proof about their exact arguments (UserIsProjectAdmin<U, P>) that only a trusted module in proofs/ can mint, so a skipped or mismatched authorization check is a compile error. TypeScript can’t stop a proof being forged with {} as UserIsProjectAdmin<U, P>; these rules close that gap.
The lint plugin ships inside @gdp-ts/core, so once your codebase uses gdp-ts there is nothing extra to install. Add the preset after core:
import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";
import gdp from "ultracite/oxlint/gdp";
export default defineConfig({
extends: [core, gdp],
ignorePatterns: core.ignorePatterns,
});
The preset follows upstream’s strict mode:
| Rule | Applies | What it catches |
|---|---|---|
gdp-ts/no-define-proof |
outside proofs/ |
Importing or calling defineProof to mint a proof outside a trusted module. |
gdp-ts/no-proof-assertion |
outside proofs/ |
Type assertions to Named, Proof, or any type imported from a proofs path. |
gdp-ts/no-type-assertion |
outside proofs/ and lib/ids.ts |
Every other type assertion except as const. The honest path never needs one. |
gdp-ts/no-exported-prover |
inside proofs/ |
Exporting a prover, which would let any module mint the proof. |
Upstream’s strict-mode no-any is left off because core’s typescript/no-explicit-any already bans any everywhere. Inside proofs/, typescript/no-empty-interface and typescript/no-empty-object-type allow an empty interface that extends a single type, which is how gdp-ts declares a proof (interface UserIsProjectAdmin<U, P> extends Proof<"UserIsProjectAdmin", [U, P]> {}). Core’s no-redeclare stays on, so give the prover its own name (const prover = defineProof("UserIsProjectAdmin")) instead of reusing the proof interface’s name as the upstream recipe does.
The preset follows the upstream layout: trusted modules in proofs/ and branded-id constructors in lib/ids.ts. For a different layout, add overrides with the same shape in your own config.
On ESLint
The same preset ships for ESLint. Spread it after core:
import core from "ultracite/eslint/core";
import gdp from "ultracite/eslint/gdp";
export default [...core, ...gdp];
It matches the Oxlint preset: upstream’s strict mode stops proofs being forged with type assertions or minted outside proofs/, and empty proof interfaces (interface X<P> extends Proof<"X", [P]> {}) are allowed inside proofs/. It also sets @typescript-eslint/no-unused-vars to ignore _-prefixed arguments, as Oxlint does by default, because sensitive functions take proofs they never read at runtime (_proof: UserIsProjectAdmin<U, P>).