skip to content

In Flutter, where does a widget's ambient TextDirection come from, and how do you force a left-to-right island inside an Arabic screen?

level: middleimportance: should knowfreq 36%

answer

  1. an inherited widget above you
  2. Localizations builds it
  3. WidgetsLocalizations.textDirection
  4. the built-in fallback is always ltr
  5. wrap the island in its own Directionality

basics

~10 s

The ambient direction comes from the nearest Directionality widget, which the app's Localizations widget inserts using WidgetsLocalizations.textDirection. For a left-to-right island, wrap that subtree in Directionality(textDirection: TextDirection.ltr).

solid answer

~30 s

`Directionality` is an inherited widget; `Directionality.of(context)` returns its `TextDirection` and asserts when none exists, while `maybeOf` returns `null`. In an app, `MaterialApp` builds a `Localizations` widget that wraps its child in a `Directionality` whose value is `WidgetsLocalizations.textDirection` for the resolved locale. `GlobalWidgetsLocalizations` from `flutter_localizations` returns `rtl` for `ar`, `fa`, `he`, `ps`, `sd` and `ur`; the framework's built-in `DefaultWidgetsLocalizations` always returns `ltr`, so an app that forgets `GlobalWidgetsLocalizations.delegate` stays left-to-right in Arabic. To force a left-to-right island, such as an IBAN or an exchange-rate formula, wrap it in `Directionality(textDirection: TextDirection.ltr, child: ...)`, or pass `textDirection` to the `Text`, `TextField` or `Row` itself.

code

dart · 22 lines
dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;

class RateLine extends StatelessWidget {
  const RateLine({super.key, required this.rate});

  final double rate;

  @override
  Widget build(BuildContext context) {
    final locale = Localizations.localeOf(context).toString();
    final formatted = intl.NumberFormat.decimalPatternDigits(
      locale: locale,
      decimalDigits: 4,
    ).format(rate);
    // The formula reads left to right in every UI language.
    return Directionality(
      textDirection: TextDirection.ltr,
      child: Text('1 USD = $formatted ILS'),
    );
  }
}

go deeper

for a junior

Know that Directionality sets the direction for its subtree and that MaterialApp provides one, so you rarely add it yourself.

for a middle

Explain how Localizations builds Directionality from WidgetsLocalizations.textDirection, why the global widgets delegate is needed for Arabic and Hebrew, and how of differs from maybeOf.

for a senior

Diagnose an Arabic app stuck in ltr from its delegate list, keep left-to-right islands minimal, and resolve the intl TextDirection clash with an import prefix.

for a principal

Decide which content classes are always left-to-right in the product and encode them in shared widgets so screens do not improvise overrides.

## `Directionality` is an inherited widget `Directionality` holds one value, a `TextDirection` (`ltr` or `rtl`), and exposes it to its subtree: - `Directionality.of(context)` returns the nearest value and registers a dependency, so the widget rebuilds if the direction changes. With no ancestor it fails with *"No Directionality widget found."* - `Directionality.maybeOf(context)` returns `null` instead, for widgets that can work without one. Text layout, `Row` and `Column` ordering, directional padding and alignment, and icons marked to mirror all read this value. ## Who inserts it in an app You rarely write a `Directionality` at the top of an app because the `Localizations` widget created by `WidgetsApp` (and therefore by `MaterialApp` and `CupertinoApp`) does it: 1. The app resolves a locale from the device's preferred locales and `supportedLocales`. 2. It loads one delegate per resource type, including a `WidgetsLocalizations`. 3. `Localizations` wraps the app in `Directionality(textDirection: widgetsLocalizations.textDirection)`, and also labels the semantics tree with that direction. Which `WidgetsLocalizations` is loaded decides the direction: | Delegate that supplies `WidgetsLocalizations` | Direction for Arabic or Hebrew | |---|---| | `GlobalWidgetsLocalizations.delegate` (from `flutter_localizations`) | `rtl` for `ar`, `fa`, `he`, `ps`, `sd`, `ur` | | `DefaultWidgetsLocalizations.delegate` (built in, appended last) | always `ltr` | The built-in delegate claims to support every locale, so it silently fills the gap when the global one is missing. A currency converter that adds `Locale('ar')` and `Locale('he')` to `supportedLocales` but forgets the global delegates shows Arabic strings in a left-to-right layout. Passing `AppLocalizations.localizationsDelegates` or `GlobalMaterialLocalizations.delegates` avoids that, because both lists include `GlobalWidgetsLocalizations.delegate`. ## Forcing a left-to-right island Some content is left-to-right whatever the UI language: an IBAN, a card number, a formula such as `1 USD = 3.71 ILS`, code snippets. Options, from broad to narrow: - **Wrap a subtree**: `Directionality(textDirection: TextDirection.ltr, child: ...)` changes direction for everything below it, including padding, alignment and `Row` order. - **Set it on one widget**: `Text`, `RichText`, `TextField`, `Row`, `Column` and `Flex` take a `textDirection` parameter that overrides the ambient value for that widget only. - **Mark a run inside a string**: `package:intl`'s `BidiFormatter` and the `Bidi.LRM` and `Bidi.RLM` marks keep a short left-to-right fragment from being reordered inside right-to-left text. Keep the island as small as possible: a whole-row override also mirrors that row's padding and icons, which is rarely what the design wants. ## Nested locales and mixed-direction text - **`Localizations.override`** creates a nested `Localizations` for one subtree, for example a Hebrew receipt preview inside an English settings screen. It reuses the parent's delegates and builds its own `Directionality` from the overriding locale's `WidgetsLocalizations`, so the preview lays out right-to-left while the rest of the screen stays left-to-right. - **Mixed text inside one string** is handled by the Unicode bidirectional algorithm during text shaping: Latin currency codes and digits inside a Hebrew sentence are laid out as left-to-right runs automatically. The widget's `TextDirection` only sets the **base** direction of the paragraph, which decides where the line starts and how neutral characters such as punctuation at the ends behave. - **Scrolling follows direction too**: horizontal `ListView` and `PageView` derive their axis direction from `Directionality`, so a horizontal list of currencies starts at the right edge in Arabic unless you pass `reverse: true` or an explicit direction. ## The `TextDirection` name clash `package:intl/intl.dart` exports its own class called `TextDirection` (used by its bidi utilities). A file that imports both `package:flutter/material.dart` and `package:intl/intl.dart` without a prefix sees two `TextDirection` types, and `TextDirection.rtl` becomes ambiguous. The usual fix is `import 'package:intl/intl.dart' as intl;`, which is also what gen-l10n's generated code does, or `hide TextDirection` on the intl import. ## Testing both directions - Pump the widget under `Directionality(textDirection: TextDirection.rtl, child: ...)` in a widget test to check the mirrored layout without a full app. - Pump `MaterialApp(locale: const Locale('ar'), ...)` with the real delegates to prove the delegate wiring as well. - Read `Directionality.of(context)` in a debug overlay while testing on a device set to Hebrew.

  • Your Arabic build shows Arabic text but a left-to-right layout; what do you check first?
    Whether `GlobalWidgetsLocalizations.delegate` is in `localizationsDelegates`. Without it the built-in `DefaultWidgetsLocalizations` supplies `WidgetsLocalizations` for every locale and always reports `ltr`, so the app's `Directionality` never turns `rtl`.
  • What is the difference between Directionality.of and Directionality.maybeOf?
    Both read the nearest `Directionality` and register a dependency. `of` asserts with 'No Directionality widget found' when there is none; `maybeOf` returns `null`, which suits widgets that can fall back to a sensible default.
  • Why prefer a per-widget textDirection over wrapping a whole row in Directionality?
    Wrapping changes direction for everything below, so the row's directional padding, alignment and mirrored icons flip back to left-to-right too. Setting `textDirection` on the single `Text` or `TextField` limits the change to the content that needs it.

Directionality is like a country's driving side: every lane and roundabout inside the border follows it, and a left-to-right island is a fenced private track with its own rule that stops at the fence.

saying these in an interview costs you the question

  • Flutter reads the text direction from the device on every Text widget.
  • Adding Locale('ar') to supportedLocales is enough to get a right-to-left layout.
  • Directionality.of returns ltr when no Directionality ancestor exists.
  • The intl package's TextDirection is the same type as Flutter's.
  • Wrap the whole screen in an ltr Directionality to show one IBAN.