eslint-plugin-logical-properties
Transform your normal TS/JS-based CSS into RTL-ready CSS (think: vanilla extract or JSX style attribute).
This works by targeting certain JSX attributes and TS/JS function calls to identify what objects need transforming. Needless to say, this works on pure TS/JS Objects.
Usage
[!WARNING] This plugin will only work with a flat config ESLint (please upgrade, it's great!)
As with any eslint plugin, it comes with presets and ways of adapting it to your own needs.
Basic Setup (Preset Only)
The simplest way to use this plugin is with one of the presets:
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const finalEslintConfig = [
logicalPropertiesPlugin.configs.recommended,
// logicalPropertiesPlugin.configs.warning,
// logicalPropertiesPlugin.configs.disable,
];
Available presets:
recommended(all rules turned on,errorseverity)warning(all rules set towarnseverity)disable(all rules turned off)
These presets include the plugin and default settings automatically.
Advanced Setup (Preset + Custom Settings)
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const customConfig = [
{
name: 'logical-properties',
plugins: {
'logical-properties': logicalPropertiesPlugin,
},
settings: {
'logical-properties': {
functions: ['style', 'globalStyle', 'myStyleFactory'],
keyframes: ['keyframes', 'myKeyframes'],
jsxAttributes: ['style', 'sx'],
resolvers: ['selectors.*', '@media.*', '@supports.*', 'variants.*.*', 'custom.tokens.*'],
},
},
rules: {
...logicalPropertiesPlugin.configs.recommended.rules,
},
},
];
Per-Rule Override
If one rule needs custom behavior, rule options override shared settings:
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const finalEslintConfig = [
logicalPropertiesPlugin.configs.recommended,
{
name: 'logical-properties-overrides',
rules: {
'logical-properties/padding': ['error', {
functions: ['myStyleFactory'],
resolvers: ['custom.tokens.*'],
}],
},
},
];
From Scratch (No Preset)
For complete control, you can build the config manually without presets:
import logicalPropertiesPlugin from 'eslint-plugin-logical-properties';
const finalEslintConfig = [
{
name: 'logical-properties',
plugins: {
'logical-properties': logicalPropertiesPlugin,
},
settings: {
'logical-properties': {
functions: ['style', 'globalStyle'],
keyframes: ['keyframes'],
jsxAttributes: ['style'],
resolvers: ['selectors.*', '@media.*'],
},
},
rules: {
'logical-properties/padding': 'error',
'logical-properties/margin': 'error',
'logical-properties/inset': 'error',
// ... add other rules as needed
},
},
];
Custom Configuration
If you want explicit all-rules overrides in one place, use createLogicalPropertiesConfig.
This helper keeps its previous behavior and generates a complete ESLint config.
- helper-based all-rules config:
import { createLogicalPropertiesConfig } from 'eslint-plugin-logical-properties';
const config = createLogicalPropertiesConfig('error', {
functions: ['style', 'globalStyle'],
keyframes: ['keyframes'],
jsxAttributes: ['style'],
resolvers: ['selectors.*', '@media.*'],
});
[!NOTE] The helper generates an entire config, with the plugin attached, not just rules, so you can use it directly in your ESLint config.
Configuration Priority
Option resolution happens in this order:
- Internal defaults
- Shared plugin settings (
settings['logical-properties']) - Per-rule options (
rules['logical-properties/<rule>'][1])
Configuration
All rules share the same options:
- functions
- keyframes
- jsxAttributes
- resolvers
The difference lies in how they are processed:
- functions: expected to be called with CSS objects or arrays of CSS objects (ignores all else, like strings)
- keyframes: expected to be called with an object whose properties are CSS objects
- jsxAttributes: attributes on your JSX components that should be treated as CSS objects
- resolvers: nested object paths to recursively inspect for CSS objects
[!NOTE] When referring to CSS Objects, it's understood as an object whose keys are CSS properties
Default Config
const options = {
functions: ['style', 'globalStyle', 'recipe', 'sprinkles', 'defineProperties'],
keyframes: ['keyframes'],
jsxAttributes: ['style'],
resolvers: [
'selectors.*',
'@media.*',
'@supports.*',
'base',
'variants.*.*',
'compoundVariants.*.style',
],
}
// Helper
const generatedConfig = createLogicalPropertiesConfig('error', options);
// Manual Config
const manualConfig = {
rules: {
'logical-properties/padding': ['error', options]
}
};
Shorthand Behavior
For shorthand rules (padding, margin, borderRadius), fixes are conservative:
- 1 value: unchanged
- 2 values: rewritten to logical pair properties
- 3 values: expanded to block-start, inline (single), block-end
- 4 values: expanded to primitive logical properties
Examples:
// 1 value -> unchanged
style({ padding: '8px' })
// 2 values -> logical pair
style({ padding: '8px 16px' })
// becomes
style({ paddingBlock: '8px', paddingInline: '16px' })
// 3 values -> block-start, single inline, block-end
style({ padding: '8px 16px 4px' })
// becomes
style({
paddingBlockStart: '8px',
paddingInline: '16px',
paddingBlockEnd: '4px',
})
// 4 values -> primitive logical properties
style({ padding: '8px 16px 4px 12px' })
// becomes
style({
paddingBlockStart: '8px',
paddingInlineEnd: '16px',
paddingBlockEnd: '4px',
paddingInlineStart: '12px',
})
The same logic also works for quoted strings and template strings.
Migration Notes
If you are upgrading from a version that always expanded shorthand values to four properties, the biggest changes are two-value and three-value shorthand behavior.
Before/After Autofix
// Before (old behavior - 2 values)
style({ padding: '8px 16px' })
// autofix
style({
paddingBlockStart: '8px',
paddingBlockEnd: '8px',
paddingInlineStart: '16px',
paddingInlineEnd: '16px',
})
// After (current behavior - 2 values)
style({ padding: '8px 16px' })
// autofix
style({
paddingBlock: '8px',
paddingInline: '16px',
})
// Before (old behavior - 3 values)
style({ padding: '8px 16px 4px' })
// autofix
style({
paddingBlockStart: '8px',
paddingInlineStart: '16px',
paddingInlineEnd: '16px',
paddingBlockEnd: '4px',
})
// After (current behavior - 3 values)
style({ padding: '8px 16px 4px' })
// autofix
style({
paddingBlockStart: '8px',
paddingInline: '16px',
paddingBlockEnd: '4px',
})
// Before and after for one-value shorthand
style({ padding: '8px' })
// no autofix
style({ padding: '8px' })
// Before and after for 4 values
style({ padding: '8px 16px 4px 12px' })
// autofix (unchanged)
style({
paddingBlockStart: '8px',
paddingInlineEnd: '16px',
paddingBlockEnd: '4px',
paddingInlineStart: '12px',
})
Rules
| Rule | Source | Replacement | Type |
|---|---|---|---|
| border | borderLeft | borderInlineStart | property |
| border | borderLeftColor | borderInlineStartColor | property |
| border | borderLeftStyle | borderInlineStartStyle | property |
| border | borderLeftWidth | borderInlineStartWidth | property |
| border | borderRight | borderInlineEnd | property |
| border | borderRightColor | borderInlineEndColor | property |
| border | borderRightStyle | borderInlineEndStyle | property |
| border | borderRightWidth | borderInlineEndWidth | property |
| border | borderTop | borderBlockStart | property |
| border | borderTopColor | borderBlockStartColor | property |
| border | borderTopStyle | borderBlockStartStyle | property |
| border | borderTopWidth | borderBlockStartWidth | property |
| border | borderBottom | borderBlockEnd | property |
| border | borderBottomColor | borderBlockEndColor | property |
| border | borderBottomStyle | borderBlockEndStyle | property |
| border | borderBottomWidth | borderBlockEndWidth | property |
| border | border | borderInlineStart, borderInlineEnd, borderBlockStart, borderBlockEnd | shorthand mapping |
| border-radius | borderTopLeftRadius | borderStartStartRadius | property |
| border-radius | borderTopRightRadius | borderStartEndRadius | property |
| border-radius | borderBottomLeftRadius | borderEndStartRadius | property |
| border-radius | borderBottomRightRadius | borderEndEndRadius | property |
| border-radius | borderRadius (2 values) | borderStartStartRadius + borderEndEndRadius, borderStartEndRadius + borderEndStartRadius | shorthand pair mapping |
| border-radius | borderRadius (3-4 values) | primitive logical radius properties | shorthand expansion |
| inset | top | insetBlockStart | property |
| inset | bottom | insetBlockEnd | property |
| inset | left | insetInlineStart | property |
| inset | right | insetInlineEnd | property |
| margin | marginLeft | marginInlineStart | property |
| margin | marginRight | marginInlineEnd | property |
| margin | marginTop | marginBlockStart | property |
| margin | marginBottom | marginBlockEnd | property |
| margin | margin (2 values) | marginBlock, marginInline | shorthand pair mapping |
| margin | margin (3 values) | marginBlockStart, marginInline, marginBlockEnd | shorthand 3-value |
| margin | margin (4 values) | primitive logical margin properties | shorthand expansion |
| padding | paddingLeft | paddingInlineStart | property |
| padding | paddingRight | paddingInlineEnd | property |
| padding | paddingTop | paddingBlockStart | property |
| padding | paddingBottom | paddingBlockEnd | property |
| padding | padding (2 values) | paddingBlock, paddingInline | shorthand pair mapping |
| padding | padding (3 values) | paddingBlockStart, paddingInline, paddingBlockEnd | shorthand 3-value |
| padding | padding (4 values) | primitive logical padding properties | shorthand expansion |
| overflow | overflowX | overflowInline | property |
| overflow | overflowY | overflowBlock | property |
| overscroll-behavior | overscrollBehaviorX | overscrollBehaviorInline | property |
| overscroll-behavior | overscrollBehaviorY | overscrollBehaviorBlock | property |
| size | width | inlineSize | property |
| size | height | blockSize | property |
| size | minWidth | minInlineSize | property |
| size | minHeight | minBlockSize | property |
| size | maxWidth | maxInlineSize | property |
| size | maxHeight | maxBlockSize | property |
| float | float: left | float: inline-start | value |
| float | float: right | float: inline-end | value |
| clear | clear: left | clear: inline-start | value |
| clear | clear: right | clear: inline-end | value |
Index
Functions
Variables
Variables
constdefault
Type declaration
configs: { disable: { plugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }; rules: {}; settings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } } }; recommended: { plugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }; rules: {}; settings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } } }; warning: { plugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }; rules: {}; settings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } } } }
disable: { plugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }; rules: {}; settings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } } }
readonlyplugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }
readonlylogical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } }
readonlymeta: { name: string; version: string }
readonlyname: string
readonlyversion: string
rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule }
readonlyborder: RuleModule
readonlyborder-radius: RuleModule
readonlyclear: RuleModule
readonlyfloat: RuleModule
readonlyinset: RuleModule
readonlymargin: RuleModule
readonlyoverflow: RuleModule
readonlyoverscroll-behavior: RuleModule
readonlypadding: RuleModule
readonlysize: RuleModule
rules: {}
[k string]: T
readonlysettings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } }
readonlylogical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] }
readonlyfunctions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]
readonlyjsxAttributes: readonly [style]
readonlykeyframes: readonly [keyframes]
readonlyresolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style]
recommended: { plugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }; rules: {}; settings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } } }
readonlyplugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }
readonlylogical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } }
readonlymeta: { name: string; version: string }
readonlyname: string
readonlyversion: string
rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule }
readonlyborder: RuleModule
readonlyborder-radius: RuleModule
readonlyclear: RuleModule
readonlyfloat: RuleModule
readonlyinset: RuleModule
readonlymargin: RuleModule
readonlyoverflow: RuleModule
readonlyoverscroll-behavior: RuleModule
readonlypadding: RuleModule
readonlysize: RuleModule
rules: {}
[k string]: T
readonlysettings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } }
readonlylogical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] }
readonlyfunctions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]
readonlyjsxAttributes: readonly [style]
readonlykeyframes: readonly [keyframes]
readonlyresolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style]
warning: { plugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }; rules: {}; settings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } } }
readonlyplugins: { logical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } } }
readonlylogical-properties: { meta: { name: string; version: string }; rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule } }
readonlymeta: { name: string; version: string }
readonlyname: string
readonlyversion: string
rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule }
readonlyborder: RuleModule
readonlyborder-radius: RuleModule
readonlyclear: RuleModule
readonlyfloat: RuleModule
readonlyinset: RuleModule
readonlymargin: RuleModule
readonlyoverflow: RuleModule
readonlyoverscroll-behavior: RuleModule
readonlypadding: RuleModule
readonlysize: RuleModule
rules: {}
[k string]: T
readonlysettings: { logical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] } }
readonlylogical-properties: { functions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]; jsxAttributes: readonly [style]; keyframes: readonly [keyframes]; resolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style] }
readonlyfunctions: readonly [style, globalStyle, recipe, sprinkles, defineProperties]
readonlyjsxAttributes: readonly [style]
readonlykeyframes: readonly [keyframes]
readonlyresolvers: readonly [selectors.*, @media.*, @supports.*, base, variants.*.*, compoundVariants.*.style]
readonlymeta: { name: string; version: string }
readonlyname: string
readonlyversion: string
rules: { border: RuleModule; border-radius: RuleModule; clear: RuleModule; float: RuleModule; inset: RuleModule; margin: RuleModule; overflow: RuleModule; overscroll-behavior: RuleModule; padding: RuleModule; size: RuleModule }
readonlyborder: RuleModule
readonlyborder-radius: RuleModule
readonlyclear: RuleModule
readonlyfloat: RuleModule
readonlyinset: RuleModule
readonlymargin: RuleModule
readonlyoverflow: RuleModule
readonlyoverscroll-behavior: RuleModule
readonlypadding: RuleModule
readonlysize: RuleModule