Biome linter and formatter patterns for consistent code quality. Trigger: When configuring linting, formatting code, or setting up code quality tools.
Use this skill when:
ALWAYS use a single biome.json in monorepo root:
{
"$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnusedVariables": "error",
"useExhaustiveDependencies": "warn"
},
"style": {
"noNonNullAssertion": "warn",
"useImportType": "error"
},
"suspicious": {
"noExplicitAny": "error",
"noConsoleLog": "warn"
}
}
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"trailingComma": "es5",
"semicolons": "asNeeded",
"arrowParentheses": "asNeeded"
}
}
}
Biome organizes imports automatically:
// Before Biome
import { useState } from 'react'
import type { Quiz } from '@/types'
import { Button } from '@/components/ui/button'
import { calculateScore } from './utils'
// After Biome (organized)
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import type { Quiz } from '@/types'
import { calculateScore } from './utils'
Order enforced by Biome:
ALWAYS enable strict TypeScript rules:
{
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "error",
"noUnsafeDeclarationMerging": "error"
},
"correctness": {
"noUnusedVariables": "error",
"useExhaustiveDependencies": "warn"
},
"style": {
"useImportType": "error",
"noNonNullAssertion": "warn"
}
}
}
}
Consistent formatting across packages:
{
"formatter": {
"indentWidth": 2,
"lineWidth": 100,
"indentStyle": "space"
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded",
"trailingComma": "es5",
"arrowParentheses": "asNeeded"
}
}
}
Why these choices:
quoteStyle: "single" - Consistent with most JS projectssemicolons: "asNeeded" - Clean code, ASI-safetrailingComma: "es5" - Better git diffsarrowParentheses: "asNeeded" - Cleaner arrow functionslineWidth: 100 - Balance readability and screen widthAdd to each package.json:
{
"scripts": {
"lint": "biome check .",
"lint:fix": "biome check --apply .",
"format": "biome format --write .",
"format:check": "biome format .",
"check": "biome check --apply ."
}
}
Root package.json (runs on all packages):
{
"scripts": {
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"format": "turbo run format",
"format:check": "turbo run format:check"
}
}
GitHub Actions example:
name: Code Quality
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm lint
- run: pnpm format:check
Using husky + lint-staged:
// package.json
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"biome check --apply --no-errors-on-unmatched"
]
}
}
# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm lint-staged
biome.json:
{
"files": {
"ignore": [
"node_modules",
"dist",
".next",
"build",
"coverage",
"*.config.js"
]
}
}
.vscode/settings.json:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
Issue: noExplicitAny errors
// ❌ Bad
function handleData(data: any) {
return data.value
}
// ✅ Good
function handleData(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value
}
throw new Error('Invalid data')
}
// ✅ Better with Zod
import { z } from 'zod'
const DataSchema = z.object({
value: z.string(),
})
function handleData(data: unknown) {
const parsed = DataSchema.parse(data)
return parsed.value
}
Issue: noUnusedVariables
// ❌ Bad
function calculateScore(answers: Answer[], quizId: string) {
return answers.filter(a => a.isCorrect).length
// quizId is unused
}
// ✅ Good - remove unused param
function calculateScore(answers: Answer[]) {
return answers.filter(a => a.isCorrect).length
}
// ✅ Good - prefix with _ if intentionally unused
function calculateScore(answers: Answer[], _quizId: string) {
return answers.filter(a => a.isCorrect).length
}
Issue: useImportType
// ❌ Bad
import { Quiz } from '@/types'
const quiz: Quiz = { ... }
// ✅ Good
import type { Quiz } from '@/types'
const quiz: Quiz = { ... }
# 1. Check issues
pnpm lint
# 2. Auto-fix what can be fixed
pnpm lint:fix
# 3. Format code
pnpm format
# 4. Check if there are remaining issues
pnpm lint
# Check linting (no changes)
biome check .
# Check and auto-fix
biome check --apply .
# Format files (write)
biome format --write .
# Format files (check only)
biome format .
# Check specific files
biome check src/**/*.ts
# Run with detailed output
biome check --verbose .
# Check with specific config
biome check --config-path ./custom-biome.json .
biome check --apply before committingnoExplicitAny: "error" to prevent any typesorganizeImports for consistent import orderbiome.json in monorepo root// @ts-ignore instead of // biome-ignorebiome checkany type (Biome will catch this)Why Biome:
Migration from ESLint/Prettier:
.eslintrc.* and .prettierrc.* filesbiome.json configurationRoot biome.json (shared config):
{
"$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"formatter": {
"enabled": true,
"indentWidth": 2,
"lineWidth": 100
}
}
Package-specific overrides (if needed):
// frontend/biome.json
{
"extends": ["../biome.json"],
"linter": {
"rules": {
"suspicious": {
"noConsoleLog": "off" // Allow console in frontend dev
}
}
}
}
Solution:
.vscode/settings.jsonSolution:
{
"organizeImports": {
"enabled": true
}
}
Run: biome check --apply .
Solution: