ESLint plugin
Lint rules for ESLint and Oxlint that help you migrate from styled-components to yak and point you to the patterns that compile to the fastest CSS.
Installation
Install @yak/eslint-plugin in your project:
npm i -D @yak/eslint-pluginpnpm add -D @yak/eslint-pluginyarn add -D @yak/eslint-pluginbun add -D @yak/eslint-pluginAdd it to your eslint config:
import yakPlugin from "@yak/eslint-plugin";
import { defineConfig } from "eslint/config";
export default defineConfig([
yakPlugin.configs.recommended,
]);Now, you can run eslint to check your code.
const myDiv = styled.div`
> div {}
`;
// ^ eslint: Nesting selector missing. Did you forget the &?Oxlint
@yak/eslint-plugin also works with Oxlint, with the same rule prefix @yak/. A good starting configuration:
{
"jsPlugins": ["@yak/eslint-plugin"],
"rules": {
"@yak/css-nesting-operator": "error",
"@yak/enforce-semicolon": "error",
"@yak/style-conditions": "warn"
}
}Rules
css-nesting-operator
Enforces css selectors in yak to correctly use the nesting selector (&).
Example:
const myDiv = styled.div`
> div {}
& > div {}
`;Reason why
Unlike in styled-components, you need the nesting selector to scope styles correctly.
We kept this magic out of yak because writing styles should feel as natural and as close to vanilla CSS as possible.
enforce-semicolon
Enforces semicolons after a mixin, to make distinguishing between mixins and nested selectors easier.
Example:
styled.div`
${skeletonStyle}
${skeletonStyle};
`;Reason why
Unlike runtime CSS-in-JS libraries that combine strings at execution time, yak has to understand the code statically. A variable might have different meanings:
- Selectors (like
${Button} div { color: blue }) define styling rules for components - Mixins (like
${skeletonMixin};) inject pre-defined CSS rules - Constants (like
margin-top: ${marginTop}) - Runtime Variables (like
x: ${({$x}) => $x})
Constants and runtime values are easy to detect as they are always after a colon (:). To tell selectors and mixins apart, however, yak needs the semicolon (;):
// This works correctly - mixin properly terminated with semicolon
styled.div`
${skeleton};
div {
color: blue;
}
`
// This causes ambiguity - is it a mixin or part of the selector?
styled.div`
${skeleton}
div {
color: blue;
}
`style-conditions
Warns if runtime performance could be improved by using css literals.
The rule checks arrow functions used as CSS declaration values, where yak creates runtime CSS variables. Arrow functions outside declarations are runtime class expressions and may return local or imported css mixins.
Example:
styled.button`
color: ${() => color};
color: ${({$color}) => $color};
// or if the value is defined outside the component:
color: ${color};
`;styled.button`
color: ${() => {
${() => {
if (variant === 'primary') {
return primary;
return css` // [!code ++]
color: ${({$primary}) => $primary};
`;
// or if the value is defined outside the component:
return css` // [!code ++]
color: ${primary};
`;
} else {
return secondary;
return css` // [!code ++]
color: ${({$secondary}) => $secondary};
`;
// or if the value is defined outside the component:
return css` // [!code ++]
color: ${secondary};
`;
}
}};
`;Extracted css literals can also be reused as conditional classes:
const activeStyles = css`
opacity: 1;
`;
styled.button`
${({$active}) => $active && activeStyles}
`;Reason why
yak offers two different approaches for implementing dynamic styles, each with its own use cases and performance characteristics.
Class-based Dynamic Styles:
The first approach compiles dynamic styles into separate CSS classes that are toggled at runtime:
styled.button<{$primary?: boolean}>` color: blue; ${({$primary}) => $primary && css` color: red; `} `;In this example:
- The base style color: blue is extracted into a CSS class that's always applied
- The conditional style color: red is extracted into a separate CSS class that's only toggled based on the $primary prop
- This approach is fast because all styles are pre-compiled and only class names are manipulated at runtime
CSS Variables for Truly Dynamic Values
The second approach uses CSS custom properties (variables) for values that cannot be determined at build time:
styled.div<{$x: number}>` left: ${({$x}) => `${$x}px`} `;In this example:
- The value for $x is extracted into a CSS variable (e.g., --x)
- At runtime, this variable is set via inline styles:
<div style="--x: 43px">...</div> - This allows for fully dynamic values but adds a small runtime overhead
Best Practices
- Use the class-based approach (first example) for binary conditions and fixed value variations
- Use CSS variables (second example) only when values are truly dynamic, such as:
- User inputs
- Calculated positions
- Animation states
- Values from external APIs
The @yak/style-conditions rule flags unnecessary uses of CSS variables, which keeps your HTML smaller and faster.
Next steps
- Migration from styled-components: the plugin's main use case
- Features: the patterns the rules point you to