Skip to content
Ultracite
Esc
↑↓navigate↵open⌘Jpreview
On this page

Oxlint

Explore Ultracite's Oxlint and Oxfmt setup for teams that prioritize lint speed, shared presets, and lightweight editor defaults.

Oxlint is part of the Oxc project, a collection of high-performance JavaScript tools written in Rust. Paired with Oxfmt for formatting, this toolchain is ideal for large codebases where speed is critical.

Benefits

  • 50-100x faster — Lint your entire codebase in milliseconds
  • 15 plugin equivalents — Built-in support for React, TypeScript, Next.js, Vue, Jest, Vitest, and more
  • Bug-focused rules — Prioritizes catching real bugs over stylistic issues
  • High signal-to-noise ratio — Fewer false positives, more actionable feedback
  • Oxc ecosystem — Part of a larger project with parser, resolver, transformer, and minifier
  • Drop-in ready — Works alongside existing setups or as a complete replacement

Usage

Ultracite generates two TypeScript config files for Oxlint. They’re named oxlint.config.ts and oxfmt.config.ts in an ES module package ("type": "module" in package.json) and oxlint.config.mts and oxfmt.config.mts otherwise, because Node only loads ES module syntax from a .ts file in an ES module package. init never changes "type". The examples on this page use .ts:

import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";

export default defineConfig({
  extends: [core],
  ignorePatterns: core.ignorePatterns,
});
import { defineConfig } from "oxfmt";
import ultracite from "ultracite/oxfmt";

export default defineConfig({
  ...ultracite,
});

Add framework presets as needed:

import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";
import next from "ultracite/oxlint/next";
import react from "ultracite/oxlint/react";

export default defineConfig({
  extends: [core, react, next],
  ignorePatterns: core.ignorePatterns,
});

ESLint Parity (Optional)

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.

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-doctor
pnpm dlx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
yarn dlx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
bunx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
nubx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
aube dlx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor

React 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 rules
  • ultracite/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-doctor
pnpm add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
yarn add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
bun add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
nub add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
aube add -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
import { 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,
});

Anti-Slop Rules (Optional)

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-slop
pnpm dlx ultracite init --linter oxlint --js-plugins anti-slop
yarn dlx ultracite init --linter oxlint --js-plugins anti-slop
bunx ultracite init --linter oxlint --js-plugins anti-slop
nubx ultracite init --linter oxlint --js-plugins anti-slop
aube dlx ultracite init --linter oxlint --js-plugins anti-slop

Or 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 (Optional)

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/lint
pnpm dlx ultracite init --linter oxlint --js-plugins @shadcn/lint
yarn dlx ultracite init --linter oxlint --js-plugins @shadcn/lint
bunx ultracite init --linter oxlint --js-plugins @shadcn/lint
nubx ultracite init --linter oxlint --js-plugins @shadcn/lint
aube dlx ultracite init --linter oxlint --js-plugins @shadcn/lint

Or add it to your config manually:

npm install -D @shadcn/lint
pnpm add -D @shadcn/lint
yarn add -D @shadcn/lint
bun add -D @shadcn/lint
nub add -D @shadcn/lint
aube add -D @shadcn/lint
import { 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.

Configuration Approach

Ultracite’s Oxlint configuration uses an opt-out approach. This means we explicitly enable rules from every category at the error level, then selectively disable rules that are too strict or don’t fit our opinionated defaults. This ensures maximum bug-catching coverage while avoiding noise.

Rule Categories

Oxlint organizes rules into the following categories:

Correctness

Rules that catch definite bugs and errors. These are high-confidence rules that identify code that is almost certainly wrong.

Suspicious

Rules that catch code patterns that are likely bugs or mistakes. These have a slightly higher false-positive rate but catch important issues.

Pedantic

Rules that enforce stricter coding standards. These are more opinionated and may require more effort to satisfy.

Performance

Rules that identify performance issues and suggest optimizations for better runtime efficiency.

Restriction

Rules that restrict certain language features or patterns. These are typically project-specific preferences.

Style

Rules that enforce consistent code style across the codebase.

Included Plugins

Oxlint includes built-in support equivalent to these ESLint plugins:

  • eslint — Core JavaScript rules
  • typescript — TypeScript-specific rules
  • unicorn — Opinionated code quality rules
  • oxc — Oxc-specific optimizations
  • import — Import/export validation
  • jsdoc — JSDoc comment validation
  • node — Node.js-specific rules
  • promise — Promise and async/await best practices
  • jest / vitest — Testing framework rules (enabled via the jest / vitest framework presets)

For extra ESLint parity, the optional js-plugins preset adds github (eslint-plugin-github), sonarjs (eslint-plugin-sonarjs), and react-doctor (oxlint-plugin-react-doctor) via Oxlint’s JS plugin support. See ESLint Parity — it is not enabled by default. The optional shadcn preset adds shadcn (@shadcn/lint) the same way — see Design System Rules.

Adding Third-Party Plugins

Oxlint supports community plugins for additional rules beyond the built-in set. For example, Ultracite’s Biome preset includes cognitive complexity checking via noExcessiveCognitiveComplexity, but Oxlint only has the standard cyclomatic complexity rule built in. You can close this gap with a third-party plugin like oxlint-plugin-complexity:

npm install -D oxlint-plugin-complexity
pnpm add -D oxlint-plugin-complexity
yarn add -D oxlint-plugin-complexity
bun add -D oxlint-plugin-complexity
nub add -D oxlint-plugin-complexity
aube add -D oxlint-plugin-complexity
import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";

export default defineConfig({
  extends: [core],
  ignorePatterns: core.ignorePatterns,
  jsPlugins: ["oxlint-plugin-complexity"],
  rules: {
    "complexity/complexity": ["error", { cognitive: 15 }],
  },
});

This approach lets you opt in to additional checks without Ultracite taking on third-party dependencies in its core preset.

Effect

Effect’s type-aware diagnostics — floating Effects, unhandled error and requirement channels, Layer and Scope mistakes, and Effect-native alternatives to console, Date, process.env, and friends — are available to Oxlint through @effect/tsgo. Ultracite doesn’t ship an Effect preset because the effecttsgo plugin isn’t part of stock Oxlint: @effect/tsgo replaces the installed oxlint and oxlint-tsgolint binaries with builds that include it, and only supports specific versions of each. Layer it on top of core yourself:

npm install -D @effect/tsgo oxlint-tsgolint
pnpm add -D @effect/tsgo oxlint-tsgolint
yarn add -D @effect/tsgo oxlint-tsgolint
bun add -D @effect/tsgo oxlint-tsgolint
nub add -D @effect/tsgo oxlint-tsgolint
aube add -D @effect/tsgo oxlint-tsgolint

Patch the binaries after every install with a prepare script (--no-typescript limits the patch to Oxlint; drop it if you also want Effect’s TypeScript-Go integration):

{
  "scripts": {
    "prepare": "effect-tsgo patch --no-typescript --oxlint"
  }
}

Then extend one of the presets @effect/tsgo ships:

import { strict } from "@effect/tsgo/oxlint-presets";
import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";

export default defineConfig({
  extends: [core, strict],
  ignorePatterns: core.ignorePatterns,
  rules: {
    "effecttsgo/global-console-in-effect": "error",
    "effecttsgo/global-date-in-effect": "error",
  },
});

strict raises every Effect diagnostic that is on by default to error; use recommended instead to keep upstream’s severities. Both presets turn on Oxlint’s type-aware mode, so --type-aware isn’t needed — and core’s type-aware rules, such as typescript/no-floating-promises, run too. Upstream leaves some rules off, like the *-in-effect family above; enable the ones you want in rules, but skip effecttsgo/strict-boolean-expressions, which duplicates core’s typescript/strict-boolean-expressions. Some rules only apply to Effect 3 or Effect 4 and do nothing on the other major version — see the rule list.

A few things to keep in mind:

  • Unpatched Oxlint fails the whole run with Unknown plugin: 'effecttsgo', so the patch has to be in place everywhere you lint, CI included.
  • Reinstalling or upgrading oxlint or oxlint-tsgolint (including through ultracite upgrade) can restore the stock binaries without re-running prepare. Run npx effect-tsgo patch --no-typescript --oxlint again afterwards.
  • The patch refuses versions it doesn’t support, which fails the install. Check the supported versions in the @effect/tsgo README before upgrading either package.
  • Effect’s fixes are reported as suggestions, so ultracite fix only applies them with --unsafe.

Enforcing Path Aliases

Ultracite’s core preset does not assume a project layout, so it does not force @/* (or #/*) aliases over relative imports — that convention depends on your tsconfig.json, and it would false-positive in monorepos, libraries, and projects without a src directory. If your project maps @/* to src/* and you want to ban ../ imports between source modules, add an override scoped to src:

import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";

export default defineConfig({
  extends: [core],
  ignorePatterns: core.ignorePatterns,
  overrides: [
    {
      files: ["src/**"],
      rules: {
        "import/no-relative-parent-imports": "error",
      },
    },
  ],
});

import/no-relative-parent-imports is a native Oxlint rule: it flags ../ in imports, type imports, and re-exports at any depth, while still allowing ./sibling imports within a directory. Scoping it to src/** keeps tests, scripts, and config files that legitimately reach into src unaffected.

If you would rather point people at the alias in the diagnostic itself, use no-restricted-imports with a pattern instead:

overrides: [
  {
    files: ["src/**"],
    rules: {
      "no-restricted-imports": [
        "error",
        {
          patterns: [
            {
              regex: "^\\.\\./",
              message: "Use the @/* alias for imports between src modules.",
            },
          ],
        },
      ],
    },
  },
],

Formatter Settings (Oxfmt)

Ultracite configures Oxfmt with these defaults:

  • Indentation: 2 spaces
  • Line Width: 80 characters
  • Semicolons: Always required
  • Double Quotes: Yes
  • Trailing Commas: ES5 style
  • Arrow Parentheses: Always include
  • Tailwind Class Sorting: Enabled, including inside clsx, cva, tw, twMerge, cn, twJoin, and tv calls

Tailwind class sorting uses the same algorithm as prettier-plugin-tailwindcss. If you use Tailwind CSS v4 with a custom theme, point Oxfmt at your stylesheet so sorting respects your custom utilities:

import { defineConfig } from "oxfmt";
import ultracite from "ultracite/oxfmt";

export default defineConfig({
  ...ultracite,
  sortTailwindcss: {
    functions: ["clsx", "cva", "tw", "twMerge", "cn", "twJoin", "tv"],
    stylesheet: "./app/globals.css",
  },
});

Rule Reference

For the complete list of rules and their settings, see the Oxlint configuration on GitHub.

VS Code Extension

Install the Oxc extension for VS Code:

code --install-extension oxc.oxc-vscode

Was this page helpful?