Tutorials//10 Min. Lesezeit

Von rechts nach links (RTL): Ein praktischer Leitfaden zur CSS- und React-Implementierung

Eray Gündoğmuş
Teilen

RTL-Unterstützung gehört zu jenen Features, die so lange aufgeschoben werden, bis es absolut nicht mehr geht. Dann stellen Teams fest, dass sie ein gesamtes Produkt gebaut haben, das davon ausgeht, dass Text von links nach rechts fließt — und die nachträgliche RTL-Integration bedeutet, jede Layout-Datei anfassen zu müssen.

Dieser Leitfaden beschreibt, wie man es von Anfang an richtig macht — oder zumindest, wie man die nachträgliche Integration so sauber wie möglich gestaltet. Wir behandeln CSS-logische Eigenschaften, das dir-Attribut, Flexbox-Verhalten, React-Muster, Tailwind-Utilities und alles dazwischen.

Welche Sprachen benötigen RTL?

Vor der Implementierung sollte man die Zielgruppe verstehen. RTL-Schriftsysteme umfassen:

  • Arabisch — über 400 Mio. Muttersprachler, offizielle Sprache in 26 Ländern
  • Hebräisch — über 10 Mio. Sprecher, dominierend in einem Hocheinkommensmarkt
  • Persisch/Farsi — über 80 Mio. Sprecher im Iran, Afghanistan und Tadschikistan
  • Urdu — über 70 Mio. Muttersprachler, Ko-Amtssprache in Pakistan
  • Paschtu — über 60 Mio. Sprecher
  • Sindhi, Uigurisch, Kurdisch (Sorani) — kleinere, aber bedeutende Nutzerbasen

Allein die arabischen und hebräischen Märkte repräsentieren ein enormes Umsatzpotenzial im E-Commerce und SaaS. Produkte ohne RTL-Unterstützung werden in diesen Regionen schlicht nicht eingesetzt — Inhalte zu lokalisieren, ohne das Layout zu lokalisieren, ist sinnlos.

CSS-logische Eigenschaften: Die Grundlage

Die einzelne größte Verbesserung, die man am CSS für RTL-Unterstützung vornehmen kann, ist der Wechsel von physischen zu logischen Eigenschaften. Physische Eigenschaften (margin-left, padding-right, border-left) sind fest an Bildschirmkanten gebunden. Logische Eigenschaften (margin-inline-start, padding-inline-end, border-inline-start) passen sich automatisch an die Schreibrichtung des Dokuments an.

Diese eine Änderung löst ungefähr 80 % der RTL-Layout-Probleme.

Zuordnungstabelle:

Physische EigenschaftLogisches Äquivalent
margin-leftmargin-inline-start
margin-rightmargin-inline-end
padding-leftpadding-inline-start
padding-rightpadding-inline-end
border-leftborder-inline-start
border-rightborder-inline-end
leftinset-inline-start
rightinset-inline-end
margin-topmargin-block-start
margin-bottommargin-block-end
padding-toppadding-block-start
padding-bottompadding-block-end
text-align: lefttext-align: start
text-align: righttext-align: end
widthinline-size
heightblock-size

Browser-Unterstützung 2026: Ausgezeichnet. Alle gängigen Browser unterstützen CSS-logische Eigenschaften seit Jahren. Man kann sie bedenkenlos verwenden.

/* Vorher — bricht in RTL */
.card {
  margin-left: 1rem;
  padding-right: 1.5rem;
  border-left: 2px solid var(--accent);
  text-align: left;
}

/* Nachher — funktioniert in LTR und RTL */
.card {
  margin-inline-start: 1rem;
  padding-inline-end: 1.5rem;
  border-inline-start: 2px solid var(--accent);
  text-align: start;
}

Im LTR-Modus wird margin-inline-start zu margin-left aufgelöst. In RTL wird es zu margin-right. Das CSS wird einmal geschrieben, beide Richtungen funktionieren korrekt.

Das dir-Attribut

Richtung auf HTML-Element-Ebene festlegen:

<html lang="ar" dir="rtl">

Oder elementweise bei gemischten Inhalten:

<p dir="rtl">مرحباً بالعالم</p>
<p dir="ltr">Hello world</p>

Die CSS-Eigenschaft direction macht dasselbe, aber in CSS:

.arabic-content {
  direction: rtl;
}

Das [dir="rtl"]-Selektormuster ist nützlich für RTL-spezifische Überschreibungen, wenn logische Eigenschaften nicht ausreichen:

/* Standard (LTR) */
.nav-icon {
  transform: none;
}

/* RTL-Überschreibung für Richtungssymbole */
[dir="rtl"] .nav-icon--arrow {
  transform: scaleX(-1);
}

unicode-bidi: bidi-override erzwingt eine bestimmte Richtung unabhängig vom Inhalt. Sparsam einsetzen — normalerweise nur für Code-Blöcke oder Inhalte, die nicht umsortiert werden dürfen:

.code-block {
  direction: ltr;
  unicode-bidi: isolate;
}

Flexbox und Grid in RTL

Hier gibt es wirklich gute Nachrichten: Flexbox und CSS Grid berücksichtigen die Dokumentrichtung automatisch.

.nav {
  display: flex;
  flex-direction: row;
  gap: 1rem;
}

In LTR fließen Elemente von links nach rechts. In RTL bewirkt dasselbe CSS, dass Elemente von rechts nach links fließen. Es muss nichts geändert werden.

Was automatisch funktioniert:

  • flex-direction: row kehrt sich in RTL um
  • justify-content: flex-start richtet sich am Zeilenanfang aus (rechts in RTL)
  • Grid-Spaltenreihenfolge berücksichtigt die Richtung
  • Grid-Template-Bereiche passen sich korrekt an

Was manuelle Aufmerksamkeit erfordert:

/* Dieser Transform kippt nicht automatisch */
.slide-in {
  transform: translateX(-100%);
}

/* Mit logischem Eigenschaftsäquivalent korrigieren */
[dir="rtl"] .slide-in {
  transform: translateX(100%);
}

Positionierung mit left/right kippt ebenfalls nicht:

/* Bricht in RTL */
.tooltip {
  position: absolute;
  left: 100%;
}

/* Stattdessen logische Eigenschaften verwenden */
.tooltip {
  position: absolute;
  inset-inline-start: 100%;
}

Häufige Muster, die RTL-Korrekturen benötigen

Pfeilsymbole, die eine Richtung anzeigen, müssen in RTL gespiegelt werden:

/* Nur Richtungssymbole spiegeln */
[dir="rtl"] .icon--arrow-right,
[dir="rtl"] .icon--chevron-right,
[dir="rtl"] .icon--next,
[dir="rtl"] .icon--forward {
  transform: scaleX(-1);
}

Nicht spiegeln: Häkchen, Warndreiecke, Logos, Benutzer-Avatare, Upload/Download-Symbole, Mediensteuerelemente (Abspielen/Pausieren). Diese sind nicht richtungsabhängig.

Der Trenner zwischen Breadcrumb-Elementen muss umgekehrt werden:

.breadcrumb-separator::before {
  content: "/";
}

[dir="rtl"] .breadcrumb-separator::before {
  content: "\\";
  /* Oder einen geeigneten RTL-neutralen Trenner wie • verwenden */
}

Besser: ein neutrales Trennzeichen oder ein SVG verwenden, das gespiegelt werden kann.

Fortschrittsbalken

Die Füllrichtung ist visuell relevant:

.progress-fill {
  width: var(--progress);
  /* In LTR: füllt von links */
  /* In RTL: sollte von rechts füllen */
  transform-origin: inline-start;
}

[dir="rtl"] .progress-fill {
  margin-inline-start: auto;
  /* Oder: einen gespiegelten Gradienten verwenden */
}

Box-Schatten

Schatten, die Tiefe oder Erhebung anzeigen, wirken oft richtungsabhängig:

/* LTR — Schatten rechts */
.card {
  box-shadow: 4px 0 8px rgba(0,0,0,0.1);
}

/* RTL — Schatten sollte links sein */
[dir="rtl"] .card {
  box-shadow: -4px 0 8px rgba(0,0,0,0.1);
}

React-Implementierung

Den Richtungsstatus als Context strukturieren, damit jede Komponente darauf zugreifen kann:

// direction-context.tsx
import { createContext, useContext, ReactNode } from "react";

type Direction = "ltr" | "rtl";

const DirectionContext = createContext<Direction>("ltr");

interface DirectionProviderProps {
  direction: Direction;
  children: ReactNode;
}

export function DirectionProvider({ direction, children }: DirectionProviderProps) {
  return (
    <DirectionContext.Provider value={direction}>
      <div dir={direction} className={`dir-${direction}`}>
        {children}
      </div>
    </DirectionContext.Provider>
  );
}

export function useDirection(): Direction {
  return useContext(DirectionContext);
}

Am App-Stamm die Richtung aus der Locale-Konfiguration übergeben:

// app.tsx
import { DirectionProvider } from "./direction-context";

const RTL_LOCALES = new Set(["ar", "he", "fa", "ur"]);

export function App({ locale }: { locale: string }) {
  const direction = RTL_LOCALES.has(locale) ? "rtl" : "ltr";

  return (
    <DirectionProvider direction={direction}>
      <Router />
    </DirectionProvider>
  );
}

Komponenten, die die Richtung kennen müssen, konsumieren den Hook:

function NavArrow() {
  const direction = useDirection();

  return (
    <svg
      style={{
        transform: direction === "rtl" ? "scaleX(-1)" : "none"
      }}
      aria-hidden="true"
    >
      {/* Pfadpfeil */}
    </svg>
  );
}

Mit CSS Modules: ein Data-Attribut hinzufügen und darauf selektieren:

<div data-direction={direction} className={styles.container}>
/* component.module.css */
.container {
  padding-inline-start: 1rem;
}

/* Nur überschreiben, was logische Eigenschaften nicht abdecken */
.container[data-direction="rtl"] .icon {
  transform: scaleX(-1);
}

Bei Verwendung einer Plattform wie Better i18n ist die Locale-Richtung vom SDK zusammen mit der Locale selbst verfügbar, sodass dir an derselben Stelle wie die Locale abgeleitet werden kann — ohne zusätzliche Einrichtung.

Tailwind CSS RTL-Unterstützung

Tailwind enthält rtl:- und ltr:-Varianten direkt out of the box. In der Konfiguration aktivieren:

// tailwind.config.js
module.exports = {
  // ...
  future: {
    hoverOnlyWhenSupported: true,
  },
};

Die Varianten werden aktiviert, wenn ein übergeordnetes Element dir="rtl" oder dir="ltr" hat.

Grundlegende Verwendung:

<!-- Margin kippt je nach Richtung -->
<div class="ms-4 me-2">
  <!-- ms- = margin-inline-start, me- = margin-inline-end -->
  <!-- Das sind Tailwinds logische Eigenschaften-Utilities -->
</div>

<!-- Explizite Richtungsüberschreibungen -->
<button class="ltr:ml-4 rtl:mr-4">Absenden</button>

<!-- Symbol spiegeln -->
<svg class="rtl:scale-x-[-1]">...</svg>

Tailwinds logische Eigenschafts-Utilities (ms-, me-, ps-, pe-, border-s, border-e, start-, end-) sind die richtige Standardwahl — sie ersetzen physische Utilities und handhaben RTL ganz ohne Varianten:

<!-- Vorher: physische Utilities, bricht in RTL -->
<nav class="pl-6 border-l-2 text-left">

<!-- Nachher: logische Utilities, funktioniert in beiden -->
<nav class="ps-6 border-s-2 text-start">

rtl:- und ltr:-Varianten nur für Dinge verwenden, die logische Eigenschaften nicht abdecken können — wie Symbol-Transforms oder Animationsursprünge.

RTL-Layouts testen

Chrome DevTools

Der schnellste Weg, RTL in der Vorschau zu sehen: DevTools öffnen, zum Rendering-Tab wechseln, und "Emulate CSS media feature forced-colors" suchen — aber nützlicher ist es, einfach dir="rtl" über das Elements-Panel zum Root-Element hinzuzufügen. Kein Seiten-Reload erforderlich.

Playwright

Richtungsbasierte Tests schreiben, die das Layout in beiden Modi validieren:

// rtl.spec.ts
import { test, expect } from "@playwright/test";

test.describe("RTL-Layout", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/");
    await page.evaluate(() => {
      document.documentElement.setAttribute("dir", "rtl");
    });
  });

  test("Navigation wird in RTL korrekt dargestellt", async ({ page }) => {
    const nav = page.locator("nav");
    await expect(nav).toBeVisible();

    // Überprüfen, ob das Menü in RTL auf der rechten Seite ist
    const navBox = await nav.boundingBox();
    const viewportWidth = page.viewportSize()?.width ?? 1280;
    expect(navBox!.x).toBeGreaterThan(viewportWidth / 2);
  });

  test("Formulareingaben richten sich korrekt aus", async ({ page }) => {
    const label = page.locator('label[for="email"]');
    const input = page.locator("#email");

    const labelBox = await label.boundingBox();
    const inputBox = await input.boundingBox();

    // In RTL sollte das Label rechts von oder über der Eingabe sein
    // Das hängt vom Layout ab, Assertion entsprechend anpassen
    expect(labelBox).toBeTruthy();
    expect(inputBox).toBeTruthy();
  });
});

Visuelle Regression

Visuelle Regressionstests mit einer dir="rtl"-Variante ausführen. Mit Playwrights eingebautem Screenshot-Vergleich:

test("RTL-Homepage-Snapshot", async ({ page }) => {
  await page.goto("/");
  await page.evaluate(() => {
    document.documentElement.setAttribute("dir", "rtl");
  });
  await expect(page).toHaveScreenshot("homepage-rtl.png");
});

Manuelle QA-Checkliste

  • Navigationselemente fließen von rechts nach links
  • Seitenleiste ist rechts
  • Formularbeschriftungen richten sich korrekt aus
  • Richtungssymbole (Pfeile, Chevrons) sind gespiegelt
  • Fortschrittsbalken füllen von rechts
  • Dropdowns öffnen in der richtigen Richtung
  • Modals/Drawer öffnen von der richtigen Seite
  • Breadcrumb-Trennzeichen zeigen in die richtige Richtung
  • Textausrichtung ist durchgehend korrekt
  • Scrollbalken erscheinen links (Browser handhaben das automatisch)
  • Zahlen und lateinischer Text bleiben LTR innerhalb von RTL-Inhalten

Schriften und Typografie für RTL

Arabisch und Hebräisch benötigen geeignete Schriftarten — Standard-Systemschriften können schlecht dargestellt werden.

Empfohlene Schriftarten:

  • Noto Sans Arabic — Googles umfassende Abdeckung, kostenlos, passend zur Noto-Familie
  • IBM Plex Arabic — Hervorragend für technische/Entwickler-Tools, passt zu IBM Plex Sans
  • Cairo — Modern, klar, gut für UI
  • Tajawal — Geometrisch, passt gut zu serifenlosen LTR-Schriften
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@400;500;600;700&display=swap");

:lang(ar) {
  font-family: "Noto Sans Arabic", "Segoe UI", system-ui, sans-serif;
  font-size: 1.05em; /* Arabisch liest sich oft etwas größer besser */
  line-height: 1.8;  /* Arabisch benötigt mehr vertikalen Platz als Latein */
}

:lang(he) {
  font-family: "Noto Sans Hebrew", "Segoe UI", system-ui, sans-serif;
  line-height: 1.6;
}

Wichtige typografische Anpassungen:

  • Arabischer Text profitiert typischerweise von 1–2 Pixel größerer Schriftgröße im Vergleich zu gleichwertigem lateinischen Text
  • Zeilenhöhe sollte für Arabisch 1,6–2,0 betragen (gegenüber 1,4–1,6 für Latein) wegen diakritischer Zeichen
  • Buchstabenabstand (letter-spacing) sollte für Arabisch generell 0 oder leicht negativ sein — niemals positiv
  • font-variant-numeric: ltr hält Ziffern LTR innerhalb von RTL-Text

Bidirektionaler Text (Bidi)

RTL-Dokumente enthalten oft LTR-Fragmente: englische Markennamen, Zahlen, URLs, Code. Der Unicode-Bidirektionale-Algorithmus behandelt die meisten Fälle automatisch, aber komplexe Layouts benötigen explizite Steuerung.

Das <bdi>-Element isoliert die Richtungseigenschaft eines Fragments vom umgebenden Text:

<p dir="rtl">
  المستخدم <bdi>JohnDoe123</bdi> أرسل رسالة
</p>

Ohne <bdi> könnte der Benutzername je nach seinen Zeichen mit falscher Richtung dargestellt werden.

unicode-bidi: isolate macht dasselbe in CSS:

.username {
  unicode-bidi: isolate;
}

Zahlen bleiben LTR in RTL-Text automatisch — der Bidi-Algorithmus handhabt das. Telefonnummern, Preise, Daten werden alle in Leserichtung dargestellt. Normalerweise ist kein Eingreifen erforderlich.

Code-Blöcke müssen immer LTR sein:

pre, code {
  direction: ltr;
  unicode-bidi: isolate;
  text-align: start; /* start = links in LTR */
}

Markennamen und Produktnamen, die in lateinischer Schrift geschrieben sind, bleiben innerhalb von RTL-Text natürlich LTR. Bei falscher Darstellung in <bdi> oder einem Span mit dir="ltr" einschließen:

<span dir="ltr">Better i18n</span>

Wenn Übersetzungen über eine Plattform wie Better i18n verwaltet werden, liefert das SDK Locale-Strings bereits einschließlich aller inline Richtungsmarkierungen, die Übersetzer hinzugefügt haben — daher ist keine nachträgliche Verarbeitung von Strings im Anwendungscode erforderlich.

RTL und Better i18n

Wenn Übersetzungen über LTR- und RTL-Locales verwaltet werden, sollten die Richtungsdaten neben der Locale-Konfiguration liegen — nicht über Komponenten verstreut. Better i18n stellt Locale-Metadaten einschließlich Textrichtung über dasselbe SDK bereit, das die Übersetzungsstrings liefert. Wenn ein Benutzer auf Arabisch umgestellt wird, wechselt die Richtung automatisch als Teil der Locale-Aktivierung — nicht als separater Schritt, an den jemand extra denken muss.

RTL-Implementierungscheckliste

Vor dem Ausliefern der RTL-Unterstützung prüfen:

CSS

  • Physische margin/padding/border auf logische Eigenschaften umgestellt
  • left/right-Positionierung durch inset-inline-start/inset-inline-end ersetzt
  • text-align: left/right durch text-align: start/end ersetzt
  • Richtungssymbole spiegeln mit [dir="rtl"]-Selektor oder rtl:-Tailwind-Variante
  • Animationen und Transforms berücksichtigen die Richtung

HTML

  • dir-Attribut am <html>-Element gesetzt
  • lang-Attribut korrekt gesetzt
  • <bdi> für gemischte Inline-Inhalte verwendet

React

  • Richtungs-Context-Provider umschließt die App
  • Locale-zu-Richtung-Mapping deckt alle RTL-Locales ab
  • Komponenten, die die Richtung konsumieren, verwenden den Hook, keine fest codierten Werte

Testing

  • RTL-Screenshots für alle Hauptseiten überprüft
  • Automatisierte Tests decken RTL-Layout ab
  • QA-Checkliste abgeschlossen

Typografie

  • RTL-geeignete Schriften für Arabisch/Hebräisch/Persisch geladen
  • Zeilenhöhe für RTL-Schriftsysteme angepasst
  • Schriftgröße bei Bedarf angepasst

RTL-Unterstützung ist kein einzelnes Feature — es ist eine Eigenschaft des gesamten Layout-Systems. Teams, die es richtig machen, behandeln die Richtung als erstklassige Dimension ihres Designsystems, wählen von Anfang an logische Eigenschaften und lassen Flexbox und Grid die schwere Arbeit erledigen. Teams, die damit kämpfen, haben LTR-Annahmen in jede Ecke ihres CSS eingebaut und müssen sie jetzt einzeln überschreiben.

Mit logischen Eigenschaften beginnen. Richtung am HTML-Element setzen. Flexbox den Rest automatisch handhaben lassen. Dann die spezifischen Muster angehen — Symbole, Transforms, Schriften — die Richtungsbewusstsein erfordern. Sorgfältig durchgeführt erfordert RTL-Unterstützung weit weniger Aufwand, als die meisten Teams erwarten.

Better i18n ist eine entwicklerorientierte Lokalisierungsplattform, die für moderne Frontend-Teams entwickelt wurde. Typsichere SDKs, Git-basierte Workflows, CDN-Auslieferung und KI-Übersetzung mit Glossar-Durchsetzung — ohne Locale-Dateien im Repository.

Comments

Loading comments...