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

[Oxlint](https://oxc.rs/docs/guide/usage/linter.html) is part of the [Oxc project](https://oxc.rs/), a collection of high-performance JavaScript tools written in Rust. Paired with [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) 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`:

```ts title="oxlint.config.ts"
import { defineConfig } from "oxlint";
import core from "ultracite/oxlint/core";

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

```ts title="oxfmt.config.ts"
import { defineConfig } from "oxfmt";
import ultracite from "ultracite/oxfmt";

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

Add framework presets as needed:

```ts title="oxlint.config.ts"
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`](https://github.com/github/eslint-plugin-github), [`eslint-plugin-sonarjs`](https://github.com/SonarSource/eslint-plugin-sonarjs), and [`oxlint-plugin-react-doctor`](https://www.npmjs.com/package/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`.

```package-install
npx ultracite init --linter oxlint --js-plugins eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
```

:::note
The React Doctor rules used to be bundled into the `react`, `next`, and `tanstack` presets. They now live in `js-plugins` so those framework presets stay on the fast native path. Extend `js-plugins` alongside your framework preset to keep them.
:::

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:

```package-install
npm install -D eslint-plugin-github eslint-plugin-sonarjs oxlint-plugin-react-doctor
```

```ts title="oxlint.config.ts"
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,
});
```

:::note
`jsPlugins: jsPlugins.jsPlugins` re-declares the plugin packages on your root config. Oxlint already loads them from the extended preset, but dependency analyzers such as [Knip](https://knip.dev) only read `jsPlugins` from the root config and would otherwise report the packages as unused devDependencies. `ultracite init` emits this line for you.
:::

:::note
`settings: jsPluginSettings` must live on your root config — Oxlint does not merge `settings` from extended configs. It keeps React Doctor's ported rules (such as `only-export-components`) in their framework-aware "curated" mode, so Next.js route-segment exports like `export const dynamic = "force-static"` are not flagged as non-component exports.
:::

In a Next.js or TanStack app, add the matching add-on preset as well:

```ts title="oxlint.config.ts"
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](https://github.com/dmmulroy/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`:

```package-install
npx ultracite init --linter oxlint --js-plugins anti-slop
```

Or add it to your config manually:

```ts title="oxlint.config.ts"
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`](https://github.com/shadcn-ui/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`:

```package-install
npx ultracite init --linter oxlint --js-plugins @shadcn/lint
```

Or add it to your config manually:

```package-install
npm install -D @shadcn/lint
```

```ts title="oxlint.config.ts"
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:

```ts title="oxlint.config.ts"
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](https://github.com/shadcn-ui/lint/blob/main/docs/rules.md). `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](#eslint-parity-optional) — it is not enabled by default. The optional **shadcn** preset adds **shadcn** (`@shadcn/lint`) the same way — see [Design System Rules](#design-system-rules-with-shadcnlint-optional).

## 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](https://www.npmjs.com/package/oxlint-plugin-complexity):

```package-install
npm install -D oxlint-plugin-complexity
```

```ts title="oxlint.config.ts"
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](https://effect.website/)'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`](https://github.com/Effect-TS/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:

```package-install
npm install -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):

```json title="package.json"
{
  "scripts": {
    "prepare": "effect-tsgo patch --no-typescript --oxlint"
  }
}
```

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

```ts title="oxlint.config.ts"
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](https://github.com/Effect-TS/tsgo#diagnostic-status).

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](https://github.com/Effect-TS/tsgo#supported-package-versions) 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`:

```ts title="oxlint.config.ts"
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:

```ts title="oxlint.config.ts"
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:

```ts title="oxfmt.config.ts"
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](https://github.com/haydenbleasel/ultracite/tree/main/packages/cli/config/oxlint).

## VS Code Extension

Install the [Oxc extension](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) for VS Code:

```bash title="Terminal"
code --install-extension oxc.oxc-vscode
```
