Directional
Overview
Directional modifiers let you apply utility styles conditionally based on the text direction of the document or a parent element. They wrap utility declarations in CSS selectors that match either right-to-left (RTL) or left-to-right (LTR) contexts, generating variant utility classes that adapt to text direction.
Why Use Directional Modifiers?
Directional modifiers help you:
- Support internationalization: Adapt layouts for RTL languages like Arabic, Hebrew, and Persian
- Flip directional properties: Swap margins, paddings, and positions based on text direction
- Build bidirectional components: Create components that work in both LTR and RTL contexts
- Maintain zero specificity: The
:where()wrapper prevents specificity conflicts
useRtlModifier
The useRtlModifier() function creates a modifier that applies styles in right-to-left contexts. It uses :where() to maintain zero specificity.
import { styleframe } from "styleframe";
import { useMarginLeftUtility, useMarginRightUtility } from "@styleframe/theme";
import { useRtlModifier } from "@styleframe/theme";
const s = styleframe();
const rtl = useRtlModifier(s);
useMarginLeftUtility(s, {
md: '1rem',
0: '0',
}, [rtl]);
useMarginRightUtility(s, {
md: '1rem',
0: '0',
}, [rtl]);
export default s;
._margin-left\:md { margin-left: 1rem; }
._margin-right\:md { margin-right: 1rem; }
._rtl\:margin-left\:0 {
&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
margin-left: 0;
}
}
._rtl\:margin-right\:md {
&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
margin-right: 1rem;
}
}
<!-- Flip margin direction for RTL -->
<div class="_margin-left:md _rtl:margin-left:0 _rtl:margin-right:md">
Content with directional margin
</div>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
rtl | &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) |
:where() to keep specificity at zero. This prevents the RTL variant from winning over other utility classes due to specificity, making the cascade more predictable.useLtrModifier
The useLtrModifier() function creates a modifier that applies styles in left-to-right contexts. Like the RTL modifier, it uses :where() for zero specificity.
import { styleframe } from "styleframe";
import { useTextAlignUtility } from "@styleframe/theme";
import { useLtrModifier, useRtlModifier } from "@styleframe/theme";
const s = styleframe();
const ltr = useLtrModifier(s);
const rtl = useRtlModifier(s);
useTextAlignUtility(s, {
left: 'left',
right: 'right',
}, [ltr, rtl]);
export default s;
._text-align\:left { text-align: left; }
._text-align\:right { text-align: right; }
._ltr\:text-align\:left {
&:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) {
text-align: left;
}
}
._rtl\:text-align\:right {
&:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
text-align: right;
}
}
<!-- Explicit directional text alignment -->
<p class="_ltr:text-align:left _rtl:text-align:right">
Directional text alignment
</p>
CSS Selector
| Modifier Name | CSS Selector |
|---|---|
ltr | &:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) |
useDirectionalModifiers
The useDirectionalModifiers() function registers both directional modifiers at once and returns them as a destructurable object.
import { styleframe } from "styleframe";
import { usePaddingLeftUtility, usePaddingRightUtility } from "@styleframe/theme";
import { useDirectionalModifiers } from "@styleframe/theme";
const s = styleframe();
const { rtl, ltr } = useDirectionalModifiers(s);
usePaddingLeftUtility(s, {
md: '1rem',
0: '0',
}, [rtl, ltr]);
usePaddingRightUtility(s, {
md: '1rem',
0: '0',
}, [rtl, ltr]);
export default s;
<!-- Flip padding direction -->
<div class="_padding-left:md _rtl:padding-left:0 _rtl:padding-right:md">
Content with directional padding
</div>
Returned Modifiers
| Key | Modifier Name | CSS Selector |
|---|---|---|
rtl | rtl | &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) |
ltr | ltr | &:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) |
Examples
Bidirectional Navigation
import { styleframe } from "styleframe";
import {
useMarginLeftUtility,
useMarginRightUtility,
usePaddingLeftUtility,
usePaddingRightUtility,
} from "@styleframe/theme";
import { useDirectionalModifiers } from "@styleframe/theme";
const s = styleframe();
const { rtl } = useDirectionalModifiers(s);
useMarginLeftUtility(s, { auto: 'auto', 0: '0' }, [rtl]);
useMarginRightUtility(s, { auto: 'auto', 0: '0' }, [rtl]);
usePaddingLeftUtility(s, { md: '1rem', 0: '0' }, [rtl]);
usePaddingRightUtility(s, { md: '1rem', 0: '0' }, [rtl]);
export default s;
<nav>
<ul>
<li class="_margin-right:auto _rtl:margin-right:0 _rtl:margin-left:auto">
Logo
</li>
<li class="_padding-left:md _rtl:padding-left:0 _rtl:padding-right:md">
Menu item
</li>
</ul>
</nav>
Best Practices
- Prefer logical properties: When possible, use CSS logical properties (
margin-inline-start,padding-inline-end) instead of directional modifiers, as they handle RTL automatically - Use directional modifiers for exceptions: Apply them when logical properties don't cover your use case (e.g., absolute positioning, transforms)
- Set
dirattribute: Ensure your HTML has the correctdir="rtl"ordir="ltr"attribute for the selectors to match - Test both directions: Verify your layouts work correctly in both LTR and RTL modes
- Keep specificity low: The
:where()wrapper ensures directional modifiers don't cause specificity battles
FAQ
:where() pseudo-class has zero specificity, which means the directional modifier won't increase the specificity of the generated selector. This prevents common issues where RTL overrides would unintentionally win over other utility classes.margin-inline-start, padding-inline-end, inset-inline-start) for most use cases. Use directional modifiers when you need to apply completely different styles in RTL vs LTR that go beyond simple property flipping.:dir(rtl) (the CSS pseudo-class) and [dir="rtl"] (the HTML attribute selector), plus [dir="rtl"] * for nested elements. This ensures broad compatibility across browsers.