Biome Skill
When to Use
Use this skill when:
- Setting up Biome for the first time
- Configuring linting rules
- Setting up formatting standards
- Integrating with CI/CD
- Fixing linting errors
- Configuring import sorting
- Setting up pre-commit hooks with Biome
Critical Patterns
Biome Configuration
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"
}
}
}Import Organization
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:
- Side-effect imports
- External dependencies
- Internal absolute imports
- Relative imports
- Type-only imports (grouped separately)
Linting Rules for TypeScript
ALWAYS enable strict TypeScript rules:
{
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "error",
"noUnsafeDeclarationMerging": "error"
},
"correctness": {
"noUnusedVariables": "error",
"useExhaustiveDependencies": "warn"
},
"style": {
"useImportType": "error",
"noNonNullAssertion": "warn"
}
}
}
}Formatter Configuration
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 width
Code Examples
Package Scripts
Add 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"
}
}CI Integration
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:checkPre-commit Hooks
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-stagedIgnoring Files
biome.json:
{
"files": {
"ignore": [
"node_modules",
"dist",
".next",
"build",
"coverage",
"*.config.js"
]
}
}VSCode Integration
.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"
}
}Common Patterns
Fixing Common Issues
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 = { ... }Auto-fix Workflow
# 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 lintCommands Reference
# 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 .Best Practices
ALWAYS:
- Run
biome check --applybefore committing - Use
noExplicitAny: "error"to preventanytypes - Enable
organizeImportsfor consistent import order - Configure pre-commit hooks to run Biome
- Use single
biome.jsonin monorepo root
NEVER:
- Disable rules without documenting why
- Use
// @ts-ignoreinstead of// biome-ignore - Commit code that fails
biome check - Mix Biome with other formatters (Prettier, ESLint)
- Use
anytype (Biome will catch this)
Biome vs ESLint/Prettier
Why Biome:
- ✅ Fast: 10-100x faster than ESLint
- ✅ All-in-one: Linter + Formatter in one tool
- ✅ Zero config: Works out of the box
- ✅ TypeScript-first: Native TS support
- ✅ Import sorting: Built-in organize imports
Migration from ESLint/Prettier:
- Remove ESLint and Prettier dependencies
- Remove
.eslintrc.*and.prettierrc.*files - Add
biome.jsonconfiguration - Update scripts to use Biome
- Update pre-commit hooks
- Update CI/CD pipelines
Monorepo Setup
Root 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
}
}
}
}Troubleshooting
Issue: Biome not formatting on save
Solution:
- Install Biome VSCode extension
- Set as default formatter in
.vscode/settings.json - Enable format on save
Issue: Import organization not working
Solution:
{
"organizeImports": {
"enabled": true
}
}Run: biome check --apply.
Issue: Conflicts with existing ESLint config
Solution:
- Remove ESLint completely or
- Use ESLint only for custom rules Biome doesn't support
- Don't mix formatters
Resources
- Biome Docs: https://biomejs.dev
- Configuration: https://biomejs.dev/reference/configuration/
- VSCode Extension: https://marketplace.visualstudio.com/items?itemName=biomejs.biome
- Migration Guide: https://biomejs.dev/guides/migrate-eslint-prettier/