Right-to-Left Support

Summary

The standard way to style components depending on the text directionality using [dir="ltr"] doesn't work in the context of shadow DOM. This proposal introduces support for the :dir() pseudo-class to support styling components based on text direction in a shadow compliant way.

Motivation

Support for Right-to-Left (RTL) text is part of the HTML specification. The direction of the text for display can be set via the dir attribute on:

In order to style elements in the pages based on the text direction, developers used to CSS attribute selectors dir attribute selector. For example:

.menu-item {
    float: left;
}

[dir="rtl"] .menu-item {
    float: right;
}

While this works great for standard HTML pages, it doesn't work as is in the context of Shadow DOM since the dir attribute may not be present in the shadow tree.

Proposal

Adding support for the directionality pseudo-class :dir() in the LWC compiler would allow developers to style the components based on directionality in a way that is compliant with the Shadow DOM encapsulation. It accepts 1 direction argument that can either be ltr or rtl. It allows others to match against elements based on its directionality. Note that this selector doesn't match against the stylistic direction defined via the position CSS property.

This pseudo selector is defined as part of the Selectors Level 4 W3C Working Draft. The selector is currently supported natively by Gecko, and is currently under discussion for Wekbit and Blink. While this selector is not natively supported in Blink, Polymer uses the :dir() pseudo-class for direction-based styling.

.menu-item {
    float: left;
}

:dir(rtl) .menu-item {
    float: right;
}

In some cases, CSS is not enough to style components depending on the text direction. In the context of dropdowns and modals elements positioning is done via Javascript and depending on the directionality the position of the elements needs to be changed. Because the directionality of an element can be influenced by multiple factors and also because browsers don't provide a standard API to get the directionality, LWC will not expose a specific API to get the directionality of a component. Component authors can access the directionality in multiple ways without the intervention of the framework.

// Option 1: Accessing base direction from the page
function getBaseDirection() {
    const html = document.documentElement;
    return html.getAttribute('dir') === 'rtl' ? 'rtl' : 'ltr';
}

// Option 2: Access inherited component direction
import { LightningElement } from 'lwc';

export default class TextDirection extends LightningElement {
    connectedCallback() {
        // Wait for a RAF to avoid layout trashing
        requestAnimationFrame(() => {
            const { direction } = window.getComputedStyle(this.template.host);
            console.log('Component direction :', direction);
        });
    }
}

Detailed design

There are multiple aspects here to take into account when it comes to supporting the :dir() selector in LWC:

In order to accommodate the requirements, this proposal uses a CSS transformation done at compile time and a runtime polyfill. This approach is similar to the Polymer DirMixin one.

CSS transformation

Since support for the :host-context() pseudo-class selector has been dropped in LWC, the only way to style a component dynamically is via the addition of the class or an attribute on the host element. This class or attribute can then be consumed via the :host() pseudo-class selector from within the shadow tree. During compilation all the :dir(rtl) pseudo-class selectors will be transformed into :host([dir="rtl"]) pseudo-class selectors.

/* Original */
:dir(rtl) p {
    float: right;
}

/* Transformed */
:host([dir="rtl"]) p {
    float: right;
}

Adding the dir attribute on all the host elements even in the case where the dir is set to ltr is too expensive. The usage of :dir(ltr) should make the compiler warn or error.

Runtime polyfill

Then at runtime, the engine will be in charge of adding a MutationObserver on the document element listening for changes to the dir attribute. If it changes, the engine will then be in charge of reflecting the changes on all the impacted host elements.

Drawbacks

Alternative

How do we teach this?

RTL support is a fairly advanced use case, so for most users, they won't encounter this feature at all. Even if the selector doesn't happen to be implemented by some browser vendors we can keep the polyfill in place.

We can add a section in the component styling section of the developer guide to cover this.

undefined