The same product - a technical book in a store - opens in Arabic. The catalog, the card, the price, the buy button all have to feel natural to a reader whose eye travels right to left. The naive fix suggests itself: take the finished layout and flip it whole - apply transform: scaleX(-1) to the page or just swap left and right in the styles. It feels as if RTL is a mirror and one reflection is enough.
In reality direction is a property of text, not cosmetics over the layout. The order of characters is governed by the bidi algorithm (the Unicode Bidirectional Algorithm): the browser itself decides which way each fragment of a string flows, based on the category of its characters. Arabic letters run right to left, Latin letters and digits left to right, and both flows coexist inside one line at once. A mirror filter knows none of this: it flips pixels, not meaning, and breaks what must never be flipped.
You start at the root of the document. The lang attribute states the language of the content, dir states the base direction: dir=rtl turns around the flow of the interface, the text alignment, the order of columns, and the logic of where a line begins. This is not a styling hack but a signal to the platform: the browser, the screen reader, and the bidi algorithm all take their cue from it. One correct dir on html does more than a hundred spot fixes in CSS.
Next come logical properties - the CSS properties that describe padding and borders not through physical sides (left, right, top, bottom) but through the flow of text. The inline axis runs along the line, the block axis across it, from line to line. margin-inline sets the margins at the edges of the line, padding-inline the inner padding, border-inline-start the border on the start side, text-align: start aligns to the start. In LTR the start is on the left, in RTL on the right, and one rule works both ways with no duplicates and no separate Arabic stylesheet.
Mixed strings are a separate concern. An order number ABC-123 or a price in Latin digits inside an Arabic sentence is exactly the kind of opposing flow the bidi algorithm sometimes assembles in an unexpected order: brackets, hyphens, and signs stick to the wrong fragment. The bdi element (bidirectional isolate) isolates such a piece so its direction does not leak outward and drag the neighboring text along. The article number stays readable and the sentence around it stays intact.
Icons call for judgment, not blanket reflection. Directional symbols - back and forward arrows, the reply glyph, a progress indicator - should point the other way in RTL, and here mirroring through transform: scaleX() is appropriate. But universal marks must not be mirrored: the play triangle means the passage of time, not a side, while the check mark, the brand logo, and a clock face are the same across cultures. Flip them along with the layout and you get a reversed logo and a check mark facing the wrong way.
The amount of text has to be localizable too. Translating one label changes its length: a German string is easily a third longer than the original, an Arabic one shorter but taller because of its diacritics. A button with a hard-coded width clips the translation, and a label baked into an image cannot be translated or read by a screen reader at all. So sizes are driven by content, line length is kept within a comfortable reading range, and text stays text, not pixels.
A telling failure looks like this. The book card was built with border-left: 4px and margin-left: auto, and the Arabic version was produced with scaleX(-1) on the whole container. The border stayed on the left though the flow wants it on the right; the logo and the Buy check mark were reflected and read as an error; the order number fell apart without bdi. Replacing physical properties with logical ones, setting dir on the root, and mirroring only directional icons remove all four defects at once - the interface is now described in the language of flow, not of sides.
.card {
margin-inline: auto;
padding-inline: 1rem;
border-inline-start: 4px solid currentColor;
text-align: start;
}
.icon-next {
transform: scaleX(var(--direction, 1));
}<html lang="ar" dir="rtl">
<p>رقم الطلب: <bdi>ABC-123</bdi></p>