CSS: How to Host Right-to-Left Styling

Published on September 30, 2010 (↻ February 5, 2024), filed under (RSS feed for all categories).

This and many other posts are also available as a pretty, well-behaved ebook: On Web Development.

This post is partially outdated.

For international projects, don’t use separate style sheets for right-to-left (RTL) styling: use natural (@dir) or artificial (@id, @class) hooks instead. The only exception are unbearable performance issues due to hundreds of RTL rules.

I recommended this earlier when sharing a few RTL tips but do only here explain in a bit more detail. A side note, the focus of the post lies on projects with a global audience whose majority reads from left to right. If you are working on a site solely and forever available in, say, Hebrew, Arabic, and Danish, you may want to base defaults on RTL and customize styling for LTR. You get the idea.

The reasons why you don’t want custom RTL style sheets are maintainability and ease of use. I don’t get tired saying that: From a maintenance standpoint you want to include—note: you can still use more—one style sheet in your documents, and that style sheet should use a reasonable name. The point is that you want to reduce the probability of updating CSS references, and that everybody on your team knows and remembers what style sheet to use.

So what’s the deal with RTL styling?

<!DOCTYPE html>
<html lang="en">
  <title>Test</title>
  <link rel="stylesheet" href="default.css">
  <p>This is only a test.

Let’s say you have a left padding on the paragraph.

p {
  padding: 0 0 0 1em;
}

So what’s the best way now to deal with your Arabic version of this page? (Machine translation to the rescue.)

<!DOCTYPE html>
<html lang="ar" dir="rtl">
  <title>ۧ۟ŰȘۚۧ۱</title>
  <link rel="stylesheet" href="default.css">
  <p>Ù‡Ű°Ű§ Ù„ÙŠŰł ŰłÙˆÙ‰ ۧ۟ŰȘۚۧ۱.

The bit of padding you’re setting on your p elements should appear on the right.

Now, the simplest solution is this:

p {
  padding: 0 0 0 1em;
}

html[dir='rtl'] p {
  padding: 0 1em 0 0;
}

The markup itself remains the same, the style sheet stays the same, no need for special documentation, no need for anyone to remember anything (except for, which kind of enforces itself, setting @dir).

Unfortunately, some older user agents may not recognize the html[dir='rtl'] selector. If that is a problem for you you’ll need an alternative hook, like

<!DOCTYPE html>
<html lang="ar" dir="rtl" id=”rtl”>
  <title>ۧ۟ŰȘۚۧ۱</title>
  <link rel=”stylesheet” href=”default.css”>
  <p>Ù‡Ű°Ű§ Ù„ÙŠŰł ŰłÙˆÙ‰ ۧ۟ŰȘۚۧ۱.

Not every flavor of HTML permits an ID on the html start tag. HTML 5 does.

In order to cater for RTL, default.css would then simply say (until @dir can be used instead)

p {
  padding: 0 0 0 1em;
}

#rtl p {
  padding: 0 1em 0 0;
}

As you will have noticed, complexity slightly increased: People working on these pages will need to remember to set the id="rtl" hook in order to achieve proper styling. The modification is still simple and easy to remember, and we’re talking about a temporary markup modification, not a permanent one as custom RTL style sheets introduce them (an unfortunate practice “frameworks” like 960 Grid System prefer).

In the case of separate RTL style sheets you will need to remember to use a different style sheet,

<!DOCTYPE html>
<html lang="ar" dir="rtl">
  <title>ۧ۟ŰȘۚۧ۱</title>
  <link rel="stylesheet" href="default.rtl.css”>
  <p>Ù‡Ű°Ű§ Ù„ÙŠŰł ŰłÙˆÙ‰ ۧ۟ŰȘۚۧ۱.

and you will also need to maintain that extra style sheet. I’ll spare you examples for that, like what you’d need to do when changing the sample padding to 2em.

In complex projects the impact of either practice is obviously bigger. You will also see practices emerge that exaggerate pain points. For example, I’ve worked on one large project in the past where an internal framework offered RTL functionality via a separate style sheet. Now that particular framework took care of basic styling and was not touched when developing custom sites, i.e. such sites included the particular framework plus an additional style sheet for the site itself. What I could observe was that some developers included the RTL version of the framework, but for additional styling they did not create an RTL version of the additional, custom style sheet, but set up RTL class names like .rtl_foo for the RTL version of .foo.

In other words,

Imagine how such RTL documents looked like, and think about how messy it became to understand what was done, how, and where.

(The recommendation applied to this last case means a framework.css and a custom.css, and custom.css to use html[dir='rtl'] .foo for RTL deviations.)

Don’t use separate style sheets for RTL styling.

Update (November 11, 2018)

A late note, the situation improves with CSS Logical Properties; these avoid directionality inherent on the CSS side and with that make it possible to write style sheets that work with both LTR and RTL without modifications. (Logical properties are not necessary in unidirectional projects and therefore most useful in bi-directional projects.)

Update (July 27, 2021)

Interesting and relevant, too, is the :dir() pseudo-class. Barely supported at the moment, it will likewise make work on bi-directional projects significantly easier.

Was this useful or interesting? Share (toot) this post, become a one-time nano-sponsor, or support my work by learning with my ebooks.

About Me

Jens Oliver Meiert, on November 9, 2024.

I’m Jens (long: Jens Oliver Meiert), and I’m a frontend engineering leader and tech author/publisher. I’ve worked as a technical lead for companies like Google and as an engineering manager for companies like Miro, I’m a contributor to several web standards, and I write and review books for O’Reilly and Frontend Dogma.

I love trying things, not only in web development (and engineering management), but also in other areas like philosophy. Here on meiert.com I share some of my experiences and views. (Please be critical, interpret charitably, and give feedback.)

Comments (Closed)

  1. On September 30, 2010, 17:08 CEST, David said:

    Brilliant as always Jens! Very well explained and points well illustrated. Would be curious to know how UX is effected by simply flip flopping the layout, etc. Do RTL users follow a mirror copy of page scanning habits as LTR users? Future post perhaps?

  2. On September 30, 2010, 17:33 CEST, Vladimir Carrer said:

    Interesting concept, thank you for sharing.

  3. On September 30, 2010, 19:03 CEST, Gabri said:

    lately i was working on a Project that includes LTR & RTL styling and i used the same exact method you mentioned here using html[dir=’rtl’] selector and it was very easy to use and to maintain.

  4. On October 1, 2010, 5:32 CEST, Ali Sattari said:

    As someone who deals with RTL on a daily basis, I would prefer to have an extra style sheet which contains only rules and properties that are affecting direction and simply override them.
    That would be usually less than 5% of whole style sheet.
    This way main style sheet is LTR, no need to use attribute selectors or extra ids or classes in markup and you get to load the extra style sheet just when it is needed.

  5. On October 1, 2010, 21:51 CEST, Jens Oliver Meiert said:

    David, thanks—the short story is, yes, you can pretty much flip the layout. However there are indeed a few more things to write about and you make me consider doing so đŸ˜Š

    Ali, I see your point and I generally understand the logic to be tempting. I think the 5% you mention are realistic—mileage may vary, up to 15% I’d say—, however that is something I see exactly as a plus when it comes to making an LTR style sheet RTL-ready. Focus on how much simpler it is to work with one style sheet, too (if you had only one before, you’d otherwise, in a way, double complexity). Not only does that one style sheet come with a few maintenance advantages but it’s also just easier to use. I guess I’m about to repeat what I wrote before.

  6. On October 5, 2010, 14:34 CEST, Hassan said:

    I use WordPress, and it automatically link the rtl.css file if it’s present. I think it’s the simplest way. Most of the time it doesn’t get larger than 20 or 30 lines.