Skip to content

Configuration

The plugin works out of the box with zero configuration, but offers powerful customization options for advanced use cases.

postcss.config.js
module.exports = {
plugins: [
require('postcss-logical-polyfill')()
]
}
const logicalPolyfill = require('postcss-logical-polyfill');
module.exports = {
plugins: [
logicalPolyfill({
// Direction selectors (default shown)
rtl: { selector: '[dir="rtl"]' },
ltr: { selector: '[dir="ltr"]' },
// Output order for unscoped properties
outputOrder: 'ltr-first' // or 'rtl-first'
})
]
}

Control how the plugin generates direction-specific selectors:

{
rtl: { selector: '[dir="rtl"]' },
ltr: { selector: '[dir="ltr"]' }
}

Control the order of generated rules for unscoped properties:

{
outputOrder: 'ltr-first' // Default: LTR rules first, then RTL
// outputOrder: 'rtl-first' // RTL rules first, then LTR
}

Example Output:

.container {
margin-left: 1rem;
margin-right: 1rem;
}
[dir="ltr"] .container {
padding-left: 1rem;
}
[dir="rtl"] .container {
padding-right: 1rem;
}

Use a PostCSS configuration rather than adding another CSS loader to Next.js:

postcss.config.mjs
export default {
plugins: {
'postcss-logical-polyfill': {}
}
};

A custom PostCSS configuration replaces Next.js defaults. Include the other plugins your application needs; run nesting expansion and any CSS-producing framework plugin before this plugin. Set the root HTML dir attribute in your layout.

vite.config.js
import { defineConfig } from 'vite';
import logicalPolyfill from 'postcss-logical-polyfill';
export default defineConfig({
css: {
postcss: {
plugins: [
logicalPolyfill({
outputOrder: 'ltr-first'
})
]
}
}
});
webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
require('postcss-logical-polyfill')({
rtl: { selector: '.rtl' },
ltr: { selector: '.ltr' }
})
]
}
}
}
]
}
]
}
};

Run this plugin after Tailwind generates its CSS so that generated logical declarations can be transformed. Configure the Tailwind PostCSS plugin for your installed major version (tailwindcss for v3, @tailwindcss/postcss for v4). Keep any other processors required by your target browsers.

This plugin transforms logical properties; it does not mirror physical utilities such as ml-* and mr-*. Prefer logical utilities when you need direction-aware layouts.

For complex scenarios, you can use advanced selector patterns:

{
rtl: {
selector: 'html[dir="rtl"]', // More specific
},
ltr: {
selector: 'html[dir="ltr"]',
}
}
{
rtl: {
selector: '[dir="rtl"], .rtl-mode' // Multiple selectors
},
ltr: {
selector: '[dir="ltr"], .ltr-mode'
}
}

You must set the dir attribute on your HTML for the generated CSS to work:

<!DOCTYPE html>
<html dir="ltr"> <!-- For left-to-right layouts -->
<head>
<meta charset="UTF-8">
<title>My App</title>
</head>
<body>
<!-- Your content -->
</body>
</html>

For RTL languages:

<html dir="rtl"> <!-- For right-to-left layouts -->

For applications that support dynamic direction switching:

// JavaScript to toggle direction
function toggleDirection() {
const html = document.documentElement;
const currentDir = html.getAttribute('dir');
html.setAttribute('dir', currentDir === 'rtl' ? 'ltr' : 'rtl');
}
import { useEffect, useState } from 'react';
function App() {
const [direction, setDirection] = useState('ltr');
useEffect(() => {
document.documentElement.setAttribute('dir', direction);
}, [direction]);
return (
<div>
<button onClick={() => setDirection(direction === 'ltr' ? 'rtl' : 'ltr')}>
Switch to {direction === 'ltr' ? 'RTL' : 'LTR'}
</button>
{/* Your app content */}
</div>
);
}

The plugin automatically optimizes block-direction properties to generate fewer rules:

/* Input */
.element {
margin-block: 1rem;
padding-block-start: 0.5rem;
}
/* Optimized Output - No direction selectors needed */
.element {
margin-top: 1rem;
margin-bottom: 1rem;
padding-top: 0.5rem;
}

For large codebases, consider processing only files that contain logical properties:

// Only process certain file patterns
module.exports = {
plugins: [
// Other plugins
require('postcss-logical-polyfill')()
]
};

You can debug the transformation process by examining the generated CSS:

const postcss = require('postcss');
const logicalPolyfill = require('postcss-logical-polyfill');
const result = await postcss([logicalPolyfill()])
.process(css, { from: undefined });
console.log('Original CSS:', css);
console.log('Transformed CSS:', result.css);

Direction Attribute Missing

Ensure dir="ltr" or dir="rtl" is set on your HTML element

Selector Specificity

Check that your direction selectors have appropriate specificity

Order Matters

Consider the outputOrder option for consistent rule ordering

  1. Set HTML Direction: Always set the dir attribute on your <html> element
  2. Test Both Directions: Test your layout in both LTR and RTL modes
  3. Use Block Properties: Prefer block-direction properties when possible for better performance
  4. Consistent Selectors: Use consistent direction selectors across your application
  5. Framework Integration: Run the plugin after CSS generation and nesting expansion
/* Before: Physical properties */
.element {
margin-left: 1rem;
margin-right: 2rem;
padding-left: 0.5rem;
}
/* After: Logical properties */
.element {
margin-inline: 1rem 2rem;
padding-inline-start: 0.5rem;
}

Like postcss-logical, this plugin converts logical properties to physical properties. It generates both horizontal LTR and RTL scopes in one stylesheet, rather than mapping all declarations for one configured direction. Review the generated selectors, specificity and supported properties when migrating.

Enable animation compilation explicitly:

logicalPolyfill({ animations: true });

The default is false. Referenced logical keyframes receive separate physical-property copies for LTR and RTL, with collision-free generated names. Static animation-name and animation shorthand references use the matching direction copy. Original definitions remain available. Duplicate names in conditional rules retain those conditions.

Definitions and references must be available in the same PostCSS root; run postcss-import first for multi-file animations. Expand CSS nesting first. Dynamic names in var()/env() remain unchanged with a warning and do not receive an old-browser fallback.