Skip to main content

Right to Left

A guide about QtQuick tools that help adapting UI to Right-to-Left (RTL) locales like Arabic and Hebrew.

How to detect an RTL environment in QtQuick

QtQml, QtQuick and QtQuick.Controls.2 provide us with three ways to check for RTL, each on a different level of granularity:

Global

Qt detects current locate at the application level as a whole, sets the layout direction of QGuiApplication accordingly, and exposes it as a property through the Qt global object:

Qt.application.layoutDirection === Qt.RightToLeft

Hierarchy tree of visual items

Any Item and Window component may have a LayoutMirroring object attached to override the implicitly inherited layout direction.

LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true

Normally you want to always set childrenInherit: true unless you really know what you are doing.

tip

The default value for LayoutMirroring.enabled is not bound in any way to Qt.application.layoutDirection, and must be set on each root Window of your application explicitly if you mean to support RTL layouts.

If you are writing a root content item for an externally provided Window (such as when a window is a custom subclass created in C++), binding LayoutMirroring on the content item is not enough: QQC2.Overlay.overlay will be a sibling to your content item, thus it won't inherit the mirroring, and you'll end up with weird non-adapted menus, dialogs and other popups. Use this trick:

Item {
id: customWindowContentItem

Window.onWindowChanged: {
if (Window.window) {
Window.window.LayoutMirroring.enabled = Qt.binding(() => Qt.application.layoutDirection === Qt.RightToLeft);
Window.window.LayoutMirroring.childrenInherit = true;
}
}
}

Controls

QtQuick.Controls module provides its own shorthand: Control::mirrored & Popup::mirrored read-only boolean properties. And these are the most fine-grained ones you can rely on.

Interestingly enough, they are not mentioned on the Right-to-left User Interfaces Qt documentation page, because QQC2 is considered a separate downstream module, so you can't refer to QQC2 types there. They do share the same underlying infrastructure though. In fact, all this is implemented in the base QQuickItem class, and mirrored is basically identical to LayoutMirroring.enabled — so it won't be unreasonable to wonder why do you need to allocate memory for extra attached objects or expose the value only in subclasses? But this was not always the case: up until Qt 6.2 they took Control::locale into account as well.

Technically, you could still bind mirrored to locale.textDirection == Qt.RightToLeft to somewhat replicate the old behavior, although personally I've never seen such code or felt the need for it.

Standard library

Now that we know how to detect an RTL layout direction, let's learn how can we use this information to properly reverse our UI. Some things are automatically affected by LayoutMirroring.enabled but other aren't.

✅ Affected:

  • Item::anchors: left, right, leftMargin, rightMargin & horizontalCenterOffset.
  • QtQuick Positioners: effectiveLayoutDirection property of Grid, Flow, Row & Column.
  • QtQuick.Layouts: default horizontal alignment for ColumnLayout and default horizontal flow (so called effectiveLayoutDirection, except this time it's not publicly exposed) for RowLayout and GridLayout.
  • Text/Label, TextInput/TextField and TextEdit/TextArea counterparts, but only if the horizontalAlignment is explicitly set.

Not affected:

  • Let's get this elephant out of the room first. The Item::x coordinate property is not mirrored. To specify the distance between a leading edge and its parent use parent.width - width - x expression. This trick is useful for, e.g. positioning Menu popup around its visual parent drop-down button.
  • Paddings (leftPadding & rightPadding), pretty much everywhere, even in types like QtQuick Positioners which are otherwise responsive to RTL:
  • Margins of Flickable (and, by extension, ListView), even though internally they are confusingly dubbed as d->hData.startMargin & endMargin.
  • Effective default alignment of Text. That's why in KDE codebase there is a workaround in QQC2.Label implementations (one, two):
    T.Label {
    // Work around Qt bug where left aligned text is not right aligned
    // in RTL mode unless horizontalAlignment is explicitly set.
    // https://bugreports.qt.io/browse/QTBUG-95873
    horizontalAlignment: Text.AlignLeft
    }

For paddings and margins which are not automatically mirrored you have to write conditional code with repetitive expressions like this:

leftPadding: mirrored ? Kirigami.Units.smallSpacing : 0
rightPadding: mirrored ? 0 : Kirigami.Units.smallSpacing

This is pretty much how QQC2 controls are defined. When expressions get too long to reasonably repeat them more than once, consider declaring a pair of semantically named properties:

// optionally, mark them as `readonly`
property real leadingPadding: 0
property real trailingPadding: Kirigami.Units.smallSpacing

leftPadding: mirrored ? trailingPadding : leadingPadding
rightPadding: mirrored ? leadingPadding : trailingPadding

How to use

When implementing QQC2 Controls, such as QQC2 style, use control's mirrored property for all horizontal positioning. If the root component is not a sub-type of QQC2.Control, make sure to check for root component's root.LayoutMirroring.enabled instead.

important

Do not check for any child component's LayoutMirroring.enabled! Only root component's attached object is valid for mirroring, because LayoutMirroring.childrenInherit might be disabled, in which case the high-level children would not inherit the mirroring but the control's internals should still be mirrored; in other words, implementation details does not count as "children".

Icons

The Qt documentation suggests using Image { mirror: true } for images, however not all images can be simply mirrored in RTL (see Apple Developer Documentation), there is no automatic icon reversal provisioned in Free Desktop icon themes specification, and there is no mirror group property for QtQuickIcon gadget (also known as AbstractButton::icon and Action::icon).

Free Desktop icon themes specification, however, does specify a fallback chain mechanism whereby a requested icon name consists of parts from most generic to most specific, joined with dashes. A missing icon would be searched again by its name without the last (most specific) part. Thus, developers may request an RTL variant of an icon, and it would automatically fallback to non-RTL icon if RTL variant does not exist.

tip

Use the following idiom to request an adaptive icon for controls:

Button {
icon.name: mirrored ? 'some-icon-rtl' : 'some-icon'
}

For non-controls, use LayoutMirroring.enabled instead of mirrored.

Icons can also have symbolic counterparts, which is a colorless/monochrome variant suitable for small and contrast contexts, such as buttons and menu items. For such icon sets RTL should take priority, because it is better to fallback to a non-symbolic RTL icon rather than to a symbolic but not mirrored.

tip

For symbolic RTL icons use *-rtl-symbolic naming scheme.

How to test

Running a Qt app with --reverse flag would only force the default layout direction, but won't localize text labels, so the overall experience will be limited.

To enable full experience, you need to set language/locale environment variables, for example:

export LANGUAGE=ar:en_US LANG=ar_EG.UTF-8 LC_ALL=

# or save aliases to your shell rc
alias RTL="LANGUAGE=ar:en_US LANG=ar_EG.UTF-8 LC_ALL="
alias RTL_="export LANGUAGE=ar:en_US LANG=ar_EG.UTF-8 LC_ALL="

RTL dolphin

Resources