Skip to main content

RTL (Right-to-Left) Support

The template fully supports right-to-left (RTL) languages such as Arabic and Hebrew. This page documents how RTL detection works, how layout direction is applied, and how components adapt to RTL contexts.

Architecture Overview

Source Files

FilePurpose
lib/constants.tsRTL locale list definition
app/layout.tsxRoot layout applying dir attribute
components/language-switcher.tsxLanguage map with isRTL metadata

RTL Locale Configuration

RTL locales are defined as a constant in lib/constants.ts:

export const RTL_LOCALES: readonly Locale[] = ['ar', 'he'] as const;

The language switcher also maintains RTL metadata for each locale:

const languageMap = {
en: { flagSrc: "/flags/en.svg", name: "EN", fullName: "English", isRTL: false },
ar: { flagSrc: "/flags/ar.svg", name: "AR", fullName: "العربية", isRTL: true },
he: { flagSrc: "/flags/he.svg", name: "HE", fullName: "עברית", isRTL: true },
// ... all other locales with isRTL: false
};

How Direction Is Applied

Root Layout Detection

The root app/layout.tsx detects the current locale and sets the dir attribute on the <html> element:

export default async function RootLayout({ children }) {
const locale = await getLocale();
const dir = RTL_LOCALES.includes(locale as Locale) ? 'rtl' : 'ltr';

return (
<html lang={locale} dir={dir} suppressHydrationWarning>
<body className={`${getFontClassNames(locale)} antialiased`}>
{children}
</body>
</html>
);
}

Key behaviors:

  • lang="ar" tells the browser the content language
  • dir="rtl" reverses the entire page layout direction
  • getFontClassNames(locale) can load locale-specific fonts (e.g., Arabic script fonts)

Browser Rendering

When dir="rtl" is set on <html>:

LTR BehaviorRTL Behavior
Text flows left to rightText flows right to left
Content starts at left edgeContent starts at right edge
Scrollbar on rightScrollbar on left
text-align: left defaulttext-align: right default
margin-left pushes rightmargin-left pushes left

CSS Strategies for RTL

1. CSS Logical Properties

CSS logical properties automatically adapt to the document's text direction. Use these instead of physical direction properties:

Physical PropertyLogical PropertyLTR MeaningRTL Meaning
margin-leftmargin-inline-startLeft marginRight margin
margin-rightmargin-inline-endRight marginLeft margin
padding-leftpadding-inline-startLeft paddingRight padding
padding-rightpadding-inline-endRight paddingLeft padding
text-align: lefttext-align: startLeft-alignedRight-aligned
text-align: righttext-align: endRight-alignedLeft-aligned
leftinset-inline-startLeft positionRight position
rightinset-inline-endRight positionLeft position
border-leftborder-inline-startLeft borderRight border
float: leftfloat: inline-startFloat leftFloat right

2. Tailwind CSS RTL Support

Tailwind CSS provides rtl: and ltr: variants that apply styles conditionally:

<!-- Margin that adapts to direction -->
<div class="ml-4 rtl:mr-4 rtl:ml-0">
Content with directional margin
</div>

<!-- Icon that flips in RTL -->
<svg class="rtl:rotate-180">
<path d="M1 9 4-4-4-4" /> <!-- Chevron right -->
</svg>

<!-- Flex direction that reverses -->
<div class="flex flex-row rtl:flex-row-reverse">
<span>First</span>
<span>Second</span>
</div>

3. Tailwind Logical Utilities

Modern Tailwind (v3.3+) supports logical property utilities directly:

<!-- These automatically adapt to RTL -->
<div class="ps-4"> <!-- padding-inline-start: 1rem -->
<div class="pe-4"> <!-- padding-inline-end: 1rem -->
<div class="ms-4"> <!-- margin-inline-start: 1rem -->
<div class="me-4"> <!-- margin-inline-end: 1rem -->
<div class="text-start"> <!-- text-align: start -->
<div class="text-end"> <!-- text-align: end -->

Component Patterns for RTL

Chevron separators in breadcrumbs need to flip direction in RTL:

function ChevronIcon() {
return (
<svg
className="w-3 h-3 mx-1 rtl:rotate-180"
viewBox="0 0 6 10"
>
<path d="m1 9 4-4-4-4" />
</svg>
);
}

Flex-based navigation should use logical properties:

// Header with logo left, actions right (adapts to RTL)
<header className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Logo />
<NavLinks />
</div>
<div className="flex items-center gap-2">
<LanguageSwitcher />
<ThemeToggler />
</div>
</header>

Since justify-between works based on flex direction, and dir="rtl" reverses the inline axis, the layout automatically mirrors in RTL.

Dropdowns that position with right-0 or left-0 need RTL consideration:

// Language switcher dropdown
<div className="absolute -right-4 mt-2 ...">
{/* In RTL, consider using logical positioning */}
</div>

Icons That Should Not Flip

Some icons should maintain their orientation regardless of direction:

// Checkmarks, X icons, and brand logos should NOT flip
<Check className="h-4 w-4" /> // Keep as-is

// Arrows and chevrons SHOULD flip
<ChevronRight className="h-4 w-4 rtl:rotate-180" />

Font Handling for RTL Languages

The root layout uses getFontClassNames(locale) to load appropriate fonts based on locale. Arabic and Hebrew have distinct typographic requirements:

  • Arabic script requires fonts with proper glyph joining (e.g., Noto Sans Arabic)
  • Hebrew script requires fonts with correct glyph forms
  • Line height may need adjustment for Arabic diacritical marks
// app/fonts.ts (conceptual)
export function getFontClassNames(locale: string) {
// Returns appropriate font class based on locale
// Arabic/Hebrew may use different font families
}

Testing RTL

Manual Testing

  1. Switch to Arabic or Hebrew using the LanguageSwitcher
  2. Verify the page mirrors completely (text, margins, icons)
  3. Check that interactive elements (dropdowns, modals) position correctly
  4. Verify scrollbar position moves to the left side

Programmatic Testing

// Check if current locale is RTL
import { RTL_LOCALES, type Locale } from "@/lib/constants";

function isRTL(locale: string): boolean {
return RTL_LOCALES.includes(locale as Locale);
}

Common RTL Issues

IssueCauseFix
Text alignment wrongUsing text-left instead of text-startUse logical properties
Icons not mirroredMissing rtl:rotate-180 on directional iconsAdd RTL variant
Margin on wrong sideUsing ml-* instead of ms-*Use logical Tailwind utilities
Dropdown mispositionedFixed left/right positioningUse logical inset-inline-*
Border on wrong sideUsing border-l-* instead of border-s-*Use border-s-* / border-e-*
Flex order reversed unexpectedlyUsing flex-row-reverse with RTLRemove explicit reverse in RTL

Adding a New RTL Language

To add support for a new RTL language (e.g., Urdu):

  1. Add the locale to LOCALES in lib/constants.ts
  2. Add it to RTL_LOCALES:
export const RTL_LOCALES: readonly Locale[] = ['ar', 'he', 'ur'] as const;
  1. Create the message file at messages/ur.json based on en.json
  2. Add the language map entry in components/language-switcher.tsx:
ur: { flagSrc: "/flags/ur.svg", name: "UR", fullName: "اردو", isRTL: true },
  1. Add the flag SVG to public/flags/ur.svg
  2. Test the layout thoroughly in RTL mode

Direction-Aware Component Checklist

When building or reviewing components, verify:

  • Text alignment uses text-start/text-end instead of text-left/text-right
  • Margins and padding use ms-*/me-*/ps-*/pe-* logical utilities
  • Directional icons (arrows, chevrons) have rtl:rotate-180
  • Absolute/fixed positioning uses inset-inline-start/inset-inline-end
  • Borders use border-s-*/border-e-* logical variants
  • Flex layouts rely on automatic direction reversal (no explicit flex-row-reverse unless needed for both LTR and RTL)
  • Transitions and transforms are direction-neutral
  • Modal and dropdown positions adapt correctly

Best Practices

  1. Prefer CSS logical properties over physical properties. They work correctly in both LTR and RTL without additional rtl: overrides.

  2. Use dir="rtl" on <html> (already handled by the root layout). Individual components should not set their own dir unless embedding opposite-direction content.

  3. Test with real Arabic/Hebrew content, not just English in RTL mode. Right-to-left text with mixed numbers and Latin characters reveals layout issues that reversed English does not.

  4. Do not mirror decorative images or brand logos. Only directional UI elements (arrows, chevrons, progress indicators) should flip.

  5. Keep RTL_LOCALES as the single source of truth for determining text direction. All RTL checks should reference this constant.