Linting
Lint Bamboo CSS usage with ESLint, against the same config your build reads.
@bamboocss/eslint-plugin checks how your code uses Bamboo CSS, in the editor and in CI, against the config your build
reads: a token path that does not exist, a value the build cannot resolve, a hardcoded color where a token belongs, a
recipe the build cannot name.
Installation
pnpm add -D @bamboocss/eslint-plugin
Configuration
ESLint 9 and later read flat config, eslint.config.js. Add the recommended rules:
// eslint.config.js
import bamboo from '@bamboocss/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
export default [{ files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser } }, bamboo.configs['flat/recommended']]
bamboo.configs['flat/all'] turns on every rule instead, except prefer-shorthand-properties, the opposite of
prefer-longhand-properties: with both on, each would undo the other's fixes. With defineConfig, the same is
extends: ['bamboo/flat/recommended'] beside plugins: { bamboo }.
The configs name no parser: TypeScript files take the one your project configures — the line above, or
typescript-eslint's configs — and .vue, .svelte and .astro files keep their own. In a file ESLint lints as
another language — CSS through @eslint/css, JSON through @eslint/json — the rules stand aside.
To choose the rules yourself, register the plugin and set them:
export default [
{
plugins: { bamboo },
rules: {
'bamboo/no-invalid-token-paths': 'error',
'bamboo/no-hardcoded-color': 'warn',
},
},
]
In eslint.config.ts, the plugin and its configs type-check as ESLint's own ESLint.Plugin and Linter.Config.
Legacy .eslintrc
For the config system ESLint 9 deprecated, recommended and all are .eslintrc configs. They name
@typescript-eslint/parser, which has to be installed:
{
"extends": ["plugin:bamboo/recommended"]
}
Settings
The rules read the bamboo.config.ts found from the file being linted. To read another, name the file — or the
directory holding it — relative to ESLint's working directory. For a path relative to eslint.config.js instead, make
it absolute:
import path from 'node:path'
export default [
{
settings: { '@bamboocss/configPath': path.join(import.meta.dirname, 'bamboo.config.ts') },
},
]
Rules
✅ marks the rules in flat/recommended and recommended.
| Rule | Description | ✅ |
|---|---|---|
file-not-included (opens in a new tab) | Disallow Bamboo CSS in files the config's include does not cover | ✅ |
no-config-function-in-source (opens in a new tab) | Prohibit config functions outside the Bamboo config file | ✅ |
no-debug (opens in a new tab) | Disallow the debug attribute in shipped code | ✅ |
no-deprecated-tokens (opens in a new tab) | Disallow deprecated tokens | ✅ |
no-descendant-selectors (opens in a new tab) | Disallow nested selectors that style a different element than the class is applied to | |
no-dynamic-styling (opens in a new tab) | Disallow dynamic styling; prefer static styles, CSS variables or recipes | ✅ |
no-escape-hatch (opens in a new tab) | Prohibit escape hatch syntax | |
no-hardcoded-color (opens in a new tab) | Use color tokens rather than hardcoded colors | ✅ |
no-important (opens in a new tab) | Disallow !important | |
no-invalid-nesting (opens in a new tab) | Nested styles must contain & | ✅ |
no-invalid-token-paths (opens in a new tab) | Disallow token paths that do not exist, and values the build cannot resolve | ✅ |
no-margin-properties (opens in a new tab) | Prefer gap in the parent to margins | |
no-physical-properties (opens in a new tab) | Prefer logical properties to physical ones | |
no-property-renaming (opens in a new tab) | Disallow renaming pattern and style props, which the build cannot follow | ✅ |
no-redundant-value (opens in a new tab) | Report an edge or pair value written longer than it needs to be | |
no-unlayered-override (opens in a new tab) | Disallow joining a class with one it cannot see, where neither can win by cascade layer | ✅ |
no-unresolved-styles (opens in a new tab) | Report styles the build cannot read, whose declarations never reach the stylesheet | ✅ |
no-unsafe-token-fn-usage (opens in a new tab) | Use the token directly where the token function is not needed | ✅ |
prefer-atomic-properties (opens in a new tab) | Prefer atomic properties to composite ones | |
prefer-composite-properties (opens in a new tab) | Prefer composite properties to atomic ones | |
prefer-longhand-properties (opens in a new tab) | Prefer longhand properties to shorthands | |
prefer-shorthand-properties (opens in a new tab) | Prefer shorthand properties to longhands | |
prefer-token (opens in a new tab) | Prefer a token to a value written out that the token holds | |
prefer-unified-property-style (opens in a new tab) | Disallow mixing atomic and composite forms of the same property | |
require-literal-token-path (opens in a new tab) | Require token paths the build can resolve, so prune.tokens can drop the rest | |
require-recipe-class-name (opens in a new tab) | Require a className on a recipe, so its class names do not depend on what the build could read | ✅ |