웹 서비스를 글로벌하게 운영하려면 아랍어, 히브리어처럼 RTL(오른쪽 → 왼쪽) 언어를 지원해야 합니다.
이번 글에서는 CSS Logical Properties를 활용한 RTL 대응 전략을 정리합니다.
<html lang="ar" dir="rtl">
<body>...</body>
</html>
lang="ar": 브라우저가 아랍어 폰트, 줄바꿈, 스크린리더를 올바르게 처리dir="rtl": 레이아웃 방향을 RTL로 전환dir="auto": 댓글 같은 UGC에서는 텍스트 방향을 자동 감지물리 속성(left, right) 대신 **논리 속성(inline-start, inline-end)**을 사용하면 LTR/RTL 모두 자동 대응됩니다.
/* bad */
.card { margin-left: 16px; padding-right: 12px; border-left: 4px solid #ccc; }
/* good */
.card {
margin-inline-start: 16px;
padding-inline-end: 12px;
border-inline-start: 4px solid #ccc;
}
.title { text-align: start; } /* LTR=left, RTL=right */
| 기존 속성 | 논리 속성 |
|---|---|
width | inline-size |
height | block-size |
margin-left | margin-inline-start |
padding-right | padding-inline-end |
border-left | border-inline-start |
text-align: left | text-align: start |
:dir(rtl) .nav { float: inline-start; }
[dir="rtl"] .nav { float: inline-start; }
:dir(rtl) 사용[dir="rtl"] 폴백 적용아랍어 텍스트에 숫자/영문이 섞이면 흐트러질 수 있습니다.
<p>السعر <bdi>USD 1,299</bdi> فقط</p>
.code-snippet {
direction: ltr;
unicode-bidi: isolate;
}
<bdi>: 방향성을 격리해 섞임 방지unicode-bidi: isolate: LTR 코드 블록 고정html[dir="rtl"] .icon-arrow { transform: scaleX(-1); }
transform으로 처리하거나 RTL 전용 애셋 제공justify-content: start/end → 방향에 맞게 자동 정렬gap 속성은 LTR/RTL에 무관하게 적용grid-template 네이밍은 물리적(left/right) 대신 논리적(start/end) 사용 권장lang="ar"를 지정하면 브라우저가 폰트 선택과 자간 처리에 도움lang/dir 지정margin-inline-*, text-align: start):dir(rtl) + [dir="rtl"])<bdi>, unicode-bidi)postcss-logical로 구형 브라우저 대응<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8" />
<style>
body { font-family: system-ui, "Noto Naskh Arabic", sans-serif; }
.nav { display: flex; gap: 12px; justify-content: start; }
.nav a.is-active { border-inline-start: 3px solid #6b8afd; }
.card { border-inline-start: 4px solid #6b8afd; padding: 16px; }
.price { direction: ltr; unicode-bidi: isolate; }
html[dir="rtl"] .icon-next { transform: scaleX(-1); }
</style>
</head>
<body>
<nav class="nav">
<a href="#">الرئيسية</a>
<a class="is-active" =>المنتجات
الدعم
سماعات لاسلكية
السعر USD 129
CRA/Next.js public/index.html 또는 layout.tsx에 dir="rtl" 추가
<html lang="ar" dir="rtl">
<body>
<div id="root"></div>
</body>
</html>
import React, { useEffect } from "react";
function App() {
useEffect(() => {
const lang = navigator.language; // ex: "ar", "en-US"
document.documentElement.lang = lang;
document.documentElement.dir = lang.startsWith("ar") ? "rtl" : "ltr";
}, []);
return (
<div className="app">
<h1>مرحبا بالعالم</h1>
<p>Hello World</p>
</div>
);
}
export default App;
import styled from "styled-components";
const Box = styled.div`
padding-inline-start: 20px;
border-inline-start: 3px solid tomato;
text-align: start;
`;
function Component() {
return <Box>محتوى (Content)</Box>;
}
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
export default function Layout({ children }) {
const { i18n } = useTranslation();
useEffect(() => {
document.documentElement.lang = i18n.language;
document.documentElement.dir = i18n.language === "ar" ? "rtl" : "ltr";
}, [i18n.language]);
return <>{children}</>;
}
import { createTheme } from "@mui/material/styles";
import { ThemeProvider } from "@mui/material";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import rtlPlugin from "stylis-plugin-rtl";
const cacheRtl = createCache({
key: "muirtl",
stylisPlugins: [rtlPlugin()],
});
const theme = createTheme({
direction: "rtl",
});
export default function App() {
return (
<CacheProvider value={cacheRtl}>
<ThemeProvider theme={theme}>
<div>مرحبا</div>
</ThemeProvider>
</CacheProvider>
);
}
👉 결론:
dir 속성 설정document.documentElement.dir을 동적으로 제어