CSS: How to Host Right-to-Left Styling
Published on Sep 30, 2010 (updated Feb 5, 2024), filed under development (feed). (Share this on Mastodon or Bluesky?)
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,
- framework.css for the LTR framework
- framework.rtl.css for the RTL equivalent
- custom.css for both LTR and RTL customizations, with
.foo
for LTR.rtl_foo
for RTL
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.
About Me
I’m Jens (long: Jens Oliver Meiert), and I’m a web developer, manager, and author. I’ve worked as a technical lead and engineering manager for small and large enterprises, I’m an occasional contributor to web standards (like HTML, CSS, WCAG), 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. (I value you being critical, interpreting charitably, and giving feedback.)