Workbench

Biome and Ultracite

One tool for linting and formatting, a strict preset on top, and overrides that each have a reason.

Biome lints and formats JavaScript, TypeScript, JSON and CSS in one fast binary, so there's no ESLint plus Prettier plus plugins to keep in sync. Ultracite is a strict, ready-made Biome preset with React and Next.js rules, so I don't build the rule list by hand.

Setup

Install

pnpm add -D @biomejs/biome ultracite

Extend the presets

biome.jsonc
{
  "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
  "extends": [
    "ultracite/biome/core",
    "ultracite/biome/react",
    "ultracite/biome/next"
  ]
}

Point $schema at the installed package, not a URL, so the editor autocompletes exactly the version you have.

Add the scripts

package.json
{
  "scripts": {
    "lint": "ultracite check",
    "format": "ultracite fix",
    "typecheck": "tsc --noEmit"
  }
}

Run pnpm lint and pnpm typecheck before every commit.

Overrides, each with a reason

A preset is a starting point. When a rule fights a decision the project has already made, turn it off and write down why, in the config itself.

Handlers don't need stable references

biome.jsonc
"linter": {
  "rules": {
    "performance": {
      // Avoid unnecessary useCallback. Handlers passed to DOM elements
      // gain nothing from stable references, so this rule is off.
      "noJsxPropsBind": "off"
    }
  }
}

PascalCase only where the convention says so

Component files are PascalCase, but UI primitives, hooks and utilities stay kebab-case. A scoped override enforces that only in the folders where it applies:

biome.jsonc
"overrides": [
  {
    "includes": [
      "src/components/shared/**/*.tsx",
      "src/features/*/components/**/*.tsx"
    ],
    "linter": {
      "rules": {
        "style": {
          "useFilenamingConvention": {
            "level": "error",
            "options": { "filenameCases": ["PascalCase"], "requireAscii": true }
          }
        }
      }
    }
  }
]

Tailwind v3 directives in CSS

Biome doesn't know @tailwind, so it reports it as an unknown at-rule. Allow it in the one file that uses it:

biome.jsonc
{
  "includes": ["src/styles/globals.css"],
  "linter": {
    "rules": {
      "suspicious": {
        "noUnknownAtRules": {
          "level": "error",
          "options": { "ignore": ["tailwind"] }
        }
      }
    }
  }
}

Don't lint vendored code

Folders that a tool rewrites on every update (for example installed agent skills) aren't your code. Exclude them with !! so Biome skips them entirely:

biome.jsonc
"files": {
  "includes": ["!!.claude/skills", "!!.cursor/skills"]
}

Fix on save, for people and AI editors

Formatting should never be a review comment. In my projects, Biome fixes run automatically:

  • In the editor, through the Biome extension with format on save.
  • After an AI edit, through a hook. Cursor runs it from .cursor/hooks.json and Claude Code from .claude/settings.json, both calling pnpm dlx ultracite fix, so generated code lands already formatted.

Share rules with AI editors

The project's conventions live in .cursor/rules/*.mdc, and CLAUDE.md points at the same files. One set of rules, followed by both tools.

Last updated on

On this page