Text writing direction support in CSS
The World Wide Web is, by nature, supporting internationalization.
But many contents are centered around western-like languages, often English only. For broader internationalization, you need to handle a wider range of languages, including those that are written from right to left or even in other directions.
This article will show how simple CSS good practice provides, out-of-box, a diverse linguistic support.
Let content express itself
Content leads the way!
Your browser shall be able to detect if a specific text is using a left-to-right, or a right-to-left, language. But sometimes, it becomes easier and less error-prone to give to just give the information.
Here comes the dir
universal attribute!
As the universal qualifier expresses it, this attribute can be applied to any element, anywhere in your HTML document.
You can specify it at the root of your document on the body
element, or on a span
wrapping a single word.
This attribute accepts three different values:
auto
, the default behavior, to let browser guess the best text direction, based on internal heuristics;ltr
, stands for left to right indicates that text is written from left… to right;rtl
, standing for right to left_ on the opposite match languages written from the right to the left, like arabic.
Line or block axes
Layout system on the web is built on top of long history of printing and the way to create a build a printed page by assembling rows of glyphs in blocks of text.
Apart few special use cases like floated elements, most elements of a page are positioned following one of those two models:
- as a block;
- as inline.
Block elements, as shown in previous diagram, are stacked vertically in their containers from the top to the bottom. Once the bottom of the container is reached, blocks shall wrap to the following container, for example the next column in a multi-column layout.
The diagram above shows inline elements stacking horizontally, from left to right. They are wrapping to next line when there’s no more space available in the row.
In a language written from right-to left, inline elements will still stack horizontally, but in the opposite way, as shown in the following diagram.
For now, and for the sake of simplicity, I will only stick to the horizontal flow. Remember that all of this also applies to a vertical layout.
Text direction impact on layout
We’re gonna check how a simple row of cards layout can be broken when text direction changes.
How margin breaks when direction changes
Let start with a simple example:
- you have a bunch of cards that are laid on the screen as inline blocks;
- each card must be aligned on their left to the edge of the content area;
- each card has a right margin to have spacing between elements.
.card-item {
margin-right: 2rem;
}
Looks good? Let’s try to switch to a RTL writing direction.
This is not so nice anymore, isn’t it?
Alignment on the row start is now lost because card spacing is specified as a right margin.
But left and right did not move, only the text direction did.
Let’s bring selectors to the rescue!
You will tell me that there’s no big deal. Don’t we have the ’:dir()’ selector to handle this kind of scenario?
The solution is simple. Just throw one extra CSS rule to handle this use case:
.card-item {
margin-right: 2rem;
}
.card-item:dir(rtl) {
/* For RTL language, just invert left and right margins! */
margin-left: 2rem;
margin-right: 0;
}
It’s fixed! Mission accomplished. Really?
You know you can do better, no? Let’s see how!
Use a semantic layout system
Stop depending on language-direction
How can you use blocks and inline elements to create a layout that will work in any language orientation?
The solution is not to rely on left and right side of the row, but to use writing direction start and end points.
And CSS has a good support for this, and even if this is still considered as experimental, this is pretty well supported across all major browsers.
When you need to specify a horizontal margin in a text-direction neutral way, you should use the following properties:
- ’margin-inline-start’: specifies margins on the side where begins the container: left on LTR, right on RTL.
- ’margin-inline-end’ specifies margins on the side where the container stops: right on LTR, left on RTL.
- ’margin-inline’ shortcut to specify both start and end margins. Accepts one or two values.
So, we can now simplify our first example by using this property.
.card-item {
/* No left/right, just respect text direction */
margin-inline-end: 2rem;
}
Left to right direction works as in the first example.
And same CSS rule properly applied when text flows from the right to the left.
That’s how a single CSS rule can handle margins any text direction.
We just saw in the previous example that we can replace left and right margins by something more generic: start and end margins.
Contrary to explicit left and right ones, those will use the text writing direction to position themselves.
Inline Layout
Margins are only the surrounding space of an element. And you probably guessed that CSS give the same kind of properties for all inline layout constituents:
- margins
- borders
- paddings
Block layout
What CSS has done for inline elements and horizontal properties is also available for block elements and vertical properties.
Here is a vertical variant for our cards:
- each card is now a block element with a maximum width and centered horizontally;
- first card is aligned on top of the container;
- each card is separated from others with a vertical space.
In this example the change for CSS rules is easy.
/* In this container cards are stacked vertically */
.card-column > .card-item {
display: block;
max-width: 12rem;
margin-inline: auto;
margin-block: 0 2rem;
}
As you can see, there’s no margin-top
or margin-bottom
in this code, but a generic margin-block
property, that sets combined values for margin-block-start
and margin-block-end
properties.
The general rule is that you can replace ’top’ with ’block-start’ and ’bottom’ with ’block-end’ in property names for margin, border, padding to use direction-neutral properties.
That’s a nice addition for name symmetry, but doesn’t seem to relate a lot to LTR or RTL text direction. And, nevertheless, this is also a means to support more powerful, portable and creative designs.
But that’s certainly a topic for another article, but, in case you’re not aware, there’s a ’writing-mode’ property that allows to specify if text is written horizontally or vertically and also the direction of the text (LTR or RTL).
What about your favorite containers?
Flex container
Flex container respect the text writing direction and content will flow accordingly.
In the following example:
- the row is now a flex container;
- item spacing is handled using
gap
property; - content is justified on the end of the flex row;
- margins are removed on the cards to let gap take the role.
.flex-row {
display: flex;
flex-direction: row;
justify-content: end;
gap: 2rem;
}
.card-item {
margin-inline: 0;
margin-block: 0;
}
Grid container
As for flex container, the grid is also respecting text writing direction.
In the example bellow:
- the put cards in a grid container with 3 columns;
- a gap is specified to add spacing between elements.
Wrap-up
If you need to support both left-to-right and right-to-left languages, you should avoid specifying left or right margins in your CSS. Rely on start and end properties and you will be able to build a layout capable to fit any language direction, both horizontally and vertically.
Don’t assume that all layout will flow from left to right. It’s quite common to change to adapt to language specificities.
You should check guidelines from major vendors:
One last example would be for a button with an icon and a text label. For left-to-right language the icon is displayed in front of the label in the direction of reading.
But in the case of a right to left text direction, the icon is still in front of the label in the reading direction.
This is done without any special CSS, only by using direction-neutral properties and relying on the natural behavior of a flex container.
In both scenarios, the HTML is the same except for the value of dir
attribute on the containing div.
<div dir="rtl">
<button class="btn" type="button">
<img class="btn__icon" src="gear-icon.svg" alt="gear"/>
<span class="btn__label">Action</span>
</button>
</div>
But using neutral CSS let the browser handle the layout according to the constraints imposed by text direction.
.btn {
display: flex;
align-items: center;
gap: .5rem;
margin-block: .5rem;
margin-inline: 1rem;
border: 2px solid black;
border-radius: .25rem;
padding-inline: 1rem;
padding-block: .25rem;
font-size: 1.2rem;
}
Neutral properties help you to build adaptive HTML and CSS. You then rely on the true declarative nature of those languages, instead of applying an imperative definition of the layout.
HTML and CSS reflect what you want, not how it shall be implemented.
There’s much more than horizontal text direction. CSS also support vertically written text with a variety of combinations. And this would be an entire different article.