Арабский текст читается справа налево. Японский — сверху вниз. Если написать margin-left: 16px, на RTL-странице этот отступ будет не с «начала» текста, а с «конца». Логические свойства решают это: они описывают расположение относительно направления текста, а не физических сторон.
| Физическое | Логическое | Значение |
|---------------------|-----------------------------|-----------------------------|
| margin-left | margin-inline-start | Начало строки |
| margin-right | margin-inline-end | Конец строки |
| margin-top | margin-block-start | Начало блока (верх) |
| margin-bottom | margin-block-end | Конец блока (низ) |
| padding-left | padding-inline-start | — |
| width | inline-size | Размер в направлении строки |
| height | block-size | Размер в направлении блока |
| top | inset-block-start | — |
| left | inset-inline-start | — |
/* LTR (по умолчанию): inline-start = left, block-start = top */
/* RTL: inline-start = right, block-start = top */
/* Вертикальный японский: inline-start = top, block-start = right */.article {
/* Вместо margin-left и margin-right */
margin-inline: auto; /* margin-inline-start + margin-inline-end */
margin-inline-start: 24px; /* margin-left в LTR, margin-right в RTL */
/* Вместо padding-top и padding-bottom */
padding-block: 32px; /* padding-block-start + padding-block-end */
padding-inline: 16px;
/* Вместо width */
inline-size: 600px; /* Ширина в LTR, высота при вертикальном тексте */
max-inline-size: 100%;
min-block-size: 400px; /* min-height в LTR */
}
/* Позиционирование */
.tooltip {
inset-inline-start: 0; /* left: 0 в LTR, right: 0 в RTL */
inset-block-start: 100%; /* top: 100% */
inset: 0; /* Все четыре стороны: top right bottom left */
}
/* Границы */
.card {
border-inline-start: 4px solid #7b2ff7; /* border-left в LTR */
border-block-end: 1px solid #e2e8f0; /* border-bottom */
border-start-start-radius: 8px; /* border-top-left-radius в LTR */
}/* Направление текста */
html[dir="rtl"] { direction: rtl; } /* Арабский, иврит */
html[lang="ar"] { direction: rtl; }
/* Режим письма */
.vertical-text {
writing-mode: vertical-rl; /* Вертикальный, справа налево (японский) */
writing-mode: vertical-lr; /* Вертикальный, слева направо */
}/* Плохо — сломается в RTL */
.breadcrumb-item + .breadcrumb-item::before {
margin-left: 8px;
margin-right: 8px;
}
/* Хорошо — работает в любом направлении */
.breadcrumb-item + .breadcrumb-item::before {
margin-inline: 8px;
}
/* Плохо — иконка всегда слева */
.button-with-icon {
padding-left: 40px;
}
/* Хорошо — иконка с «начала» текста */
.button-with-icon {
padding-inline-start: 40px;
}Все современные браузеры поддерживают логические свойства. Для старых браузеров используй fallback:
.element {
margin-left: 16px; /* Fallback */
margin-inline-start: 16px; /* Логическое (переопределит если поддерживается) */
}Демонстрация разницы физических и логических свойств в LTR и RTL окружении
// Демонстрация логических vs физических свойств в LTR и RTL
function createDemo(direction) {
const container = document.createElement('div')
container.dir = direction
container.style.cssText = `
border: 2px solid ${direction === 'ltr' ? '#7b2ff7' : '#f59e0b'};
padding: 16px;
border-radius: 8px;
font-family: sans-serif;
margin-bottom: 16px;
`
const label = document.createElement('div')
label.textContent = `direction: ${direction.toUpperCase()}`
label.style.cssText = `
font-weight: 700;
margin-bottom: 12px;
color: ${direction === 'ltr' ? '#7b2ff7' : '#d97706'};
`
container.appendChild(label)
// Элемент с ФИЗИЧЕСКИМ свойством
const physical = document.createElement('div')
physical.textContent = direction === 'ltr' ? '→ margin-left: 32px (физическое)' : '← margin-left: 32px (физическое — не адаптируется!)'
physical.style.cssText = `
margin-left: 32px;
background: #fef3c7;
padding: 8px;
border-radius: 4px;
margin-bottom: 8px;
font-size: 14px;
`
container.appendChild(physical)
// Элемент с ЛОГИЧЕСКИМ свойством
const logical = document.createElement('div')
logical.textContent = direction === 'ltr'
? '→ margin-inline-start: 32px (логическое — LTR = left)'
: '→ margin-inline-start: 32px (логическое — RTL = right!)'
logical.style.cssText = `
margin-inline-start: 32px;
background: #d1fae5;
padding: 8px;
border-radius: 4px;
font-size: 14px;
`
container.appendChild(logical)
return container
}
document.body.appendChild(createDemo('ltr'))
document.body.appendChild(createDemo('rtl'))
// Маппинг логических → физические
function getPhysicalProperty(logicalProp, direction = 'ltr', writingMode = 'horizontal-tb') {
const map = {
ltr: {
'margin-inline-start': 'margin-left',
'margin-inline-end': 'margin-right',
'margin-block-start': 'margin-top',
'margin-block-end': 'margin-bottom',
'padding-inline-start': 'padding-left',
'padding-inline-end': 'padding-right',
'inset-inline-start': 'left',
'inset-inline-end': 'right',
'inline-size': 'width',
'block-size': 'height',
'border-inline-start': 'border-left',
},
rtl: {
'margin-inline-start': 'margin-right',
'margin-inline-end': 'margin-left',
'padding-inline-start': 'padding-right',
'padding-inline-end': 'padding-left',
'inset-inline-start': 'right',
'inset-inline-end': 'left',
'border-inline-start': 'border-right',
},
}
const dirMap = direction === 'rtl' ? { ...map.ltr, ...map.rtl } : map.ltr
return dirMap[logicalProp] || logicalProp
}
console.log('=== LTR маппинг ===')
;['margin-inline-start', 'padding-inline-end', 'inline-size', 'inset-inline-start'].forEach(prop => {
console.log(`${prop} → ${getPhysicalProperty(prop, 'ltr')}`)
})
console.log('\n=== RTL маппинг ===')
;['margin-inline-start', 'padding-inline-end', 'inset-inline-start'].forEach(prop => {
console.log(`${prop} → ${getPhysicalProperty(prop, 'rtl')}`)
})Арабский текст читается справа налево. Японский — сверху вниз. Если написать margin-left: 16px, на RTL-странице этот отступ будет не с «начала» текста, а с «конца». Логические свойства решают это: они описывают расположение относительно направления текста, а не физических сторон.
| Физическое | Логическое | Значение |
|---------------------|-----------------------------|-----------------------------|
| margin-left | margin-inline-start | Начало строки |
| margin-right | margin-inline-end | Конец строки |
| margin-top | margin-block-start | Начало блока (верх) |
| margin-bottom | margin-block-end | Конец блока (низ) |
| padding-left | padding-inline-start | — |
| width | inline-size | Размер в направлении строки |
| height | block-size | Размер в направлении блока |
| top | inset-block-start | — |
| left | inset-inline-start | — |
/* LTR (по умолчанию): inline-start = left, block-start = top */
/* RTL: inline-start = right, block-start = top */
/* Вертикальный японский: inline-start = top, block-start = right */.article {
/* Вместо margin-left и margin-right */
margin-inline: auto; /* margin-inline-start + margin-inline-end */
margin-inline-start: 24px; /* margin-left в LTR, margin-right в RTL */
/* Вместо padding-top и padding-bottom */
padding-block: 32px; /* padding-block-start + padding-block-end */
padding-inline: 16px;
/* Вместо width */
inline-size: 600px; /* Ширина в LTR, высота при вертикальном тексте */
max-inline-size: 100%;
min-block-size: 400px; /* min-height в LTR */
}
/* Позиционирование */
.tooltip {
inset-inline-start: 0; /* left: 0 в LTR, right: 0 в RTL */
inset-block-start: 100%; /* top: 100% */
inset: 0; /* Все четыре стороны: top right bottom left */
}
/* Границы */
.card {
border-inline-start: 4px solid #7b2ff7; /* border-left в LTR */
border-block-end: 1px solid #e2e8f0; /* border-bottom */
border-start-start-radius: 8px; /* border-top-left-radius в LTR */
}/* Направление текста */
html[dir="rtl"] { direction: rtl; } /* Арабский, иврит */
html[lang="ar"] { direction: rtl; }
/* Режим письма */
.vertical-text {
writing-mode: vertical-rl; /* Вертикальный, справа налево (японский) */
writing-mode: vertical-lr; /* Вертикальный, слева направо */
}/* Плохо — сломается в RTL */
.breadcrumb-item + .breadcrumb-item::before {
margin-left: 8px;
margin-right: 8px;
}
/* Хорошо — работает в любом направлении */
.breadcrumb-item + .breadcrumb-item::before {
margin-inline: 8px;
}
/* Плохо — иконка всегда слева */
.button-with-icon {
padding-left: 40px;
}
/* Хорошо — иконка с «начала» текста */
.button-with-icon {
padding-inline-start: 40px;
}Все современные браузеры поддерживают логические свойства. Для старых браузеров используй fallback:
.element {
margin-left: 16px; /* Fallback */
margin-inline-start: 16px; /* Логическое (переопределит если поддерживается) */
}Демонстрация разницы физических и логических свойств в LTR и RTL окружении
// Демонстрация логических vs физических свойств в LTR и RTL
function createDemo(direction) {
const container = document.createElement('div')
container.dir = direction
container.style.cssText = `
border: 2px solid ${direction === 'ltr' ? '#7b2ff7' : '#f59e0b'};
padding: 16px;
border-radius: 8px;
font-family: sans-serif;
margin-bottom: 16px;
`
const label = document.createElement('div')
label.textContent = `direction: ${direction.toUpperCase()}`
label.style.cssText = `
font-weight: 700;
margin-bottom: 12px;
color: ${direction === 'ltr' ? '#7b2ff7' : '#d97706'};
`
container.appendChild(label)
// Элемент с ФИЗИЧЕСКИМ свойством
const physical = document.createElement('div')
physical.textContent = direction === 'ltr' ? '→ margin-left: 32px (физическое)' : '← margin-left: 32px (физическое — не адаптируется!)'
physical.style.cssText = `
margin-left: 32px;
background: #fef3c7;
padding: 8px;
border-radius: 4px;
margin-bottom: 8px;
font-size: 14px;
`
container.appendChild(physical)
// Элемент с ЛОГИЧЕСКИМ свойством
const logical = document.createElement('div')
logical.textContent = direction === 'ltr'
? '→ margin-inline-start: 32px (логическое — LTR = left)'
: '→ margin-inline-start: 32px (логическое — RTL = right!)'
logical.style.cssText = `
margin-inline-start: 32px;
background: #d1fae5;
padding: 8px;
border-radius: 4px;
font-size: 14px;
`
container.appendChild(logical)
return container
}
document.body.appendChild(createDemo('ltr'))
document.body.appendChild(createDemo('rtl'))
// Маппинг логических → физические
function getPhysicalProperty(logicalProp, direction = 'ltr', writingMode = 'horizontal-tb') {
const map = {
ltr: {
'margin-inline-start': 'margin-left',
'margin-inline-end': 'margin-right',
'margin-block-start': 'margin-top',
'margin-block-end': 'margin-bottom',
'padding-inline-start': 'padding-left',
'padding-inline-end': 'padding-right',
'inset-inline-start': 'left',
'inset-inline-end': 'right',
'inline-size': 'width',
'block-size': 'height',
'border-inline-start': 'border-left',
},
rtl: {
'margin-inline-start': 'margin-right',
'margin-inline-end': 'margin-left',
'padding-inline-start': 'padding-right',
'padding-inline-end': 'padding-left',
'inset-inline-start': 'right',
'inset-inline-end': 'left',
'border-inline-start': 'border-right',
},
}
const dirMap = direction === 'rtl' ? { ...map.ltr, ...map.rtl } : map.ltr
return dirMap[logicalProp] || logicalProp
}
console.log('=== LTR маппинг ===')
;['margin-inline-start', 'padding-inline-end', 'inline-size', 'inset-inline-start'].forEach(prop => {
console.log(`${prop} → ${getPhysicalProperty(prop, 'ltr')}`)
})
console.log('\n=== RTL маппинг ===')
;['margin-inline-start', 'padding-inline-end', 'inset-inline-start'].forEach(prop => {
console.log(`${prop} → ${getPhysicalProperty(prop, 'rtl')}`)
})Создай компонент навигационной хлебной крошки, который корректно работает в LTR и RTL режимах. Используй логические свойства: `margin-inline-start`, `padding-inline`, `border-inline-start`. Переключай направление текста через `dir` атрибут на `<html>` и кнопку.
`margin-inline: 6px` — горизонтальные отступы (заменяет `margin-left` + `margin-right`). `border-inline-start: 4px solid #7b2ff7` — граница с "начала" строки (слева в LTR, справа в RTL). `padding-inline-start: 16px`. `padding-block: 12px`. Нажми кнопку и посмотри как граница переходит на другую сторону.