← Articles

RTL support in Flutter

By Charlin Joe · 8 December 2025

RTL (right-to-left) layout support is required for Arabic, Hebrew, Persian, and Urdu. Flutter has solid RTL support built in, but you need to set it up correctly and test it.

Enabling RTL support

MaterialApp(
  // List supported locales
  supportedLocales: const [
    Locale('en'),
    Locale('ar'), // Arabic
    Locale('he'), // Hebrew
  ],
  // Delegate that provides RTL directionality
  localizationsDelegates: const [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
    AppLocalizations.delegate, // Your generated strings
  ],
  // Optionally force RTL for testing
  // builder: (context, child) => Directionality(textDirection: TextDirection.rtl, child: child!),
)
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: ^0.19.0

How Flutter handles RTL automatically

When TextDirection.rtl is active:

  • Row children reverse order
  • Padding(left: 16) becomes right padding
  • Alignment.centerLeft becomes center-right
  • Icons.arrow_forward flips to point left
  • ListView scrolls from right to left
  • Text aligns right by default

Most built-in widgets handle this automatically.

Directional vs absolute positioning

// WRONG: Uses absolute left/right — won't flip in RTL
Padding(
  padding: const EdgeInsets.only(left: 16, right: 8),
  child: ...,
)

// RIGHT: Uses start/end — flips automatically
Padding(
  padding: const EdgeInsetsDirectional.only(start: 16, end: 8),
  child: ...,
)

// Similarly:
Alignment.centerLeft   // Absolute, won't flip
AlignmentDirectional.centerStart  // Directional, flips

BorderRadius.only(topLeft: Radius.circular(12))    // Absolute
BorderRadiusDirectional.only(topStart: Radius.circular(12))  // Flips

Detecting text direction in widgets

final isRTL = Directionality.of(context) == TextDirection.rtl;

// Flip an icon manually when auto-flip isn't enough
Icon(
  Icons.arrow_back,
  textDirection: Directionality.of(context), // Auto-mirrors on RTL
)

// Or use Transform.scale for explicit flip
Transform.scale(
  scaleX: isRTL ? -1 : 1,
  child: const Icon(Icons.chevron_right),
)

Text alignment

// 'start' aligns left in LTR, right in RTL
Text(
  content,
  textAlign: TextAlign.start, // Correct
)

// 'left' always aligns left — avoid for body text
Text(
  content,
  textAlign: TextAlign.left, // Only for code/numbers where direction must be fixed
)

Testing RTL

# iOS Simulator: Settings → General → Language & Region → Region → Saudi Arabia
# Android: Settings → System → Language → Arabic

# Or in code, temporarily force RTL:
builder: (context, child) => Directionality(
  textDirection: TextDirection.rtl,
  child: child!,
),

Widget test:

testWidgets('layout is correct in RTL', (tester) async {
  await tester.pumpWidget(
    Directionality(
      textDirection: TextDirection.rtl,
      child: const MaterialApp(home: ProductCard()),
    ),
  );
  // Verify layout
  expect(find.byType(ProductCard), findsOneWidget);
});

Number and date formatting

Numbers and dates are locale-sensitive:

import 'package:intl/intl.dart';

// Format numbers for the current locale
final formatted = NumberFormat.currency(
  locale: Localizations.localeOf(context).toString(),
  symbol: '\$',
).format(price);

// Arabic numerals vs Western (depends on locale)
// 'ar' locale: ١٢٣٤٥
// 'ar_EG' locale: 12345 (Egypt uses Western numerals)
// Force Western numerals:
final western = NumberFormat('#,##0.00', 'en').format(price);

Mixed direction text

When displaying mixed LTR/RTL text (e.g., English product names on an Arabic page):

Text.rich(
  TextSpan(
    children: [
      TextSpan(
        text: 'اشتري ', // Arabic: "Buy "
        style: const TextStyle(fontFamily: 'Arabic'),
      ),
      TextSpan(
        text: 'iPhone 15 Pro',  // English product name
        style: const TextStyle(fontFamily: 'Roboto'),
      ),
    ],
  ),
  textDirection: TextDirection.rtl,
)

Common pitfalls

Using EdgeInsets.only(left:...) throughout. Go through widgets and replace positional EdgeInsets with EdgeInsetsDirectional. A good lint rule (use_decorated_box) can catch some of these.

Absolute Alignment in Container. Alignment.centerLeft stays left in RTL. Replace with AlignmentDirectional.centerStart.

Not testing on a real RTL locale. Forcing TextDirection.rtl catches most layout issues but doesn't catch locale-specific text or number formatting bugs. Test on an Arabic or Hebrew device locale as well.

Sign in to like, dislike, or report.

RTL support in Flutter — ANN Tech