Skip to content

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-lefttext-startText align start
text-righttext-endText align end
border-lborder-sBorder start
border-rborder-eBorder 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/ChevronEnd components
  • Keep Latin text as dir="ltr" with proper alignment
  • Test with Hebrew language

Don'ts ❌

  • Don't use left/right in 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 →