Appearance
RTL (Right-to-Left) Support ​
Support for RTL languages (Hebrew) with automatic layout flipping.
useRtl Composable ​
The useRtl() composable provides RTL state:
vue
<script setup>
const { dir, resolvedDir, isRtl } = useRtl()
// dir - current direction from i18n locale config
// Can be: 'ltr' | 'rtl' | 'auto'
//
// resolvedDir - computed direction, always resolved
// Always: 'ltr' | 'rtl' (never 'auto')
// If dir is 'auto', resolves to 'ltr'
//
// isRtl - boolean, true when dir === 'rtl'
</script>Auto-initialized
useRtl() is initialized in app.vue and automatically sets the dir attribute on the <html> element. You only need to call it when you need the values.
When to Use Each
- Use
isRtl- For most conditional logic (boolean checks) - Use
resolvedDir- When you need a guaranteed 'ltr' or 'rtl' value (e.g., for props) - Use
dir- Rarely needed (includes 'auto' option)
CSS Logical Properties ​
Use logical properties instead of directional ones - they automatically flip in RTL:
DO: Use Logical Properties ✅ ​
vue
<template>
<!-- Margins -->
<div class="ms-4">Margin start (left in LTR, right in RTL)</div>
<div class="me-4">Margin end (right in LTR, left in RTL)</div>
<!-- Padding -->
<div class="ps-4">Padding start</div>
<div class="pe-4">Padding end</div>
<!-- Positioning -->
<div class="start-0">Position from start</div>
<div class="end-0">Position from end</div>
<!-- Text alignment -->
<p class="text-start">Aligned to start</p>
<p class="text-end">Aligned to end</p>
<!-- Borders -->
<div class="border-s">Border on start side</div>
<div class="border-e">Border on end side</div>
</template>DON'T: Use Directional Properties ❌ ​
vue
<template>
<!-- ❌ These DON'T flip in RTL -->
<div class="ml-4">Always left margin</div>
<div class="mr-4">Always right margin</div>
<div class="left-0">Always positioned left</div>
<p class="text-left">Always left-aligned</p>
</template>Logical Property Reference ​
| Old (Directional) | New (Logical) | Meaning |
|---|---|---|
ml-* | ms-* | Margin inline start |
mr-* | me-* | Margin inline end |
pl-* | ps-* | Padding inline start |
pr-* | pe-* | Padding inline end |
left-* | start-* | Position from start |
right-* | end-* | Position from end |
text-left | text-start | Text align start |
text-right | text-end | Text align end |
border-l | border-s | Border start |
border-r | border-e | Border end |
Directional Icons ​
Use the special chevron components that handle RTL automatically:
vue
<script setup>
import { ChevronStart, ChevronEnd, ChevronsStart, ChevronsEnd } from '@/components/chevron'
</script>
<template>
<!-- âś… Use directional components -->
<ChevronStart /> <!-- Points to start (left in LTR, right in RTL) -->
<ChevronEnd /> <!-- Points to end (right in LTR, left in RTL) -->
<ChevronsStart /> <!-- Double chevron pointing to start -->
<ChevronsEnd /> <!-- Double chevron pointing to end -->
<!-- ❌ Don't use left/right directly -->
<IconChevronLeft /> <!-- Always points left, doesn't flip -->
<IconChevronRight /> <!-- Always points right, doesn't flip -->
</template>How It Works
These components automatically render the correct icon based on isRtl:
- LTR:
ChevronStart= left,ChevronEnd= right - RTL:
ChevronStart= right,ChevronEnd= left
LTR Content in RTL Layout ​
Some content should stay LTR even in RTL layouts (emails, phone numbers, names):
vue
<script setup>
const { isRtl } = useRtl()
// Computed alignment for LTR content in RTL context
const ltrTextAlignment = computed(() => isRtl.value ? 'text-end' : 'text-start')
</script>
<template>
<!-- Email stays LTR but aligns to the right in RTL -->
<UiInput
v-model="email"
dir="ltr"
:class="ltrTextAlignment"
/>
<!-- Phone number -->
<UiInput
v-model="phone"
dir="ltr"
:class="ltrTextAlignment"
/>
<!-- Name (Latin characters) -->
<UiInput
v-model="name"
dir="ltr"
:class="ltrTextAlignment"
/>
</template>Why This Pattern?
dir="ltr"- Keeps text readable (left-to-right):class="ltrTextAlignment"- Aligns text properly (right in RTL)
This prevents Latin text from appearing backwards while maintaining proper visual alignment.
Content That Should Stay LTR ​
- âś… Email addresses
- âś… Phone numbers
- âś… URLs
- âś… Latin names (English, Spanish)
- âś… Street addresses (if Latin)
- âś… SSN / ID numbers
- âś… Codes (postal, reference, etc.)
Conditional Logic with isRtl ​
When you need different behavior in RTL:
vue
<script setup>
const { isRtl } = useRtl()
// Conditional classes
const containerClass = computed(() => ({
'flex-row': !isRtl.value,
'flex-row-reverse': isRtl.value
}))
// Conditional values
const alignmentStyle = computed(() => ({
textAlign: isRtl.value ? 'right' : 'left'
}))
</script>Rarely Needed
In most cases, logical properties handle RTL automatically. Only use isRtl when you have specific logic that can't be handled with CSS.
Testing RTL ​
Switch to Hebrew ​
Use the language switcher in the UI to switch to Hebrew (עברית).
What to Check ​
- âś… Layout flips (sidebar on right)
- âś… Text aligns right
- âś… Chevrons point correct direction
- âś… Margins/padding flip
- âś… Scroll bars on left
- âś… LTR content (emails, phones) stays readable
- âś… Forms and inputs work properly
Best Practices ​
Do's âś…
- Always use logical properties (
ms-,me-,ps-,pe-) - Use
ChevronStart/ChevronEndcomponents - Keep Latin text as
dir="ltr"with proper alignment - Test with Hebrew language
Don'ts ❌
- Don't use
left/rightin CSS - Don't manually flip icons with transforms
- Don't use directional chevrons (left/right)
- Don't assume text direction in logic
Related: Internationalization → | Styling & Theming → | UI Components →