Status: IMPLEMENTED
Champion: Pierre-Marie Dartus (@pmdartus)
Revision: latest
RFC created: 2019/02/12
Last updated: 2020/08/20
RFC: https://github.com/salesforce/lwc-rfcs/pull/1
Implementation: https://github.com/salesforce/lwc/pull/1099
This RFC has been merged
# 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:
- the root
html
tag to set the base direction of the page - or on any HTML element to set the text direction on a specific subtree
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:
- it should work in both native and synthetic Shadow DOM
- it should be polyfilled on all the browsers since Blink and Webkit don't implement this selector.
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
-
Polyfill related:
- Direction has to be set at the application level: Adding the
dir
attribute in a subtree will not be honored since the framework would take over thedir
attribute on the host element.
- Direction has to be set at the application level: Adding the
-
Spec related:
- Lack of progress from the Blink and Webkit team in the implementation of the
:dir()
pseudo-class: The Wekbit issue and Blink issue doesn't show any major progress on the implementation of this selector since 2016. - Open questions about direction behavior in the context of Shadow DOM: There is still an open question about how the direction should be inherited in the context of Shadow DOM.
- Lack of progress from the Blink and Webkit team in the implementation of the
# Alternative
- Transforming
:dir(rtl) p
to[dir="rtl"] p
: This approach works well in the context on synthetic shadow DOM but breaks with the native one.
# 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.