@plumeria/eslint-plugin
ESLint plugin for Plumeria.
Below are the available rules and the recommended configuration.
Recommended configuration
The plugin:@plumeria/recommended config enables the following:
@plumeria/custom-props-require-import: error@plumeria/expand-border-shorthands: warn@plumeria/no-combinator: error@plumeria/no-destructure: error@plumeria/no-inline-object: error@plumeria/no-inner-call: error@plumeria/no-invalid-selector: error@plumeria/no-mixed-styling-props: error@plumeria/no-mutable-binding: warn@plumeria/no-order-dependent-overlap: warn@plumeria/no-style-prop-relay: error@plumeria/no-unknown-css-properties: error@plumeria/no-unresolved-composition: warn@plumeria/no-unused-keys: warn@plumeria/sort-properties: warn@plumeria/format-properties: warn@plumeria/validate-values: warn@plumeria/validate-pseudos: warn@plumeria/validate-at-rules: warn
import plumeria from '@plumeria/eslint-plugin';
export default [plumeria.configs.recommended];
Configuring the styling prop
custom-props-require-import, no-inline-object, no-mixed-styling-props,
no-style-prop-relay and no-unresolved-composition need to know which JSX
prop carries styles. It is classStyle unless you changed it, so most projects
configure nothing.
If you did rename it — via styleProp on withPlumeria or on the unplugin
options — tell the plugin the same name. The lint the bundler plugins run in the
build is given that name already. ESLint cannot read it from your bundler
config, so set it once in settings and every rule picks it up:
import plumeria from '@plumeria/eslint-plugin';
export default [
plumeria.configs.recommended,
{
settings: {
plumeria: { styleProp: 'sx' },
},
},
];
A single rule can override that if you need it to:
{
rules: {
'@plumeria/no-mixed-styling-props': ['error', { styleProp: 'sx' }],
},
}
The name must match whatever the loader or unplugin was given. If they disagree, the lint rules report against a prop the compiler never transforms.
Recommended rules
The following rules are enabled by plumeria.configs.recommended with the
severity shown in Recommended configuration.
custom-props-require-import
Require the @plumeria/core import in a file that passes a css.create style to a
component through a prop other than the styling prop. The compiler cannot tell that
such a prop carries a style, so it compiles the file only when the file imports
@plumeria/core. A file that writes the styling prop itself is compiled without
the import.
Accepts { styleProp }; see Configuring the styling prop.
expand-border-shorthands
Expands a border shorthand that bundles a width, a style and a color —
border, borderBlock, borderInline, and the eight edge forms — into the
three declarations it stands for. Those bundles are the only properties left
that cross an axis shorthand without either containing the other, so expanding
them turns the last unrankable pairs into ordinary shorthand-to-longhand ones.
Fixable. A value it cannot split, such as var(--edge) or inherit, is
reported without a fix: leaving it silent would let the expanded declarations
elsewhere outrank it.
borderTop: '1px solid red'
// becomes
borderTopWidth: '1px', borderTopStyle: 'solid', borderTopColor: 'red'
A shorthand resets what it omits, so borderBlock: 'solid' expands with
medium and currentcolor written out.
no-combinator
Disallow combinators >, +, ~ and descendant combinator (space) unless inside functional pseudo-classes.
no-destructure
Disallow destructuring APIs.
no-inline-object
Disallow passing inline object to classStyle and css.use(). Only compiled styles from css.create() are allowed.
no-inner-call
Disallow calling APIs inside functions.
no-invalid-selector
Disallow invalid selector inside css.create() and css.keyframes() and css.viewTransitionClass().create() example: (Pseudo -> Query, Query -> Query)
Also reports a functional pseudo-class nested inside one of the same name, such as :where(:where(.a), .b), and flattens it with a fix when the nested call is a whole argument. :not() may nest inside :not(). Reports a key nested deeper than 16 levels or holding a stray quote.
no-mixed-styling-props
Disallow mixing the styling prop with className or style. classStyle can handle both className and style.
Accepts { styleProp }; see Configuring the styling prop.
no-order-dependent-overlap
Warns when two properties in one style overlap but neither outranks the other, so the one written last wins. It covers one property under its logical and its physical name, and two shorthands that cross without either containing the other. A shorthand and its longhand are ranked by specificity and never reported.
It also reports two conditions in one style where one provably matches a subset
of the other — (min-width: 900px) inside (min-width: 600px), or two queries
naming the same container — and the narrower one is written first. The optimizer
places the narrower one last whatever the source says, so the declarations read
the wrong way round. The swap is offered as a suggestion rather than a fix.
A pair of spellings is one property in a horizontal writing mode and two properties in a vertical one, which is per element and cannot be read from the source. The rule reads them as one property; disable the line where an element is known to be vertical.
no-style-prop-relay
Reports a style prop that a component receives but only passes on. Apply it to the styling prop or css.use() on an element the component renders.
Accepts { styleProp }; see Configuring the styling prop.
no-unknown-css-properties
Disallow unknown CSS properties in camelCase within css.create, css.keyframes, css.viewTransitionClass, and css.positionTry. Inside css.positionTry, it also reports properties that @position-try does not accept.
In css.create, a key that holds an object is reported with a hint that nested selectors start with :, [ or @.
no-unresolved-composition
A safety net for css.use(). Warns when its result is combined with other class names or passed to another function, or when a function key is passed to it. Pass every style to one css.use() call, and apply function keys to the styling prop.
Accepts { styleProp }; see Configuring the styling prop.
no-unused-keys
Warns when object keys are defined but not used, mainly in component files.
sort-properties
Automatically sorts CSS properties in the recommended order for consistency and maintainability.
format-properties
Automatically format for consistency and maintainability.
- Formats a line into a multi-line.
- Formats by filling in blank lines.
validate-values
Validates CSS property values for correctness. Only standard CSS properties are checked; properties with string literal keys (e.g., computed or dynamic property names) are not validated.
validate-pseudos
Validates CSS pseudo-classes and pseudo-elements inside css.create(). It checks for typos and structural correctness and supports validation of computed keys when TypeScript is available.
validate-at-rules
Validates at-rules inside css.create(). It accepts @media, @container, and @supports, each with a prelude, whether or not a space separates it from the keyword, and @starting-style on its own, and supports validation of computed keys when TypeScript is available.
Optional rules
These rules are not enabled by plumeria.configs.recommended. Enable the
spelling policy that fits the project:
export default [
plumeria.configs.recommended,
{
rules: {
'@plumeria/no-logical-properties': 'warn',
},
},
];
no-physical-properties / no-logical-properties
Disallow one of the two names a property can carry, so a project writes edges
under one spelling only. no-physical-properties reports paddingLeft and
suggests paddingInlineStart; no-logical-properties reports the reverse.
Accepts { sizes }, which adds width, height, their min/max forms,
overflow-x and overflow-y to the properties covered. It is false by
default: a size is one property under either spelling, while an edge is what a
direction reverses.
Turning both rules on is contradictory. Reach for one when you want the pairs
no-order-dependent-overlap reports to be impossible to write: a property that
appears under one spelling only can never meet its other spelling on an element.
A shorthand with no single counterpart, such as borderBlockWidth, is outside
either rule and stays reported.
The bundler plugins turn the matching rule on in the build lint as well:
withoutPhysicalProperties runs no-physical-properties, and
withoutLogicalProperties runs no-logical-properties, { sizes } included.
CLI (plumeria-lint)
This package provides a CLI command, plumeria-lint, as a convenient way
to run Plumeria's custom ESLint rules.
It runs oxlint, which this package depends on, for fast linting with code
snippets in output, so oxlint needs no separate install.
Installation
npm install -D @plumeria/eslint-plugin
# or
pnpm add -D @plumeria/eslint-plugin
Usage
plumeria-lint
The process exits with a non-zero status code if any errors or warnings are found, making it suitable for use in CI and build pipelines.
Styling prop
If the styling prop is renamed, pass the name with --style-prop. Without the
flag, plumeria-lint asks ESLint for settings.plumeria.styleProp when ESLint
and a flat config are installed, and uses classStyle otherwise:
plumeria-lint --style-prop sx
Aborting Builds on Lint Errors (Parallel Pipeline)
You can run plumeria-lint in parallel with your build command (e.g. next build or vite build) using the -- separator:
{
"scripts": {
"build": "plumeria-lint -- next build"
}
}
If plumeria-lint detects any styling errors or warnings, it will print the diagnostics, kill the build process immediately, and exit with a non-zero code. This avoids compiling when styling validation fails.
With @plumeria/next-plugin and @plumeria/unplugin (except on esbuild and Bun), this lint is integrated into the build by default, so it needs no setup. Pass lint: false to the plugin to build without it.