@base-org/eslint-plugin-bds
v0.2.0
Published
ESLint rules and a CI check (bds-lint) that check Base products use BDS fonts and colours from @base-org/bds-tokens.
Readme
@base-org/eslint-plugin-bds
ESLint rules that check Base products use BDS tokens: colours, spacing, fonts and the type scale from
@base-org/bds-tokens. The rules read Tailwind classes in className, in class
helpers such as cn() and cva(), and literal values in style={{ … }}.
yarn add -D @base-org/eslint-plugin-bdsWorks with ESLint 8 (.eslintrc) and ESLint 9 (flat config). Every rule is a warning in the
recommended and strict presets, so adding the plugin never blocks a build. To block a pull request
on fonts and colours, run bds-lint in CI.
The CI check: bds-lint
bds-lint is one check for BDS fonts and colours. It runs these rules as errors: no-raw-color,
valid-color-token, no-tailwind-palette, text-gray-contrast, font-family, type-scale and
font-weight. It uses the repo's ESLint (8.57 or later) but ignores the repo's ESLint config and its
disable comments, so the result depends only on the code and the plugin version.
yarn add -D @base-org/eslint-plugin-bds eslint @typescript-eslint/parser
npx bds-lint src appExit codes: 0 clean, 1 font or colour problems, 2 could not run (a file that doesn't parse, a path
with no files, no ESLint). 2 is never a pass. --format json prints ESLint's JSON report.
In GitHub Actions, as its own step so it can block on its own:
- name: BDS fonts and colours
run: npx bds-lint src appTo adopt it in a repo with existing problems, list the folders to skip for now in bds.config.json,
in the working directory, and remove them as they're cleaned up. settings takes the same keys as
settings.bds below.
{
"ignores": ["app/(legacy)/**"],
"settings": { "allowClasses": ["text-sm"] }
}In an ESLint config, the same rules as errors are the check and flat/check presets.
Setup
ESLint 9, eslint.config.mjs:
import bds from '@base-org/eslint-plugin-bds';
export default [
bds.configs['flat/recommended'],
];ESLint 8, .eslintrc.js:
module.exports = {
extends: ['plugin:@base-org/bds/recommended'],
};Presets
| Preset | Rules |
| --- | --- |
| recommended / flat/recommended | no-raw-color, valid-color-token, no-tailwind-palette, text-gray-contrast, font-family, type-scale, font-weight |
| strict / flat/strict | All of recommended, plus prefer-semantic-color and spacing-scale |
| check / flat/check | The recommended rules as errors. bds-lint runs these |
The recommended rules only suggest spectrum classes (bg-bds-gray-0), which every BDS install
has. strict adds the rules that only fit some repos: semantic roles need the BDS token stylesheet, and
the spacing grid waits on layout steps in the token scale before it moves to recommended.
Rules
Most fixes are offered as editor suggestions, not --fix, because a person should check the colour
is right for its place.
no-raw-color
No raw hex, rgb() or hsl() colour in arbitrary classes (bg-[#0000ff]) or style values. When
the colour is exactly a BDS token, the rule names it and suggests the spectrum class or variable:
bg-[#ffffff] → bg-bds-gray-0. Base Blue suggests its brand roles instead of base-blue:
text-[#0000ff] → text-bds-brand, bg-[#0000ff] → bg-bds-bg-brand.
valid-color-token
*-bds-* classes and var(--bds-…) variables must name a token that exists, so a typo such as
text-bds-fg-mutted doesn't silently render with no colour. Suggests the closest name when only one
is close. Only colour names are checked, so text-bds-body, shadow-bds-sm and a component's own
--bds-* variables pass.
no-tailwind-palette
No Tailwind default colours (bg-white, text-gray-900, border-blue-500). Use a BDS colour
class instead. Repos that map their own palette onto BDS colours can allow it with
allowClasses.
text-gray-contrast
No text in gray-40 or lighter on a light background. fg-muted (gray-50) is the lightest text
colour. Classes under dark:, disabled states (disabled:, data-disabled:) and white text
(gray-0, for dark or brand fills) are left alone.
font-family
Type is set in Base Sans or Base Sans Mono: font-sans, font-mono, or var(--font-sans).
Flags font-serif, other families in font-[…], and fontFamily in style.
type-scale
Type comes from a BDS type scale class (text-bds-body, text-bds-title-1, text-brand-headline), which sets
size, line height and weight together. Flags Tailwind's default sizes (text-sm, text-2xl, text-lg/7),
arbitrary sizes (text-[18px], text-[larger]), leading-[…] and tracking-[…], and fontSize, lineHeight
and letterSpacing in style. In style, only a BDS type token passes, as the whole value:
var(--text-bds-body) or var(--text-bds-body--line-height). Colours in text-[…] and named steps
such as leading-6 are left alone. A repo with its own type classes can allow them with allowClasses.
font-weight
Base Sans in BDS is set in one weight: 400 (font-normal). Flags every other weight class
(font-light, font-medium, font-bold, font-thin), font-[300], font-[600] and font-[number:600], and other
fontWeight values in style. A type token's weight, var(--text-bds-body--font-weight), passes.
prefer-semantic-color (strict)
Prefer a semantic role over the spectrum step it matches in light mode: bg-bds-gray-0 →
bg-bds-bg-primary. A spectrum step stays the same in dark mode; the role switches. Skipped when
the class list already sets a dark: value. Needs the BDS token stylesheet, which defines the role
classes.
spacing-scale (strict)
Padding, margin and gap stay on the 4px grid: step 1 of the BDS spacing scale, and one Tailwind
step on web. The BDS steps 0.5 (2px) and 1.5 (6px) also pass. There is no upper cap, because the
token scale stops at 40px and has no layout steps yet, so py-16 (64px) is fine. Flags px-3.5
(14px), m-[13px] and style={{ marginTop: 7 }}. Suggests the class for an arbitrary value on the
grid: pt-[12px] → pt-3, py-[4rem] → py-16.
Settings
Set per repo under settings.bds:
settings: {
bds: {
// Functions whose string arguments are class lists.
// Default: cn, clsx, classnames, classNames, cx, twMerge, twJoin, cva, tv
callees: ['cn', 'classNames'],
// Classes every rule accepts: exact names, or /regex/ strings. Variants are ignored.
allowClasses: ['/^(bg|text|border)-blue-\\d+$/'],
// Raw colours no-raw-color accepts.
allowColors: ['#0000ff'],
// Font families font-family accepts besides Base Sans and Base Sans Mono.
allowFonts: ['Coinbase Display'],
},
},Token data
The rules read src/generated/tokens.json, built from packages/tokens. The plugin ships this copy
because ESLint 8 loads plugins with require() and @base-org/bds-tokens is ESM-only. Both
packages release together at the same version.
After changing tokens, regenerate it:
yarn workspace @base-org/eslint-plugin-bds generate:tokensA test fails when the copy and packages/tokens differ.
