Логические свойства и многоязычность
margin-inline вместо margin-left: почему привычные стороны света ломаются на арабском и японском — и как перестать о них думать.
Что ты научишься делать
- Заменишь left и right на свойства, которые едут вместе с текстом
- Развернёшь макет в RTL одним атрибутом, без правки стилей
- Поймёшь, где физические свойства всё-таки нужны
#Одна строка, которая ломает весь макет
Ты сверстал цитату: полоска слева, отступ слева, текст по левому краю. Красиво.
Потом сайт переводят на арабский. Текст идёт справа налево, и вся страница разворачивается зеркально — браузер делает это сам, если поставить dir="rtl". Всё, кроме твоей цитаты: полоска осталась слева, отступ тоже, а текст прижался к правому краю. Блок развалился.
Причина в том, что left — это про экран, а не про текст. Логические свойства говорят не «слева», а «в начале строки» — и начало у каждого языка своё.
#Две оси вместо четырёх сторон
Вместо верх-низ-лево-право в логической модели две оси.
Inline — вдоль строки. В русском и английском это горизонталь, слева направо. В арабском — та же горизонталь, но справа налево. В японской вертикальной вёрстке — сверху вниз.
Block — поперёк строки, в направлении, куда идут блоки. Обычно это сверху вниз.
| Физическое | Логическое | Что означает |
|---|---|---|
margin-left | margin-inline-start | Отступ в начале строки |
margin-right | margin-inline-end | Отступ в конце строки |
margin-top | margin-block-start | Отступ до блока |
margin-bottom | margin-block-end | Отступ после блока |
width | inline-size | Размер вдоль строки |
height | block-size | Размер поперёк строки |
text-align: left | text-align: start | Выравнивание к началу строки |
border-left | border-inline-start | Рамка в начале строки |
Так не надо
.quote {
border-left: 3px solid;
padding-left: 16px;
margin-top: 24px;
margin-bottom: 24px;
text-align: left;
}Пять строк, привязанных к сторонам экрана. При dir="rtl" полоска и отступ останутся слева, а текст уедет вправо — блок развалится.
Так надо
.quote {
border-inline-start: 3px solid;
padding-inline-start: 16px;
margin-block: 24px;
text-align: start;
}Четыре строки, и все едут вместе с текстом. Тот же CSS работает и в русском, и в арабском, и в вертикальном японском.
#Позиционирование и inset
У абсолютного позиционирования есть своя пара логических свойств, а заодно очень удобное сокращение.
inset: 0 заменяет четыре строки с нулями — это самый частый способ растянуть элемент на весь родитель. А inset-inline-start ведёт себя как left в русском и как right в арабском.
.card { position: relative;}/* Бейдж всегда в начале верхнего края — где бы это начало ни было */.card__badge { position: absolute; inset-block-start: 8px; inset-inline-start: 8px;}/* Подложка на весь родитель — одна строка вместо четырёх */.card__overlay { position: absolute; inset: 0;}#Скругления и логика углов
Скругления тоже умеют быть логическими, но имена у них непривычные: сначала ось block, потом inline.
border-start-start-radius — угол на пересечении начала блочной оси и начала строчной. В обычном русском тексте это левый верхний угол, в арабском — правый верхний.
#Как это выглядит в реальном проекте
Кусок стилей интерфейса, который живёт сразу в нескольких локалях. Ни одного left или right — и при этом ни одного отдельного файла под RTL.
@layer components { .toast { position: fixed; /* Всплывает в конце строки: справа в русском, слева в арабском */ inset-block-end: 24px; inset-inline-end: 24px; inline-size: min(360px, calc(100vw - 32px)); display: grid; grid-template-columns: auto 1fr auto; gap: 12px; padding-block: 14px; padding-inline: 16px; border-inline-start: 3px solid var(--accent); border-radius: 12px; } .toast__close { /* Кнопка закрытия — в конце строки, а не «справа» */ margin-inline-start: auto; } /* Стрелка — единственное место, где нужен разворот вручную */ .toast__arrow { transform: scaleX(1); } [dir="rtl"] .toast__arrow { transform: scaleX(-1); }}Обрати внимание на последнее правило. Логические свойства разворачивают раскладку, но не разворачивают содержимое: иконка со стрелкой вправо так и останется смотреть вправо. Такие вещи переворачивают вручную — и их обычно единицы.
#Что ещё меняется вместе с языком
Локализация не заканчивается на отступах. Есть вещи, о которых вспоминают только когда сайт уже перевели.
- Длина текста. Немецкий и финский дают слова вдвое длиннее английских, и кнопка «Save» превращается в «Speichern unter». Фиксированная ширина кнопки — верный способ получить обрезанный текст.
- Цифры. В арабской локали могут использоваться свои начертания цифр, и ширина строки меняется.
- Переносы.
hyphens: autoработает только вместе с правильнымlang— без него браузер не знает словарь переносов. - Сортировка и регистр. Это уже забота бэкенда, но верстальщику стоит знать: порядок букв в алфавитах разный.
#Когда физические свойства нужны
- Тень от источника света. Свет в интерфейсе падает сверху и не зависит от языка.
box-shadow: 0 4px 12pxтак и остаётся физическим. - Иконки с направлением. Стрелка «назад» должна смотреть в сторону начала строки — её переворачивают через
transform: scaleX(-1)под[dir="rtl"]. - Привязка к физическому устройству. Вырез камеры, безопасные зоны, аппаратные особенности экрана — это про экран, а не про текст.
- Графика и координаты. Внутри SVG и на canvas работают собственные системы координат, логических свойств там нет.
#Поддержка браузерами
| Возможность | Состояние |
|---|---|
margin-inline, padding-block и прочие отступы | Во всех современных браузерах с 2021 года |
inline-size, block-size | Там же |
inset и его логические варианты | Во всех современных браузерах, Safari подтянулся последним |
border-start-start-radius | Поддерживается, но появилось позже остальных |
writing-mode: vertical-rl | Давно и везде — используется в японской типографике |
Где спотыкаются почти все
Смешаны физические и логические свойства в одном правиле
Почему При RTL margin-left останется слева, а padding-inline-start уедет вправо. Блок разъедется, и причину искать тяжело.
Как надо В одном компоненте — одна система. Проще всего договориться: логические везде, физические только там, где это осознанное исключение.
dir поставлен на body вместо html
Почему Часть браузерных элементов — полосы прокрутки, встроенные виджеты форм — смотрят именно на корневой элемент.
Как надо Пиши <html lang="ar" dir="rtl">. Язык и направление задаются вместе.
Отдельный файл rtl.css с переопределениями
Почему Два файла, которые надо держать в синхроне. Каждая правка компонента требует правки во втором месте — и рано или поздно про неё забывают.
Как надо Логические свойства убирают необходимость во втором файле почти полностью.
Забыто, что содержимое не переворачивается
Почему Раскладка развернулась, а стрелка на кнопке «дальше» продолжает показывать вправо, то есть назад.
Как надо Пройдись по иконкам с направлением и переверни их через scaleX(-1) под [dir="rtl"].
- Строчная ось inline axis
- Направление, вдоль которого идёт текст. Зависит от языка и режима письма.
- Блочная ось block axis
- Направление, в котором идут блоки. Обычно сверху вниз.
- Логическое свойство logical property
- Свойство, привязанное к осям текста, а не к сторонам экрана.
- Направление письма direction, dir
- Атрибут
dir="ltr"илиdir="rtl", задающий начало и конец строки. - Режим письма writing-mode
- Горизонтальное или вертикальное расположение строк.
Проверь себя
Начало строки в арабском справа. Свойство едет вместе с текстом — в этом весь смысл.
Это сокращение для всех четырёх смещений сразу. Самый частый способ растянуть элемент на весь родитель.
Свет в интерфейсе падает сверху независимо от языка. Отступы и выравнивание, наоборот, должны ехать с текстом.
Переворачивается раскладка, а не картинки. Иконки с направлением переворачивают вручную через <code>scaleX(-1)</code>.
Блочная ось идёт поперёк строк — в обычном горизонтальном тексте это как раз верх и низ.
Задание
Переверстать блок под арабскую локаль
Карточка отзыва свёрстана на физических свойствах. Контейнер стоит в режиме dir="rtl", и блок разъехался: полоска и отступ слева, текст справа.
Переведи стили на логические свойства так, чтобы карточка корректно собралась в обе стороны. Разметку не трогай, dir не меняй.
Подсказка 1
border-left становится border-inline-start, padding-left — padding-inline-start.
Подсказка 2
Пара margin-top и margin-bottom с одинаковым значением сворачивается в margin-block: 20px.
Подсказка 3
width заменяется на inline-size, text-align: left — на text-align: start.
Показать решение
<div dir="rtl"> <article class="review"> <p class="review__text">قهوة ممتازة والمكان هادئ.</p> <p class="review__author">مارينا</p> </article></div>.review { border-inline-start: 3px solid #6544e0; padding-inline-start: 16px; padding-inline-end: 8px; margin-block: 20px; text-align: start; inline-size: 320px; max-inline-size: 100%;}.review__text { margin: 0 0 6px; }.review__author { margin: 0; font-size: 13px; color: #666; }