Skip to content

ADR-0003: Biome for lint + format

Status: Accepted Date: 2026-04-20

Every TypeScript project needs a linter and a formatter. Two families of tooling exist: the traditional combination (ESLint + Prettier, two tools, two configs, mature ecosystem) and the newer unified tools (Biome, dprint). The tooling running on developer machines and in CI should be fast, consistent, and low-friction.

Use Biome 2 for both linting and formatting. Single tool, single config (biome.json), single invocation. No ESLint, no Prettier.

  • One config, one version to track, one cache to invalidate. Less yak-shaving.
  • Biome is ~10–100× faster than ESLint + Prettier on equivalent rulesets. Matters less at our size today; matters a lot by the time we have 100+ files.
  • Biome’s import-sort runs as part of lint:fix. No separate organize-imports step.
  • Plugin ecosystem is smaller than ESLint’s. If we ever need a custom AST rule or an ESLint-only plugin (e.g., for a specific framework we don’t yet use), we’ll reconsider — adopting ESLint alongside is an option at that point, not a disaster.
  • Markdown and many other file types aren’t formatted by Biome 2. Acceptable; editorconfig handles whitespace for non-source files.
  • Biome 2.x is still pre-LTS. Upgrades may have rule-set changes. Mitigated by pinning the version in biome.json’s $schema and package.json.
  • ESLint + Prettier: largest ecosystem, most documentation. Slower, two configs, two failure modes for “why is my code being reformatted.” Default choice if we need a rule Biome doesn’t have.
  • dprint: similar philosophy to Biome, smaller community, plugin-per-language model. Biome’s integrated linting tips the balance.
  • Oxc: very fast, Rust-based, still maturing. Worth re-evaluating in a year.
  • No linter, just formatter: rejected — linting catches real bugs (noUnusedVariables, useImportType) that typecheck doesn’t.