Table des matières
Prise en charge de l'écriture de droite à gauche (RTL) : guide pratique d'implémentation CSS et React
La prise en charge du RTL fait partie de ces fonctionnalités que l'on reporte jusqu'à ce qu'on ne puisse plus l'éviter. Les équipes découvrent alors qu'elles ont construit un produit entier en supposant que le texte s'écoule de gauche à droite, et adapter le RTL en cours de route implique de toucher chaque fichier de mise en page.
Ce guide explique comment bien faire les choses dès le départ — ou du moins, comment effectuer l'adaptation aussi proprement que possible. Nous couvrirons les propriétés logiques CSS, l'attribut dir, le comportement de flexbox, les patterns React, les utilitaires Tailwind, et tout ce qu'il y a entre les deux.
Quelles langues nécessitent le RTL ?
Avant de passer à l'implémentation, comprenez le public visé. Les systèmes d'écriture RTL comprennent :
- L'arabe — plus de 400 millions de locuteurs natifs, langue officielle dans 26 pays
- L'hébreu — plus de 10 millions de locuteurs, dominant dans un marché à PIB élevé
- Le persan/farsi — plus de 80 millions de locuteurs en Iran, Afghanistan et Tadjikistan
- L'ourdou — plus de 70 millions de locuteurs natifs, co-officiel au Pakistan
- Le pachto — plus de 60 millions de locuteurs
- Le sindhi, l'ouïghour, le kurde (sorani) — bases d'utilisateurs plus petites mais significatives
Les marchés arabophone et hébraïque représentent à eux seuls un énorme potentiel de revenus pour le e-commerce et le SaaS. Les produits sans prise en charge RTL ne se lancent tout simplement pas dans ces régions — localiser le contenu sans localiser la mise en page n'a aucun sens.
Les propriétés logiques CSS : le fondement
Le changement le plus important que vous puissiez apporter à votre CSS pour la prise en charge RTL est de passer des propriétés physiques aux propriétés logiques. Les propriétés physiques (margin-left, padding-right, border-left) sont codées en dur par rapport aux bords de l'écran. Les propriétés logiques (margin-inline-start, padding-inline-end, border-inline-start) s'adaptent automatiquement en fonction de la direction d'écriture du document.
Ce seul changement résout environ 80 % des problèmes de mise en page RTL.
Table de correspondance :
| Propriété physique | Équivalent logique |
|---|---|
margin-left | margin-inline-start |
margin-right | margin-inline-end |
padding-left | padding-inline-start |
padding-right | padding-inline-end |
border-left | border-inline-start |
border-right | border-inline-end |
left | inset-inline-start |
right | inset-inline-end |
margin-top | margin-block-start |
margin-bottom | margin-block-end |
padding-top | padding-block-start |
padding-bottom | padding-block-end |
text-align: left | text-align: start |
text-align: right | text-align: end |
width | inline-size |
height | block-size |
Compatibilité navigateurs en 2026 : Excellente. Tous les principaux navigateurs prennent en charge les propriétés logiques CSS depuis des années. Vous pouvez les utiliser sans hésitation.
/* Avant — cassé en RTL */
.card {
margin-left: 1rem;
padding-right: 1.5rem;
border-left: 2px solid var(--accent);
text-align: left;
}
/* Après — fonctionne en LTR et RTL */
.card {
margin-inline-start: 1rem;
padding-inline-end: 1.5rem;
border-inline-start: 2px solid var(--accent);
text-align: start;
}
En mode LTR, margin-inline-start se résout en margin-left. En RTL, il se résout en margin-right. Vous écrivez le CSS une seule fois, les deux directions fonctionnent correctement.
L'attribut dir
Définissez la direction au niveau de l'élément HTML :
<html lang="ar" dir="rtl">
Ou par élément lorsque vous avez du contenu mixte :
<p dir="rtl">مرحباً بالعالم</p> <p dir="ltr">Hello world</p>
La propriété CSS direction fait la même chose en CSS :
.arabic-content {
direction: rtl;
}
Le pattern de sélecteur [dir="rtl"] est utile pour les surcharges spécifiques au RTL lorsque les propriétés logiques ne suffisent pas :
/* Par défaut (LTR) */
.nav-icon {
transform: none;
}
/* Surcharge RTL pour les icônes directionnelles */
[dir="rtl"] .nav-icon--arrow {
transform: scaleX(-1);
}
unicode-bidi: bidi-override force une direction spécifique quelle que soit le contenu. À utiliser avec parcimonie — généralement uniquement pour les blocs de code ou le contenu qui ne doit pas être réordonné :
.code-block {
direction: ltr;
unicode-bidi: isolate;
}
Flexbox et Grid en RTL
Voici une bonne nouvelle : flexbox et CSS grid respectent automatiquement la direction du document.
.nav {
display: flex;
flex-direction: row;
gap: 1rem;
}
En LTR, les éléments s'écoulent de gauche à droite. En RTL, le même CSS fait s'écouler les éléments de droite à gauche. Vous n'avez rien à changer.
Ce qui fonctionne automatiquement :
flex-direction: rows'inverse en RTLjustify-content: flex-startaligne sur le début en ligne (à droite en RTL)- L'ordre des colonnes du grid respecte la direction
- Les zones de template du grid s'adaptent correctement
Ce qui nécessite une attention manuelle :
/* Cette transformation ne se retournera pas automatiquement */
.slide-in {
transform: translateX(-100%);
}
/* Correction avec l'équivalent en propriété logique */
[dir="rtl"] .slide-in {
transform: translateX(100%);
}
Le positionnement avec left/right ne se retournera pas non plus :
/* Cassé en RTL */
.tooltip {
position: absolute;
left: 100%;
}
/* Utilisez plutôt les propriétés logiques */
.tooltip {
position: absolute;
inset-inline-start: 100%;
}
Patterns courants nécessitant des corrections RTL
Flèches de navigation et chevrons
Les icônes de flèche indiquant une direction doivent se retourner en RTL :
/* Retourner uniquement les icônes directionnelles */
[dir="rtl"] .icon--arrow-right,
[dir="rtl"] .icon--chevron-right,
[dir="rtl"] .icon--next,
[dir="rtl"] .icon--forward {
transform: scaleX(-1);
}
Ne PAS retourner : les coches, les triangles d'avertissement, les logos, les avatars d'utilisateurs, les icônes d'upload/download, les contrôles multimédias (lecture/pause). Ceux-ci ne sont pas directionnels.
Fils d'Ariane (Breadcrumbs)
Le séparateur entre les éléments du fil d'Ariane doit s'inverser :
.breadcrumb-separator::before {
content: "/";
}
[dir="rtl"] .breadcrumb-separator::before {
content: "\\";
/* Ou utilisez un séparateur neutre pour RTL comme • */
}
Mieux encore : utilisez un caractère séparateur neutre ou un SVG que vous retournez.
Barres de progression
La direction de remplissage est importante visuellement :
.progress-fill {
width: var(--progress);
/* En LTR : se remplit de gauche */
/* En RTL : devrait se remplir de droite */
transform-origin: inline-start;
}
[dir="rtl"] .progress-fill {
margin-inline-start: auto;
/* Ou : utilisez un dégradé en miroir */
}
Ombres de boîte
Les ombres indiquant la profondeur ou l'élévation semblent souvent directionnelles :
/* LTR — ombre à droite */
.card {
box-shadow: 4px 0 8px rgba(0,0,0,0.1);
}
/* RTL — l'ombre devrait être à gauche */
[dir="rtl"] .card {
box-shadow: -4px 0 8px rgba(0,0,0,0.1);
}
Implémentation React
Structurez l'état de direction comme un contexte afin que tout composant puisse le consommer :
// 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);
}
À la racine de l'application, passez la direction depuis votre configuration de locale :
// 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>
);
}
Les composants qui ont besoin de connaître la direction consomment le hook :
function NavArrow() {
const direction = useDirection();
return (
<svg
style={{
transform: direction === "rtl" ? "scaleX(-1)" : "none"
}}
aria-hidden="true"
>
{/* chemin de la flèche */}
</svg>
);
}
Avec les CSS Modules : ajoutez un attribut de données et sélectionnez dessus :
<div data-direction={direction} className={styles.container}>
/* component.module.css */
.container {
padding-inline-start: 1rem;
}
/* Surcharger uniquement ce que les propriétés logiques ne peuvent pas gérer */
.container[data-direction="rtl"] .icon {
transform: scaleX(-1);
}
Lorsque vous utilisez une plateforme comme Better i18n, la direction de la locale est disponible depuis le SDK aux côtés de la locale elle-même, vous pouvez donc dériver dir au même point que vous dérivez la locale sans aucune configuration supplémentaire.
Prise en charge RTL dans Tailwind CSS
Tailwind inclut les variantes rtl: et ltr: par défaut. Activez-les dans votre configuration :
// tailwind.config.js
module.exports = {
// ...
future: {
hoverOnlyWhenSupported: true,
},
};
Les variantes s'activent lorsqu'un élément ancêtre a dir="rtl" ou dir="ltr".
Utilisation basique :
<!-- La marge s'adapte selon la direction --> <div class="ms-4 me-2"> <!-- ms- = margin-inline-start, me- = margin-inline-end --> <!-- Ce sont les utilitaires de propriétés logiques de Tailwind --> </div> <!-- Surcharges directionnelles explicites --> <button class="ltr:ml-4 rtl:mr-4">Valider</button> <!-- Retournement d'icône --> <svg class="rtl:scale-x-[-1]">...</svg>
Les utilitaires de propriétés logiques de Tailwind (ms-, me-, ps-, pe-, border-s, border-e, start-, end-) sont le bon choix par défaut — ils remplacent les utilitaires physiques et gèrent le RTL sans variantes :
<!-- Avant : utilitaires physiques, cassé en RTL --> <nav class="pl-6 border-l-2 text-left"> <!-- Après : utilitaires logiques, fonctionne dans les deux sens --> <nav class="ps-6 border-s-2 text-start">
Utilisez les variantes rtl: et ltr: uniquement pour les choses que les propriétés logiques ne peuvent pas gérer — comme les transformations d'icônes ou les origines d'animation.
Tester les mises en page RTL
Chrome DevTools
La façon la plus rapide de prévisualiser le RTL : ouvrez les DevTools, allez dans l'onglet Rendering et ajoutez simplement dir="rtl" à l'élément racine via le panneau Elements. Aucun rechargement de page nécessaire.
Playwright
Écrivez des tests tenant compte de la direction qui valident la mise en page dans les deux modes :
// rtl.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Mise en page RTL", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
await page.evaluate(() => {
document.documentElement.setAttribute("dir", "rtl");
});
});
test("la navigation s'affiche correctement en RTL", async ({ page }) => {
const nav = page.locator("nav");
await expect(nav).toBeVisible();
// Vérifier que le menu est sur le côté droit en RTL
const navBox = await nav.boundingBox();
const viewportWidth = page.viewportSize()?.width ?? 1280;
expect(navBox!.x).toBeGreaterThan(viewportWidth / 2);
});
test("les champs de formulaire s'alignent correctement", async ({ page }) => {
const label = page.locator('label[for="email"]');
const input = page.locator("#email");
const labelBox = await label.boundingBox();
const inputBox = await input.boundingBox();
// En RTL, l'étiquette doit être à droite ou au-dessus du champ
// Cela dépend de votre mise en page, ajustez l'assertion en conséquence
expect(labelBox).toBeTruthy();
expect(inputBox).toBeTruthy();
});
});
Régression visuelle
Exécutez des tests de régression visuelle avec une variante dir="rtl". Avec la comparaison de captures d'écran intégrée à Playwright :
test("Capture d'écran RTL de la page d'accueil", async ({ page }) => {
await page.goto("/");
await page.evaluate(() => {
document.documentElement.setAttribute("dir", "rtl");
});
await expect(page).toHaveScreenshot("homepage-rtl.png");
});
Liste de contrôle QA manuelle
- Les éléments de navigation s'écoulent de droite à gauche
- La barre latérale est à droite
- Les étiquettes de formulaire s'alignent correctement
- Les icônes directionnelles (flèches, chevrons) sont retournées
- Les barres de progression se remplissent depuis la droite
- Les menus déroulants s'ouvrent dans la bonne direction
- Les modales/tiroirs s'ouvrent du bon côté
- Les séparateurs de fil d'Ariane pointent dans le bon sens
- L'alignement du texte est correct partout
- Les barres de défilement apparaissent à gauche (les navigateurs gèrent cela automatiquement)
- Les chiffres et le texte latin restent LTR dans le contenu RTL
Polices et typographie pour le RTL
L'arabe et l'hébreu nécessitent des polices appropriées — les polices système par défaut peuvent mal s'afficher.
Polices recommandées :
- Noto Sans Arabic — couverture complète de Google, gratuite, correspond à la famille Noto
- IBM Plex Arabic — Excellente pour les outils techniques/développeurs, s'associe à IBM Plex Sans
- Cairo — Moderne, épurée, bonne pour l'UI
- Tajawal — Géométrique, s'associe bien aux polices LTR sans-serif
@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; /* L'arabe est souvent plus lisible légèrement plus grand */
line-height: 1.8; /* L'arabe nécessite plus d'espace vertical que le latin */
}
:lang(he) {
font-family: "Noto Sans Hebrew", "Segoe UI", system-ui, sans-serif;
line-height: 1.6;
}
Ajustements typographiques clés :
- Le texte arabe bénéficie généralement d'une taille de police 1-2px plus grande par rapport au texte latin équivalent à la même taille
- La hauteur de ligne doit être de 1,6 à 2,0 pour l'arabe (contre 1,4 à 1,6 pour le latin) en raison des signes diacritiques
- L'espacement des lettres (
letter-spacing) doit généralement être à 0 ou légèrement négatif pour l'arabe — jamais positif font-variant-numeric: ltrmaintient les chiffres en LTR dans le texte RTL
Texte bidirectionnel (Bidi)
Les documents RTL contiennent souvent des fragments LTR : noms de marques anglaises, chiffres, URLs, code. L'algorithme bidirectionnel Unicode gère la plupart des cas automatiquement, mais les mises en page complexes nécessitent un contrôle explicite.
L'élément <bdi> isole la directionnalité d'un fragment du texte environnant :
<p dir="rtl"> المستخدم <bdi>JohnDoe123</bdi> أرسل رسالة </p>
Sans <bdi>, le nom d'utilisateur pourrait s'afficher avec une directionnalité incorrecte selon ses caractères.
unicode-bidi: isolate fait la même chose en CSS :
.username {
unicode-bidi: isolate;
}
Les chiffres restent LTR dans le texte RTL automatiquement — l'algorithme Bidi s'en charge. Les numéros de téléphone, les prix, les dates s'affichent tous dans l'ordre de lecture. Vous n'avez généralement pas besoin d'intervenir.
Les blocs de code doivent toujours être LTR :
pre, code {
direction: ltr;
unicode-bidi: isolate;
text-align: start; /* start = gauche en LTR */
}
Les noms de marques et les noms de produits écrits en script latin resteront naturellement LTR dans le texte RTL. Si vous constatez un rendu incorrect, encapsulez-les dans <bdi> ou un span avec dir="ltr" :
<span dir="ltr">Better i18n</span>
Lorsque vous gérez des traductions via une plateforme comme Better i18n, le SDK fournit les chaînes de locale incluant déjà tous les marqueurs de direction en ligne que vos traducteurs ont ajoutés, vous n'avez donc pas besoin de post-traiter les chaînes dans le code de l'application.
RTL et Better i18n
Si vous gérez des traductions entre des locales LTR et RTL, les données de direction doivent se trouver aux côtés de la configuration de locale — pas éparpillées dans les composants. Better i18n fournit les métadonnées de locale incluant la direction du texte via le même SDK qui livre vos chaînes de traduction. Lorsque vous basculez un utilisateur vers l'arabe, la direction change automatiquement dans le cadre de l'activation de la locale plutôt que comme une étape séparée que quelqu'un doit penser à câbler.
Liste de contrôle d'implémentation RTL
Avant de livrer la prise en charge RTL, vérifiez :
CSS
- Passage des marges/paddings/bordures physiques aux propriétés logiques
- Remplacement du positionnement
left/rightparinset-inline-start/inset-inline-end - Remplacement de
text-align: left/rightpartext-align: start/end - Les icônes directionnelles se retournent avec le sélecteur
[dir="rtl"]ou la variante Tailwindrtl: - Les animations et transformations tiennent compte de la direction
HTML
- L'attribut
direst défini sur l'élément<html> - L'attribut
langest défini correctement -
<bdi>est utilisé pour le contenu en ligne à direction mixte
React
- Le fournisseur de contexte de direction enveloppe l'application
- Le mappage locale-direction gère toutes les locales RTL
- Les composants consommant la direction utilisent le hook, pas des valeurs codées en dur
Tests
- Les captures d'écran RTL ont été examinées pour toutes les pages clés
- Les tests automatisés couvrent la mise en page RTL
- La liste de contrôle QA est complétée
Typographie
- Les polices appropriées au RTL sont chargées pour l'arabe/hébreu/persan
- La hauteur de ligne est ajustée pour les scripts RTL
- La taille de police est adaptée si nécessaire
La prise en charge RTL n'est pas une fonctionnalité unique — c'est une propriété de l'ensemble du système de mise en page. Les équipes qui s'en sortent bien traitent la direction comme une dimension de premier plan de leur système de design, choisissent les propriétés logiques dès le départ et laissent flexbox et grid faire le gros du travail automatiquement. Les équipes qui peinent sont celles qui ont intégré des hypothèses LTR dans chaque recoin de leur CSS et doivent maintenant les surcharger une par une.
Commencez avec les propriétés logiques. Définissez la direction sur l'élément HTML. Laissez flexbox gérer le reste automatiquement. Puis traitez les patterns spécifiques — icônes, transformations, polices — qui nécessitent une conscience directionnelle. Fait avec soin, la prise en charge RTL demande bien moins de travail que la plupart des équipes ne le prévoient.
Better i18n est une plateforme de localisation orientée développeurs, conçue pour les équipes frontend modernes. SDKs type-safe, workflows basés sur Git, livraison CDN et traduction IA avec application du glossaire — sans fichiers de locale dans votre dépôt.