Tag: MultiDirectional

Control Layout in a Multi-Directional Website

Many business websites need a multilingual setup. As with anything development-related, implementing one in an easy, efficient, and maintainable way is desirable. Designing and developing to be ready for multiple languages, whether it happens right at launch or is expected to happen at any point in the future, is smart.

Changing the language and content is the easy part. But when you do that, sometimes the language you are changing to has a different direction. For example, text (and thus layout) in English flows left-to-right while text (and thus layout) in Arabic goes right-to-left.

In this article, I want to build a multilingual landing page and share some CSS techniques that make this process easier. Hopefully the next time you’ll need to do the same thing, you’ll have some implementation techniques to draw from.

We’ll cover six major points. I believe that the first five are straightforward. The sixth includes multiple options that you need to think about first.

1. Start with the HTML markup

The lang and dir attributes will define the page’s language and direction.

<!-- English, left-to-right --> <html lang="en" dir="ltr">  <!-- Arabic, right-to-left --> <html lang="ar" dir="rtl">

Then we can use these attributes in selectors to do the the styling. lang and dir attributes are on the HTML tag or a specific element in which the language varies from the rest of the page. Those attributes help improve the website’s SEO by showing the website in the right language for users who search for it in case that each language has a separate HTML document.

Also, we need to ensure that the charset meta tag is included and its value is UTF-8 since it’s the only valid encoding for HTML documents which also supports all languages.

<meta charset="utf-8">

I’ve prepared a landing page in three different languages for demonstration purposes. It includes the HTML, CSS, and JavaScript we need.

2. CSS Custom Properties are your friend

Changing the direction may lead to inverting some properties. So, if you used the CSS property left in a left-to-right layout, you probably need right in the right-to-left layout, and so on. And changing the language may lead to changing font families, font sizes, etc.

These multiple changes may cause unclean and difficult to maintain code. Instead, we can assign the value to a custom property, then change the value when needed. This is also great for responsiveness and other things that might need a toggle, like dark mode. We can change the font-size, margin, padding, colors, etc., in the blink of an eye, where the values then cascade to wherever needed.

Here are some of the CSS custom properties that we are using in this example:

html {   /* colors */   --dark-color: #161616;   --light-color: #eee;   --primary-text-color: var(--dark-color);   --primary-bg-color: #fff;   --shadow-color: var(--light-color);   --hero-bg-gradient: linear-gradient(90deg, #30333f, #161616, #161616);    /* font sizes */   --logo-font-size: 2rem;   --lang-switcher-font-size: 1.02em;   --offers-item-after-font-size: 1.5rem;    /* margin and padding */   --btn-padding: 7px;   --sec-padding-block: 120px;    /* height and width */   --hero-height: 500px;   --cta-img-width: 45.75%; }

While styling our page, we may add/change some of these custom properties, and that is entirely natural. Although this article is about multi-directional websites, here’s a quick example that shows how we can re-assign custom property values by having one set of values on the <body>, then another set when the <body> contains a .dark class:

body {   background-color: var(--primary-bg-color);   color: var(--primary-text-color); } body.dark {   --primary-bg-color: #0f0f0f;   --primary-text-color: var(--light-color);    /* other changes */   --shadow-color: #13151a;   --hero-bg-gradient: linear-gradient(90deg, #191b20, #131313, #131313); }

That’s the general idea. We’re going to use custom properties in the same sort of way, though for changing language directions.

3) CSS pseudo-classes and selectors

CSS has a few features that help with writing directions. The following two pseudo-classes and attribute are good examples that we can put to use in this example.

The :lang() pseudo-class

We can use :lang() pseudo-class to target specific languages and apply CSS property values to them individually, or together. For example, in this example, we can change the font size when the :lang pseudo-class switches to either Arabic or Japanese:

html:lang(ar), html:lang(jp){   --offers-item-after-font-size: 1.2rem;  }

Once we do that, we also need to change the writing-mode property from its horizontal left-to-right default direction to vertical right-to-left direction account:

html:lang(jp) .about__text {   writing-mode: vertical-rl; }

The :attr() pseudo-class

The :attr() pseudo-class helps makes the “content” of the pseudo-elements like ::before or ::after “dynamic” in a sense, where we can drop the dir HTML attribute into the CSS content property using the attr() function. That way, the value of dir determines what we’re selecting and styling.

<div dir="ltr"></div> <div dir="rtl"></div>
div::after {   content: attr(dir); }

The power is the ability to use any custom data attribute. Here, we’re using a custom data-name attribute whose value is used in the CSS:

<div data-name="English content" dir="ltr"></div> <div data-name="محتوى عربي" dir="rtl"></div>
div::after {   content: attr(data-name); }

This makes it relatively easy to change the content after switching that language without changing the style. But, back to our design. The three-up grid of cards has a yellow “special” or “best” off mark beside an image.

This is the HTML for each card:

<div class="offers__item relative" data-attr="data-offer" data-i18n_attr="special_offer">   <figure class="offers__item_img">     <img src="./assets/images/offer1.png" data-attr="alt" data-i18n_attr="image_alt" alt="" class="w-100">   </figure>   <div class="offer-content_item-text">     <p class="para" data-i18n="offer_item_text"></p>     <span class="price bolder" data-i18n="offer_item_price"></span>   </div> </div>

JavaScript’s role is to:

  1. Set an attribute called data-offer on each card.
  2. Assign a “special offer” or “best offer” value to it.

Finally, we can use the data-offer attribute in our style:

.offers__item::after {   content: attr(data-offer);   /* etc. */ }

Select by the dir attribute

Many languages are left-to-right, but some are not. We can specify what should be different in the [dir='rtl']. This attribute must be on the element itself or we can use nesting to reach the wanted element. Since we’ve already added the dir attribute to our HTML tag, then we can use it in nesting. We will use it later on our sample page.

4. Prepare the web fonts

In a multilingual website, we may also want to change the font family between languages because perhaps a particular font is more legible for a particular language.

Fallback fonts

We can benefit from the fallback by writing the right-to-left font after the default one.

font-family: 'Roboto', 'Tajawal', sans-serif;

This helps in cases where the default font doesn’t support right-to-left. That snippet above is using the Roboto font, which doesn’t support Arabic letters. If the default font supports right-to-left (like the Cairo font), and the design needs it to be changed, then this is not a perfect solution.

font-family: 'Cairo', 'Tajawal', sans-serif; /* won't work as expected */

Let’s look at another way.

Using CSS variables and the :lang() pseudo-class

We can mix the previous two technique where we change the font-family property value using custom properties that are re-assigned by the :lang pseudo class.

html {   --font-family: 'Roboto', sans-serif; }  html:lang(ar){   --font-family: 'Tajawal', sans-serif; }  html:lang(jp){   --font-family: 'Noto Sans JP', sans-serif; }

5. CSS Logical Properties

In CSS days past, we used to use left and right to define offsets along the x-axis, and the top and bottom properties to to define offsets along the y-axis. That makes direction switching a headache. Fortunately, CSS supports logical properties that define direction‐relative equivalents of the older physical properties. They support things like positioning, alignment, margin, padding, border, etc.

If the writing mode is horizontal (like English), then the logical inline direction is along the x-axis and the block direction refers to the y-axis. Those directions are flipped in a vertical writing mode, where inline travels the y-axis and and block flows along the x-axis.

Writing Mode x-axis y-axis
horizontal inline block
vertical  block inline

In other words, the block dimension is the direction perpendicular to the writing mode and the inline dimension is the direction parallel to the writing mode. Both inline and block levels have start and end values to define a specific direction. For example, we can use margin-inline-start instead of margin-left. This mean the margin direction automatically inverts when the page direction is rtl. It’s like our CSS is direction-aware and adapts when changing contexts.

There is another article on CSS-Tricks, Building Multi-Directional Layouts from Ahmad El-Alfy, that goes into the usefulness of building websites in multiple languages using logical properties.

This is exactly how we can handle margins, padding and borders. We’ll use them in the footer section to change which border gets the rounded edge.

The top-tight edge of the border is rounded in a default ltr writing mode.

As long as we’re using the logical equivalent of border-top-right-radius, CSS will handle that change for us.

.footer {   border-start-end-radius: 120px; }

Now, after switching to the rtl direction, it’ll work fine.

The “call to action” section is another great place to apply this:

.cta__text {   border-start-start-radius: 50%;   border-end-start-radius: 50px; } .cta__img {   border: 1px dashed var(--secondary-color);   border-inline-start-color: var(--light-color); }

Now, in Arabic, we get the correct layout:

You might be wondering exactly how the block and inline dimensions reverse when the writing mode changes. Back to the Japanese version, the text is from vertical, going from top-to-bottom. I added this line to the code:

/* The "About" section when langauge is Japanese */ html:lang(jp) .about__text {   margin-block-end: auto;   width: max-content; }

Although I added margin to the “block” level, it is applied it to the left margin. That’s because the text rotated 90 degrees when the language switched and flows in a vertical direction.

6. Other layout considerations

Even after all this prep, sometimes where elements move to when the direction and language change is way off. There are multiple factors at play, so let’s take a look.

Position

Using an absolute or fixed position to move elements may affect how elements shift when changing directions. Some designs need it. But I’d still recommend asking yourself: do I really need this?

Fro example, the newsletter subscription form in the footer section of our example can be implemented using position. The form itself takes the relative position, while the button takes the absolute position.

<form id="newsletter-form" class="relative">   <input type="email" data-attr="placeholder" data-i18n_attr="footer_input_placeholder" class="w-100">   <button class="btn btn--tertiary footer__newsletter_btn bolder absolute" data-i18n="footer_newsLetter_btn"></button> </form>
html[dir="ltr"] .footer__newsletter_btn {   right: 0; } html[dir="rtl"] .footer__newsletter_btn {   left: 0; }
This works fine in a rtl writing mode.

In the “hero” section, I made the background using a ::before pseudo-class with an absolute position:

<header class="hero relative">   <!-- etc. --> </header>
.hero {   background-image: linear-gradient(90deg, #30333f, #161616, #161616); } .hero::before  {   content: '';   display: block;   height: 100%;   width: 33.33%;   background-color: var(--primary-color);   clip-path: polygon(20% 0%, 100% 0, 100% 100%, 0% 100%);   position: absolute;   top: 0;   right: 0; }

Here’s the HTML for our hero element:

<header class="hero relative">   <!-- etc. -->   <div class="hero__social absolute">     <div class="d-flex flex-col">       <!-- etc. -->     </div>   </div> </header>

Note that an .absolute class is in there that applies position: absolute to the hero section’s social widget. Meanwhile, the hero itself is relatively positioned.

How we move the social widget halfway down the y-axis:

.hero__social {   left: 0;   top: 50%;   transform: translateY(-50%); }

In the Arabic, we can fix the ::before pseudo-class position that is used in the background using the same technique we use in the footer form. That said, there are multiple issues we need to fix here:

  1. The clip-path direction
  2. The background linear-gradient
  3. The coffee-cup image direction
  4. The social media box’s position

Let’s use a simple flip trick instead. First, we wrap the hero content, and social content in two distinct wrapper elements instead of one:

<header class="hero relative">   <div class="hero__content">       <!-- etc. -->   </div>   <div class="hero__social absolute">     <div class="d-flex flex-col">       <!-- etc. -->     </div>   </div> </header>

Then we rotate both of the hero wrappers—the social box inner wrapper and the image—180 degrees:

html[dir="rtl"] .hero, html[dir="rtl"] .hero__content, html[dir="rtl"] .hero__img img, html[dir="rtl"] .hero__social > div {   transform: rotateY(180deg); }

Yeah, that’s all. This simple trick is also helpful if the hero’s background is an image.

transform: translate()

This CSS property and value function helps move the element on one or more axes. The difference between ltr and rtl is that the x-axis is the inverse/negative value of the current value. So, we can store the value in a variable and change it according to the language.

html {   --about-img-background-move: -20%; }  html[dir='rtl']{   --about-img-background-move: 20%; }

We can do the same thing for the background image in the another section:

<figure class="about__img relative">   <img src="image.jpg" data-attr="alt" data-i18n_attr="image_alt" class="w-100"> </figure>
.about__img::after {   content: '';   position: absolute;   z-index: -1;   transform: translateY(-75%) translateX(var(--about-img-background-move));   /* etc. */ }

Margins

Margins are used to extend or reduce spaces between elements. It accepts negative values, too. For example, a positive margin-top value (20%) pushes the content down, while a negative value (e.g. -20%) pulls the content up.

If margins values are negative, then the top and left margins move the item up or to the left. However, the right and bottom margins do not. Instead, they pull content that is located in the right of the item towards the left, and the content underneath the item up. For example, if we apply a negative top margin and negative bottom margin together on the same item, the item is moved up and pull the content below it up into the item.

Here’s an example:

<section>   <div id="d1"></div>   <div id="d2"></div>   <div id="d3"></div> </section>
div {   width: 100px;   height: 100px;   border: 2px solid; } #d1 {   background-color: yellow;   border-color: red; } #d2 {   background-color: lightblue;   border-color: blue; } #d3 {   background-color: green;   border-color: red; }

The result of the above code should be something like this:

Let’s add these negative margins to the #d2 element:

#d2 {   margin-top: -40px;   margin-bottom: -70px; }

Notice how the second box in the diagram moves up, thanks to a negative margin-top value, and the green box also moves up an overlaps the second box, thanks to a negative margin-bottom value.

The next thing you might be asking: But what is the difference between transform: translate and the margins?

When moving an element with a negative margin, the initial space that is taken by the element is no longer there. But in the case of translating the element using a transform, the opposite is true. In other words, a negative margin leads pulls the element up, but the transform merely changes its position, without losing the space reserved for it.

Let’s stick to using margin in one direction:

#d2 {   margin-top: -40px;   /* margin-bottom: -70px; */ }

Now let’s replace the margin with the transform:

#d2 {   /* margin-top: -40px;*/   transform: translateY(-40px); }

You can see that, although the element is pulled up, its initial space is still there according to the natural document flow.

Flexbox

The display: flex provides a quick way to control the how the elements are aligned in their container. We can use align-items and justify-content to align child elements at the parent level.

In our example, we can use flexbox in almost every section to make the column layout. Plus, we can use it in the “offers” section to center the set of those yellow “special” and “best” marks:

.offers__item::after {   content: attr(data-offer);   display: flex;   align-items: center;   justify-content: center;   text-align: center; }

The same can be applied to the hero section to center text vertically.

<div class="d-lg-flex align-items-center">   <div class="hero__text d-xl-flex align-items-center">     <div>       <!-- text -->     </div>   </div>   <figure class="hero__img relative">     <img src="/image.jpg" data-attr="alt" data-i18n_attr="image_alt" class="w-100">   </figure> </div>

If the flex-direction value is row, then we can benefit from controlling the width for each element. In the “hero” section, we need to set the image on the angled slope of the background where the color transitions from dark gray to yellow.

.hero__text {   width: 56.5%; } .hero__img {   width: 33.33%; }

Both elements take up a a total of 89.83% of the parent container’s width. Since we didn’t specify justify-content on the parent, it defaults to start, leaving the remaining width at the end.

We can combine the flexbox with any of the previous techniques we’ve seen, like transforms and margins. This can help us to reduce how many position instances are in our code. Let’s use it with a negative margin in the “call to action” section to locate the image.

<section class="cta d-xl-flex align-items-center">   <div class="cta__text w-100">     <!-- etc. -->   </div>   <figure class="cta__img">     <img src="image.jpg" data-attr="alt" data-i18n_attr="image_alt" class="w-100">   </figure> </section>

Because we didn’t specify the flex-wrap and flex-basis properties, the image and the text both fit in the parent. However, since we used a negative margin, the image is pulled to the left, along with its width. This saves extra space for the text. We also want to use a logical property, inline-start, instead of left to handle switching to the rtl direction.

Grid

Finally, we can use a grid container to positing the elements. CSS Grid is powerful (and different than flexbox) in that it lays things along both the x-axis and the y-axis as opposed to only one of them.

Suppose that in the “offers” section, the role of the “see all” button is to get extra data that to display on the page. Here’s JavaScript code to repeat the current content:

// offers section ==> "see all" btn functionality (function(){   document.querySelector('.offers .btn').addEventListener('click', function(){     const offersContent = document.querySelector('.offers__content');     offersContent.innerHTML += offersContent.innerHTML;     offersContent.classList.remove('offers__content--has-margin');     this.remove();   }) })();

Then, let’s use display: grid in the CSS for this section. First, here’s our HTML, with our grid container highlighted.

<div class="offers__content offers__content--has-margin d-grid">   <div class="offers__item relative" data-attr="data-offer" data-i18n_attr="special_offer">     <!-- etc. -->   </div>   <div class="offers__item relative" data-attr="data-offer" data-i18n_attr="best_offer">     <!-- etc. -->   </div>   <div class="offers__item relative" data-attr="data-offer" data-i18n_attr="best_offer">     <!-- etc. -->   </div> </div>

We implement CSS Grid on the .offers__content element:

html {   /* custom properties */   --offers-content-column: repeat(3, 1fr);   --offers-content-gap: 5vw; }  .offers__content {   display: grid;   grid-template-columns: var(--offers-content-column);   gap: var(--offers-content-gap); } .offers__content--has-margin {   margin-block-end: 60px; }

This is the result after clicking the button:

Our page is far from being the best example of how CSS Grid works. While I was browsing the designs online, I found a design that uses the following structure:

Notice how CSS Grid makes the responsive layout without media queries. And as you might expect, it works well for changing writing modes where we adjust where elements go on the grid based on the current writing mode.

Wrapping up

Here is the final version of the page. I ensured to implement the responsiveness with a mobile-first approach to show you the power of the CSS variables. Be sure to open the demo in full page mode as well.

I hope these techniques help make creating multilingual designs easier for you. We looked at a bunch of CSS properties we can use to apply styles to specific languages. And we looked at different approaches to do that, like selecting the :lang pseudo-class and data attributes using the attr() function. As part of this, we covered what logical properties are in CSS and how they adapt to a document’s writing mode—which is so much nicer than having to write additional CSS rulesets to swap out physical property units that otherwise are unaffected by the writing mode.

We also checked out a number of different positioning and layout techniques, looking specifically at how different techniques are more responsive and maintainable than others. For example, CSS Grid and Flexbox are equipped with features that can re-align elements inside of a container based on changing conditions.

Clearly, there are lots of moving pieces when working with a multilingual site. There are probably other requirements you need to consider when optimizing a site for specific languages, but the stuff we covered here together should give you all of the layout-bending superpowers you need to create robust layouts that accommodate any number of languages and writing modes.


The post Control Layout in a Multi-Directional Website appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,

Control Layout in a Multi-Directional Website

Many business websites need a multilingual setup. As with anything development-related, implementing one in an easy, efficient, and maintainable way is desirable. Designing and developing to be ready for multiple languages, whether it happens right at launch or is expected to happen at any point in the future, is smart.

Changing the language and content is the easy part. But when you do that, sometimes the language you are changing to has a different direction. For example, text (and thus layout) in English flows left-to-right while text (and thus layout) in Arabic goes right-to-left.

In this article, I want to build a multilingual landing page and share some CSS techniques that make this process easier. Hopefully the next time you’ll need to do the same thing, you’ll have some implementation techniques to draw from.

We’ll cover six major points. I believe that the first five are straightforward. The sixth includes multiple options that you need to think about first.

1. Start with the HTML markup

The lang and dir attributes will define the page’s language and direction.

<!-- English, left-to-right --> <html lang="en" dir="ltr">  <!-- Arabic, right-to-left --> <html lang="ar" dir="rtl">

Then we can use these attributes in selectors to do the the styling. lang and dir attributes are on the HTML tag or a specific element in which the language varies from the rest of the page. Those attributes help improve the website’s SEO by showing the website in the right language for users who search for it in case that each language has a separate HTML document.

Also, we need to ensure that the charset meta tag is included and its value is UTF-8 since it’s the only valid encoding for HTML documents which also supports all languages.

<meta charset="utf-8">

I’ve prepared a landing page in three different languages for demonstration purposes. It includes the HTML, CSS, and JavaScript we need.

2. CSS Custom Properties are your friend

Changing the direction may lead to inverting some properties. So, if you used the CSS property left in a left-to-right layout, you probably need right in the right-to-left layout, and so on. And changing the language may lead to changing font families, font sizes, etc.

These multiple changes may cause unclean and difficult to maintain code. Instead, we can assign the value to a custom property, then change the value when needed. This is also great for responsiveness and other things that might need a toggle, like dark mode. We can change the font-size, margin, padding, colors, etc., in the blink of an eye, where the values then cascade to wherever needed.

Here are some of the CSS custom properties that we are using in this example:

html {   /* colors */   --dark-color: #161616;   --light-color: #eee;   --primary-text-color: var(--dark-color);   --primary-bg-color: #fff;   --shadow-color: var(--light-color);   --hero-bg-gradient: linear-gradient(90deg, #30333f, #161616, #161616);    /* font sizes */   --logo-font-size: 2rem;   --lang-switcher-font-size: 1.02em;   --offers-item-after-font-size: 1.5rem;    /* margin and padding */   --btn-padding: 7px;   --sec-padding-block: 120px;    /* height and width */   --hero-height: 500px;   --cta-img-width: 45.75%; }

While styling our page, we may add/change some of these custom properties, and that is entirely natural. Although this article is about multi-directional websites, here’s a quick example that shows how we can re-assign custom property values by having one set of values on the <body>, then another set when the <body> contains a .dark class:

body {   background-color: var(--primary-bg-color);   color: var(--primary-text-color); } body.dark {   --primary-bg-color: #0f0f0f;   --primary-text-color: var(--light-color);    /* other changes */   --shadow-color: #13151a;   --hero-bg-gradient: linear-gradient(90deg, #191b20, #131313, #131313); }

That’s the general idea. We’re going to use custom properties in the same sort of way, though for changing language directions.

3) CSS pseudo-classes and selectors

CSS has a few features that help with writing directions. The following two pseudo-classes and attribute are good examples that we can put to use in this example.

The :lang() pseudo-class

We can use :lang() pseudo-class to target specific languages and apply CSS property values to them individually, or together. For example, in this example, we can change the font size when the :lang pseudo-class switches to either Arabic or Japanese:

html:lang(ar), html:lang(jp){   --offers-item-after-font-size: 1.2rem;  }

Once we do that, we also need to change the writing-mode property from its horizontal left-to-right default direction to vertical right-to-left direction account:

html:lang(jp) .about__text {   writing-mode: vertical-rl; }

The :attr() pseudo-class

The :attr() pseudo-class helps makes the “content” of the pseudo-elements like ::before or ::after “dynamic” in a sense, where we can drop the dir HTML attribute into the CSS content property using the attr() function. That way, the value of dir determines what we’re selecting and styling.

<div dir="ltr"></div> <div dir="rtl"></div>
div::after {   content: attr(dir); }

The power is the ability to use any custom data attribute. Here, we’re using a custom data-name attribute whose value is used in the CSS:

<div data-name="English content" dir="ltr"></div> <div data-name="محتوى عربي" dir="rtl"></div>
div::after {   content: attr(data-name); }

This makes it relatively easy to change the content after switching that language without changing the style. But, back to our design. The three-up grid of cards has a yellow “special” or “best” off mark beside an image.

This is the HTML for each card:

<div class="offers__item relative" data-attr="data-offer" data-i18n_attr="special_offer">   <figure class="offers__item_img">     <img src="./assets/images/offer1.png" data-attr="alt" data-i18n_attr="image_alt" alt="" class="w-100">   </figure>   <div class="offer-content_item-text">     <p class="para" data-i18n="offer_item_text"></p>     <span class="price bolder" data-i18n="offer_item_price"></span>   </div> </div>

JavaScript’s role is to:

  1. Set an attribute called data-offer on each card.
  2. Assign a “special offer” or “best offer” value to it.

Finally, we can use the data-offer attribute in our style:

.offers__item::after {   content: attr(data-offer);   /* etc. */ }

Select by the dir attribute

Many languages are left-to-right, but some are not. We can specify what should be different in the [dir='rtl']. This attribute must be on the element itself or we can use nesting to reach the wanted element. Since we’ve already added the dir attribute to our HTML tag, then we can use it in nesting. We will use it later on our sample page.

4. Prepare the web fonts

In a multilingual website, we may also want to change the font family between languages because perhaps a particular font is more legible for a particular language.

Fallback fonts

We can benefit from the fallback by writing the right-to-left font after the default one.

font-family: 'Roboto', 'Tajawal', sans-serif;

This helps in cases where the default font doesn’t support right-to-left. That snippet above is using the Roboto font, which doesn’t support Arabic letters. If the default font supports right-to-left (like the Cairo font), and the design needs it to be changed, then this is not a perfect solution.

font-family: 'Cairo', 'Tajawal', sans-serif; /* won't work as expected */

Let’s look at another way.

Using CSS variables and the :lang() pseudo-class

We can mix the previous two technique where we change the font-family property value using custom properties that are re-assigned by the :lang pseudo class.

html {   --font-family: 'Roboto', sans-serif; }  html:lang(ar){   --font-family: 'Tajawal', sans-serif; }  html:lang(jp){   --font-family: 'Noto Sans JP', sans-serif; }

5. CSS Logical Properties

In CSS days past, we used to use left and right to define offsets along the x-axis, and the top and bottom properties to to define offsets along the y-axis. That makes direction switching a headache. Fortunately, CSS supports logical properties that define direction‐relative equivalents of the older physical properties. They support things like positioning, alignment, margin, padding, border, etc.

If the writing mode is horizontal (like English), then the logical inline direction is along the x-axis and the block direction refers to the y-axis. Those directions are flipped in a vertical writing mode, where inline travels the y-axis and and block flows along the x-axis.

Writing Mode x-axis y-axis
horizontal inline block
vertical  block inline

In other words, the block dimension is the direction perpendicular to the writing mode and the inline dimension is the direction parallel to the writing mode. Both inline and block levels have start and end values to define a specific direction. For example, we can use margin-inline-start instead of margin-left. This mean the margin direction automatically inverts when the page direction is rtl. It’s like our CSS is direction-aware and adapts when changing contexts.

There is another article on CSS-Tricks, Building Multi-Directional Layouts from Ahmad El-Alfy, that goes into the usefulness of building websites in multiple languages using logical properties.

This is exactly how we can handle margins, padding and borders. We’ll use them in the footer section to change which border gets the rounded edge.

The top-tight edge of the border is rounded in a default ltr writing mode.

As long as we’re using the logical equivalent of border-top-right-radius, CSS will handle that change for us.

.footer {   border-start-end-radius: 120px; }

Now, after switching to the rtl direction, it’ll work fine.

The “call to action” section is another great place to apply this:

.cta__text {   border-start-start-radius: 50%;   border-end-start-radius: 50px; } .cta__img {   border: 1px dashed var(--secondary-color);   border-inline-start-color: var(--light-color); }

Now, in Arabic, we get the correct layout:

You might be wondering exactly how the block and inline dimensions reverse when the writing mode changes. Back to the Japanese version, the text is from vertical, going from top-to-bottom. I added this line to the code:

/* The "About" section when langauge is Japanese */ html:lang(jp) .about__text {   margin-block-end: auto;   width: max-content; }

Although I added margin to the “block” level, it is applied it to the left margin. That’s because the text rotated 90 degrees when the language switched and flows in a vertical direction.

6. Other layout considerations

Even after all this prep, sometimes where elements move to when the direction and language change is way off. There are multiple factors at play, so let’s take a look.

Position

Using an absolute or fixed position to move elements may affect how elements shift when changing directions. Some designs need it. But I’d still recommend asking yourself: do I really need this?

Fro example, the newsletter subscription form in the footer section of our example can be implemented using position. The form itself takes the relative position, while the button takes the absolute position.

<form id="newsletter-form" class="relative">   <input type="email" data-attr="placeholder" data-i18n_attr="footer_input_placeholder" class="w-100">   <button class="btn btn--tertiary footer__newsletter_btn bolder absolute" data-i18n="footer_newsLetter_btn"></button> </form>
html[dir="ltr"] .footer__newsletter_btn {   right: 0; } html[dir="rtl"] .footer__newsletter_btn {   left: 0; }
This works fine in a rtl writing mode.

In the “hero” section, I made the background using a ::before pseudo-class with an absolute position:

<header class="hero relative">   <!-- etc. --> </header>
.hero {   background-image: linear-gradient(90deg, #30333f, #161616, #161616); } .hero::before  {   content: '';   display: block;   height: 100%;   width: 33.33%;   background-color: var(--primary-color);   clip-path: polygon(20% 0%, 100% 0, 100% 100%, 0% 100%);   position: absolute;   top: 0;   right: 0; }

Here’s the HTML for our hero element:

<header class="hero relative">   <!-- etc. -->   <div class="hero__social absolute">     <div class="d-flex flex-col">       <!-- etc. -->     </div>   </div> </header>

Note that an .absolute class is in there that applies position: absolute to the hero section’s social widget. Meanwhile, the hero itself is relatively positioned.

How we move the social widget halfway down the y-axis:

.hero__social {   left: 0;   top: 50%;   transform: translateY(-50%); }

In the Arabic, we can fix the ::before pseudo-class position that is used in the background using the same technique we use in the footer form. That said, there are multiple issues we need to fix here:

  1. The clip-path direction
  2. The background linear-gradient
  3. The coffee-cup image direction
  4. The social media box’s position

Let’s use a simple flip trick instead. First, we wrap the hero content, and social content in two distinct wrapper elements instead of one:

<header class="hero relative">   <div class="hero__content">       <!-- etc. -->   </div>   <div class="hero__social absolute">     <div class="d-flex flex-col">       <!-- etc. -->     </div>   </div> </header>

Then we rotate both of the hero wrappers—the social box inner wrapper and the image—180 degrees:

html[dir="rtl"] .hero, html[dir="rtl"] .hero__content, html[dir="rtl"] .hero__img img, html[dir="rtl"] .hero__social > div {   transform: rotateY(180deg); }

Yeah, that’s all. This simple trick is also helpful if the hero’s background is an image.

transform: translate()

This CSS property and value function helps move the element on one or more axes. The difference between ltr and rtl is that the x-axis is the inverse/negative value of the current value. So, we can store the value in a variable and change it according to the language.

html {   --about-img-background-move: -20%; }  html[dir='rtl']{   --about-img-background-move: 20%; }

We can do the same thing for the background image in the another section:

<figure class="about__img relative">   <img src="image.jpg" data-attr="alt" data-i18n_attr="image_alt" class="w-100"> </figure>
.about__img::after {   content: '';   position: absolute;   z-index: -1;   transform: translateY(-75%) translateX(var(--about-img-background-move));   /* etc. */ }

Margins

Margins are used to extend or reduce spaces between elements. It accepts negative values, too. For example, a positive margin-top value (20%) pushes the content down, while a negative value (e.g. -20%) pulls the content up.

If margins values are negative, then the top and left margins move the item up or to the left. However, the right and bottom margins do not. Instead, they pull content that is located in the right of the item towards the left, and the content underneath the item up. For example, if we apply a negative top margin and negative bottom margin together on the same item, the item is moved up and pull the content below it up into the item.

Here’s an example:

<section>   <div id="d1"></div>   <div id="d2"></div>   <div id="d3"></div> </section>
div {   width: 100px;   height: 100px;   border: 2px solid; } #d1 {   background-color: yellow;   border-color: red; } #d2 {   background-color: lightblue;   border-color: blue; } #d3 {   background-color: green;   border-color: red; }

The result of the above code should be something like this:

Let’s add these negative margins to the #d2 element:

#d2 {   margin-top: -40px;   margin-bottom: -70px; }

Notice how the second box in the diagram moves up, thanks to a negative margin-top value, and the green box also moves up an overlaps the second box, thanks to a negative margin-bottom value.

The next thing you might be asking: But what is the difference between transform: translate and the margins?

When moving an element with a negative margin, the initial space that is taken by the element is no longer there. But in the case of translating the element using a transform, the opposite is true. In other words, a negative margin leads pulls the element up, but the transform merely changes its position, without losing the space reserved for it.

Let’s stick to using margin in one direction:

#d2 {   margin-top: -40px;   /* margin-bottom: -70px; */ }

Now let’s replace the margin with the transform:

#d2 {   /* margin-top: -40px;*/   transform: translateY(-40px); }

You can see that, although the element is pulled up, its initial space is still there according to the natural document flow.

Flexbox

The display: flex provides a quick way to control the how the elements are aligned in their container. We can use align-items and justify-content to align child elements at the parent level.

In our example, we can use flexbox in almost every section to make the column layout. Plus, we can use it in the “offers” section to center the set of those yellow “special” and “best” marks:

.offers__item::after {   content: attr(data-offer);   display: flex;   align-items: center;   justify-content: center;   text-align: center; }

The same can be applied to the hero section to center text vertically.

<div class="d-lg-flex align-items-center">   <div class="hero__text d-xl-flex align-items-center">     <div>       <!-- text -->     </div>   </div>   <figure class="hero__img relative">     <img src="/image.jpg" data-attr="alt" data-i18n_attr="image_alt" class="w-100">   </figure> </div>

If the flex-direction value is row, then we can benefit from controlling the width for each element. In the “hero” section, we need to set the image on the angled slope of the background where the color transitions from dark gray to yellow.

.hero__text {   width: 56.5%; } .hero__img {   width: 33.33%; }

Both elements take up a a total of 89.83% of the parent container’s width. Since we didn’t specify justify-content on the parent, it defaults to start, leaving the remaining width at the end.

We can combine the flexbox with any of the previous techniques we’ve seen, like transforms and margins. This can help us to reduce how many position instances are in our code. Let’s use it with a negative margin in the “call to action” section to locate the image.

<section class="cta d-xl-flex align-items-center">   <div class="cta__text w-100">     <!-- etc. -->   </div>   <figure class="cta__img">     <img src="image.jpg" data-attr="alt" data-i18n_attr="image_alt" class="w-100">   </figure> </section>

Because we didn’t specify the flex-wrap and flex-basis properties, the image and the text both fit in the parent. However, since we used a negative margin, the image is pulled to the left, along with its width. This saves extra space for the text. We also want to use a logical property, inline-start, instead of left to handle switching to the rtl direction.

Grid

Finally, we can use a grid container to positing the elements. CSS Grid is powerful (and different than flexbox) in that it lays things along both the x-axis and the y-axis as opposed to only one of them.

Suppose that in the “offers” section, the role of the “see all” button is to get extra data that to display on the page. Here’s JavaScript code to repeat the current content:

// offers section ==> "see all" btn functionality (function(){   document.querySelector('.offers .btn').addEventListener('click', function(){     const offersContent = document.querySelector('.offers__content');     offersContent.innerHTML += offersContent.innerHTML;     offersContent.classList.remove('offers__content--has-margin');     this.remove();   }) })();

Then, let’s use display: grid in the CSS for this section. First, here’s our HTML, with our grid container highlighted.

<div class="offers__content offers__content--has-margin d-grid">   <div class="offers__item relative" data-attr="data-offer" data-i18n_attr="special_offer">     <!-- etc. -->   </div>   <div class="offers__item relative" data-attr="data-offer" data-i18n_attr="best_offer">     <!-- etc. -->   </div>   <div class="offers__item relative" data-attr="data-offer" data-i18n_attr="best_offer">     <!-- etc. -->   </div> </div>

We implement CSS Grid on the .offers__content element:

html {   /* custom properties */   --offers-content-column: repeat(3, 1fr);   --offers-content-gap: 5vw; }  .offers__content {   display: grid;   grid-template-columns: var(--offers-content-column);   gap: var(--offers-content-gap); } .offers__content--has-margin {   margin-block-end: 60px; }

This is the result after clicking the button:

Our page is far from being the best example of how CSS Grid works. While I was browsing the designs online, I found a design that uses the following structure:

Notice how CSS Grid makes the responsive layout without media queries. And as you might expect, it works well for changing writing modes where we adjust where elements go on the grid based on the current writing mode.

Wrapping up

Here is the final version of the page. I ensured to implement the responsiveness with a mobile-first approach to show you the power of the CSS variables. Be sure to open the demo in full page mode as well.

I hope these techniques help make creating multilingual designs easier for you. We looked at a bunch of CSS properties we can use to apply styles to specific languages. And we looked at different approaches to do that, like selecting the :lang pseudo-class and data attributes using the attr() function. As part of this, we covered what logical properties are in CSS and how they adapt to a document’s writing mode—which is so much nicer than having to write additional CSS rulesets to swap out physical property units that otherwise are unaffected by the writing mode.

We also checked out a number of different positioning and layout techniques, looking specifically at how different techniques are more responsive and maintainable than others. For example, CSS Grid and Flexbox are equipped with features that can re-align elements inside of a container based on changing conditions.

Clearly, there are lots of moving pieces when working with a multilingual site. There are probably other requirements you need to consider when optimizing a site for specific languages, but the stuff we covered here together should give you all of the layout-bending superpowers you need to create robust layouts that accommodate any number of languages and writing modes.


The post Control Layout in a Multi-Directional Website appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Building Multi-Directional Layouts

There are some new features in CSS that can assist us with building layouts for different directions and languages with ease. This article is about CSS logical properties and values (e.g. margin-inline-start).  These are a W3C working draft that still going under heavy editing, but have shipped in many browsers. I want to talk about this because I’ve been using these properties for a little while and have seen a significant boost to my workflow after switching to them.

I’ll talk about the specifications and how can you use it today in your work. I live in Egypt where we use Arabic as a primary language. Arabic is written from right to left, which means Arabic websites look like a mirror image of an English version. Most of the websites we create are bilingual, which means we provide a stylesheet specific for each direction. We do that by flipping values and properties of almost everything! I will not talk in details about this part but you can talk a quick look about a past article I wrote on the topic. 

It starts with declaring the dir attribute on the HTML tag. 

 <html dir="rtl">

This attribute accepts one of two values: ltr (which is the default value if none is specified) and rtl. According to its value, the browser starts to paint the elements following a specific algorithm. Text will be written with respect to the direction and punctuations will be placed in their correct location. Some elements, like tables, will have their direction switched (for example, a <td> starting from the right in rtl). Thankfully, some new specifications, like CSS Grid, and flexbox follow a similar approach to the table. That means we don’t have to change the order of anything because the browser will take care of it!

HTML5 introduced a new auto value for the dir attribute. It will check for the first character within the element and, if it belongs to a language that is written from left-to-right (like Latin characters), the element will have an ltr direction and vice versa. The W3C urges authors to avoid relying on this value to determine text direction and use a server-side solution instead. 

An interesting use case for the auto value is when you’re unsure about the direction of the content, such user-generated content, like a comment thread. I see a lot of people contributing to discussions in Arabic websites in English. The support for auto is pretty good except, for Internet Explorer and Edge.

Introducing the :dir() pseudo-class

The :dir() pseudo-class is a new selector modifier that selects an element by evaluating its direction value. It works like this:

/* Select all paragraphs that have their computed direction value set to rtl */ p:dir(rtl) {   font-size: 16px; /* Sometimes Arabic glyphs need a size boost to feel right. */ } 
 /* Select all paragraphs that have their computed direction value set to ltr */ p:dir(ltr) {   font-size: 14px; }

The beauty of this selector is that it’s the first one to select elements based on a computed value. Whether the direction of the element was inherited from the HTML dir attribute or set using CSS like html:lang("ar") { direction: rtl; }, the selector will match the element. Even better, if you have the direction of the element set to auto, it will still correctly the element based on its content!

<style>   p {     direction: auto;   }   p:dir(ltr) {     background: green;   }   p:dir(rtl) {     background: red;   } </style> 
 <!-- The following paragraph will have a green background --> <p>This is a paragraph that starts with a latin character</p> <!-- The following paragraph will have a red background --> <p>هذا النص يستخدم حروف عربية</p>

Sadly, the support for :dir() isn’t great and limited only to Firefox.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
No No 17* No No No

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
No No No No No 68

Even if the browser support was great, the selector only allows you to target elements and manually apply certain styles to them. That means that we still should flip the values for everything (like margins, paddings, floats, positions, etc.) which doesn’t really enhance our workflow or reduce the effort to produce multi-directional layouts.

Introducing CSS logical properties and values

As defined by the W3C, logical properties and values provide us with the ability to control layout through logical, rather than physical, direction and dimension mappings. Let’s skip the technical jargon and jump directly to the details. These provide us with new properties and values that will evaluate differently according to certain conditions.

Logical values

Let’s say we have a paragraph that we want to align in a direction that’s opposite to the natural direction of the language. Let’s say this is in English that follows the ltr direction. We would do something like this:

<article>   <p class="opposite">     Lorem ipsum dolor sit amis ..   </p> </article>

And the CSS file would be like this:

.opposite {   text-align: right; }

To provide the opposite for the rtl version, we would override the selector by targeting the <html> tag with the dir attribute, or simply provide a different file for the rtl version, like this:

html[dir="rtl"] .opposite {   text-align : left; }

Logical properties and values were created to solve this problem. Why don’t we use values that evaluates to the correct context instead of using left and right? In an ltr element, the value left means the beginning or the start of the element while on the rtl element, the value right means the start! It’s simple, right?

So instead of what we wrote before, we can use:

.opposite {   text-align: end; }

And that’s it! If the element’s computed direction is ltr, the text would be aligned right; and the computed direction would be opposite for the rtl elements. So, instead of using left and right values for text-align, we can simply replace it with start and end. This is a lot easier and more convenient than our previous options.

Logical properties

What we just looked at were logical values, so let’s turn now to logical properties. Logical properties are new properties that have the same idea; they evaluate differently according to the direction of the element. Take a look at margin as an example. Previously, we wanted to add some space toward the start of the paragraph. We can do so in the ltr document by using:

article img {   margin-left: 15px; }

Now, in the case of the rtl version, we will need to add the margin to the opposite direction in addition to resetting the left value:

html[dir="rtl"] article img {   margin-left: 0;   margin-right: 15px; }

We can do better with logical properties. Consider the following:

article img {   margin-inline-start: 15px; }

The -inline-start part evaluates to the beginning of the horizontal axis of the image. In the case of ltr, that means left, and in the case of rtl, that means right.

The start and end are probably obvious by now, but what is with the word inline and why do we need it? To understand it, we need to talk about something called CSS writing modes. Jen Simmons wrote an excellent article on that topic. I won’t regurgitate everything explained there, but the bottom line is that we can use writing modes to define the writing direction. Some languages, like the Chinese, Korean, and Japanese, can be written vertically from top to bottom. CSS writing modes allow us to control that flow. Take a look at the following paragraph:

You can clearly identify the top, bottom, left and right edges of the block. What will happen if we change the direction of the paragraph using CSS writing modes to flow vertically?

When we talk about the “top” of this rotated paragraph, do we mean the where the content begins or what was the right edge when it wasn’t rotated? Identifying the four directions becomes confusing. Let’s look at them from a different perspective. In “normal” writing conditions, the vertical axis will have the suffix -block and the horizontal axis will have the suffix -inline, both followed by start or end:

And if we rotate it, it should be like this:

Since we are talking about normal horizontal layout, we will be using -inline-start and -inline-end, but it is good to know about the other properties as well. We can also write logical shorthand values by using the logical keyword. Consider the following:

article img {   margin: logical 10px 20px 30px 40px; }

The computed value of the image’s margin will be the following:

article img {   margin-block-start: 10px;   margin-inline-start: 20px;   margin-block-end: 30px;   margin-inline-end: 40px; }

The logical keyword is an experimental feature at this point in time, which means it may change or it may be replaced by the time the spec becomes official. There’s an open discussion on the topic that you can follow in the meantime.

Logical properties also allow us to apply values to a certain axis, meaning we have margin-inline and margin-block for the horizontal and vertical axises, respectively.

Property Logical Property
margin-top margin-block-start
margin-left margin-inline-start
margin-right margin-inline-end
margin-bottom margin-block-end

Just like with the logical margin properties, we also have logical padding properties which follow the same rules as the margin.

Property Logical Property
padding-top padding-block-start
padding-left padding-inline-start
padding-right padding-inline-end
padding-bottom padding-block-end

Logical positioning properties

In the previous examples we were able to modify the meaning of the property by appending suffixes, but what about the positions? The properties names changed completely from what we know now as top, right, bottom, and left.

.element {   position: absolute;   inset-block-start: 0;  /* evaluates to top */   inset-block-end: 0;    /* evaluates to bottom */   inset-inline-start: 0; /* evaluates to left in ltr and right in rtl */   inset-inline-end: 0;   /* evaluates to right in ltr and left in rtl */ }

Learning new properties and values can be hard but, hey, we get a shorthand property called inset to make it a little easier:

/* Shorthand FTW! */ .element {   position: absolute;   inset: logical 10px 20px 30px 40px; } 
 /* It evaluates to this */ .element {   position: absolute;   inset-block-start: 10px;   inset-inline-start: 20px;   inset-block-end: 30px;   inset-inline-end: 40px; }

inset supports both inset-block and inset-inline just like margin and padding.

Property Logical Property
top inset-block-start
left inset-inline-start
right inset-inline-end
bottom inset-block-end

Logical border properties

Border properties can also become logical by appending the -inline-start and -block-start.

Property Logical Property
border-top{-size|style|color} border-block-start{-size|style|color}
border-left{-size|style|color} border-inline-start{-size|style|color}
border-right{-size|style|color} border-inline-end{-size|style|color}
border-bottom{-size|style|color} border-block-end{-size|style|color}

In her deep dive on logical properties and values, Rachel Andrew includes an excellent demo that demonstrates logical border properties and how they respond to different writing modes.

How can we start using all this today?

We can start using all the magic of logical properties and value today, thanks to the power of PostCSS! Jonathan Neal wrote this lovely PostCSS plugin that enables us to write logically and compile the code to something today’s browsers will understand. The plugin works in three stages:

  • It translates the new syntax to existing standards that unsupported browsers will recognize, using the :dir pseudo-class to create output to ltr and rtl.
  • It uses another one of Neal’s plugins to translate :dir to an attribute selector, like this:
 .element:dir(ltr) {    ...  }  [dir="ltr"] .element {    ...  }
  • It uses the postcss-nested plugin to transform nested selectors to one-line selectors, the same way other CSS preprocessors do.

PostCSS works with any workflow. You can try it with Grunt, Gulp, and webpack.


I will close by saying I have seen a lot of benefits since making the shift to logical properties and values. Sure, building multi-directional layouts takes time. There’s the learning curve, the addition of more properties to write, and of course, testing. Our previous methods for creating multi-directional layouts were either taking care of both directions in development or working on one direction at a time — neither of which is all that suitable for big projects. With logical properties and values you write your code once and it works for both directions without any consideration.

References

The post Building Multi-Directional Layouts appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]