Building Bilingual RTL Web Applications
2 min readi18nrtlcss
Shipping a website in English and a right-to-left language like Persian is not
a matter of flipping a dir attribute at the last minute. It is a series of
small, deliberate decisions that compound across the entire codebase.
The core principle: logical properties
The single most important rule is to never write a physical direction
property. Instead of margin-left, write margin-inline-start. The browser
resolves inline-start to the left in an LTR context and to the right in an
RTL context, so one stylesheet serves both languages.
A concrete example
The same rule positions a card correctly whether the surrounding page is English or Persian:
.box {
margin-inline-start: 1rem;
padding-inline: 1.5rem;
border-inline-start: 1px solid var(--border);
}
Physical properties would require a parallel RTL override for every component. Logical properties remove the entire class of mirror-image bugs.
Content lives on the filesystem
A bilingual post is a folder, not a database row:
content/blog/slug/en.mdxis the English translationcontent/blog/slug/fa.mdxis the Persian translation- the absence of
fa.mdxmeans "no Persian version exists"
This convention keeps the filesystem as the single source of truth. Routing, the sitemap, and the language toggle all ask one function which languages actually exist — never a frontmatter field.
Alternate links must resolve
An hreflang pointing at a URL that returns 404 is worse than no hreflang
at all. The type-safe content pipeline
explains how validation keeps that from happening.
Direction is a property of content, not of the design system. Get it right at the source and every consumer inherits it.
For a deeper look at the tooling, see the Shiki documentation and the MDN guide to logical properties.