Pourquoi les propriétés logiques changent votre façon d’écrire du CSS

Pendant longtemps, nous avons écrit nos interfaces avec des propriétés physiques : [margin](/articles/css-style-page-web)-left, [padding](/articles/css-style-page-web)-right, border-top, width, height. Cette approche paraît naturelle quand on conçoit principalement des sites en français ou en anglais, avec une lecture de gauche à droite et de haut en bas.

Mais le web n’est pas limité à ce modèle. Une interface peut être traduite en arabe, en hébreu, en japonais vertical, ou simplement être intégrée dans un composant dont le sens d’écriture change. Dans ces cas-là, les propriétés physiques deviennent vite un piège : ce qui était “à gauche” dans une langue peut devoir devenir “au début” dans une autre.

Les propriétés logiques CSS permettent de raisonner en termes de flux de lecture plutôt qu’en coordonnées fixes. Au lieu de dire “marge gauche”, on dit “marge au début de l’axe inline”. Cela rend les composants plus robustes, plus accessibles et plus faciles à internationaliser.

Cette logique complète très bien une approche moderne du responsive design avec les container queries CSS, les design tokens en CSS et TypeScript ou encore une architecture CSS structurée avec les cascade layers.

Propriétés physiques contre propriétés logiques

Prenons un bouton avec une icône placée avant le texte :

.button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding-left: 1rem;
  padding-right: 1rem;
  margin-left: auto;
}

Ce code fonctionne dans une interface en français. Mais [margin](/articles/css-style-page-web)-left: auto ne signifie pas “pousser le bouton vers la fin de la ligne”. Il signifie littéralement “mettre une marge automatique à gauche”. En langue RTL, cette intention peut devenir incorrecte.

Avec les propriétés logiques, on exprime l’intention :

.button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding-inline: 1rem;
  margin-inline-start: auto;
}

Ici, [padding](/articles/css-style-page-web)-inline remplace [padding](/articles/css-style-page-web)-left et [padding](/articles/css-style-page-web)-right. margin-inline-start désigne le début de l’axe inline, qui dépend du mode d’écriture et de la direction du texte.

En français, le début inline est à gauche. En arabe, il est à droite. Le composant suit automatiquement.

Comprendre les axes inline et block

Les propriétés logiques reposent sur deux axes :

  • l’axe inline, qui suit la progression du texte dans une ligne ;
  • l’axe block, qui suit la progression des blocs dans la page.

Dans une page française classique, l’axe inline va de gauche à droite, et l’axe block va de haut en bas. Mais ces axes peuvent changer avec direction et writing-mode.

.article {
  direction: rtl;
}

.vertical-card {
  writing-mode: vertical-rl;
}

C’est là que les propriétés logiques prennent tout leur sens. Une règle comme [margin](/articles/css-style-page-web)-block-end ne veut pas dire “marge en bas” dans l’absolu. Elle veut dire “marge à la fin de l’axe block”. Cette nuance devient essentielle dès que l’interface sort du modèle horizontal occidental.

Les correspondances les plus utiles

Voici les remplacements à connaître en priorité :

.card {
  /* Ancien raisonnement physique */
  margin-top: 2rem;
  margin-bottom: 2rem;
  padding-left: 1rem;
  padding-right: 1rem;
  border-left: 4px solid currentColor;
}

.card-logical {
  /* Raisonnement logique */
  margin-block: 2rem;
  padding-inline: 1rem;
  border-inline-start: 4px solid currentColor;
}

Les raccourcis les plus pratiques sont :

.box {
  margin-block: 2rem;
  margin-inline: auto;

  padding-block: 1rem;
  padding-inline: 1.5rem;

  border-block-end: 1px solid var(--color-border);
  inset-block-start: 0;
  inline-size: min(100%, 72rem);
}

inline-size remplace souvent width, tandis que block-size remplace souvent height. Là encore, le bénéfice est conceptuel : on décrit la taille dans l’axe du texte, pas seulement une largeur physique.

Exemple : une carte compatible RTL

Imaginons une carte avec une bordure d’accent, un titre et un contenu. En CSS classique, on écrirait souvent :

.notice {
  border-left: 0.25rem solid #2563eb;
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
}

Cette bordure reste à gauche, même lorsque la direction passe en RTL. Si la bordure représente le début du contenu, c’est une erreur sémantique.

Version logique :

.notice {
  border-inline-start: 0.25rem solid #2563eb;
  padding-block: 1rem;
  padding-inline: 1.25rem;
  margin-block-end: 1.5rem;
}

.notice__title {
  margin-block: 0 0.5rem;
  font-size: 1.125rem;
}

.notice__content {
  margin-block: 0;
}

HTML minimal :

<article class="notice">
  <h2 class="notice__title">Paiement confirmé</h2>
  <p class="notice__content">Votre commande sera traitée dans les prochaines minutes.</p>
</article>

Pour tester le comportement, il suffit d’ajouter dir="rtl" sur un parent :

<section dir="rtl">
  <article class="notice">
    <h2 class="notice__title">تم تأكيد الدفع</h2>
    <p class="notice__content">سيتم معالجة طلبك خلال الدقائق القادمة.</p>
  </article>
</section>

La bordure passe automatiquement du bon côté, sans classe supplémentaire, sans JavaScript et sans duplication du CSS.

Intégration dans un composant Vue

Les propriétés logiques sont particulièrement utiles dans les composants réutilisables. Voici un exemple Vue simple :

<script setup lang="ts">
defineProps<{
  title: string
  tone?: 'info' | 'success' | 'warning'
}>()
</script>

<template>
  <aside class="callout" :data-tone="tone ?? 'info'">
    <h2 class="callout__title">{{ title }}</h2>
    <div class="callout__body">
      <slot />
    </div>
  </aside>
</template>

<style scoped>
.callout {
  padding-block: 1rem;
  padding-inline: 1.25rem;
  border-inline-start: 0.25rem solid var(--callout-color);
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--callout-color), white 88%);
}

.callout[data-tone='info'] {
  --callout-color: #2563eb;
}

.callout[data-tone='success'] {
  --callout-color: #16a34a;
}

.callout[data-tone='warning'] {
  --callout-color: #ca8a04;
}

.callout__title {
  margin-block: 0 0.5rem;
}

.callout__body {
  margin-block: 0;
}
</style>

On combine ici plusieurs principes : des propriétés logiques, des variables CSS, une variante pilotée par attribut et une couleur dérivée avec color-mix(). Pour approfondir cette dernière partie, vous pouvez lire l’article sur CSS color-mix().

Intégration dans un composant React

Le même principe fonctionne évidemment dans React, sans dépendre d’une bibliothèque de styles :

type AlertProps = {
  title: string
  children: React.ReactNode
}

export function Alert({ title, children }: AlertProps) {
  return (
    <aside className="alert">
      <h2 className="alert__title">{title}</h2>
      <div className="alert__content">{children}</div>
    </aside>
  )
}
.alert {
  inline-size: min(100%, 42rem);
  padding-block: 1rem;
  padding-inline: 1.25rem;
  border-inline-start: 0.25rem solid var(--accent, #2563eb);
  margin-block: 1rem;
}

.alert__title {
  margin-block-start: 0;
  margin-block-end: 0.5rem;
}

.alert__content {
  margin-block: 0;
}

L’intérêt n’est pas lié à React lui-même, mais à la portabilité du composant. Qu’il soit utilisé dans une application React, Vue, Nuxt, Next ou dans un simple rendu HTML, son layout reste cohérent.

Quand garder les propriétés physiques

Les propriétés logiques ne remplacent pas tout. Certaines situations sont réellement physiques. Par exemple, une animation qui simule la gravité peut avoir besoin de top ou bottom. Une carte géographique, un repère spatial ou un canvas WebGL peuvent également dépendre de coordonnées explicites.

.falling-object {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
}

Dans ce cas, top et left décrivent bien une position physique. Le bon critère est simple : si la règle dépend du sens de lecture, utilisez une propriété logique. Si elle dépend d’une position spatiale absolue, une propriété physique reste acceptable.

Stratégie d’adoption progressive

Inutile de réécrire toute une base CSS en une seule fois. Commencez par les composants réutilisables : cartes, alertes, boutons, champs de formulaire, menus, panneaux latéraux. Ce sont eux qui bénéficient le plus d’une logique indépendante de la langue.

Vous pouvez aussi créer une règle d’équipe : pour tout nouveau composant, préférer [margin](/articles/css-style-page-web)-block, [padding](/articles/css-style-page-web)-inline, border-inline-start, inline-size et block-size dès que l’intention est liée au flux du document.

Dans une architecture plus large, les propriétés logiques se combinent très bien avec les CSS @scope, le nesting CSS natif et les CSS Subgrid. Elles participent au même objectif : écrire des styles qui décrivent mieux l’intention du composant.

Tester réellement vos interfaces

Pour valider vos composants, ne vous contentez pas de les regarder en français. Ajoutez des tests visuels simples :

<div dir="ltr">
  <button class="button">Continuer</button>
</div>

<div dir="rtl">
  <button class="button">متابعة</button>
</div>

Vous pouvez aussi tester un mode vertical :

.demo-vertical {
  writing-mode: vertical-rl;
  border: 1px solid currentColor;
  padding-block: 1rem;
  padding-inline: 0.5rem;
}

Ces tests révèlent très vite les restes de left, right, top ou bottom utilisés par habitude.

Conclusion

Les propriétés logiques CSS ne sont pas seulement une fonctionnalité pour les sites multilingues. Elles encouragent une manière plus juste de concevoir les interfaces : raisonner en flux, en contenu, en direction de lecture et en composants adaptables.

En remplaçant progressivement les propriétés physiques par inline, block, start et end, vous rendez vos styles plus maintenables, plus accessibles et mieux préparés à l’internationalisation. C’est un changement discret, mais très structurant pour la qualité d’une base frontend moderne.