Learn how to enable support for right-to-left text in Boosted across our layout, components, and utilities.

Get familiar

We recommend getting familiar with Boosted first by reading through our Getting Started Introduction page. Once you’ve run through it, continue reading here for how to enable RTL.

You may also want to read up on the RTLCSS project, as it powers our approach to RTL.

Boosted’s RTL feature is still experimental and will evolve based on user feedback. Spotted something or have an improvement to suggest? Open an issue, we’d love to get your insights.

Required HTML

There are two strict requirements for enabling RTL in Boosted-powered pages.

  1. Set dir="rtl" on the <html> element.
  2. Add an appropriate lang attribute, like lang="ar", on the <html> element.

From there, you’ll need to include an RTL version of our CSS. For example, here’s the stylesheet for our compiled and minified CSS with RTL enabled:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/boosted@5.3.3/dist/css/boosted.rtl.min.css" integrity="sha384-u0MzeBZZtzZYfm2QxXQbV14lVgTHrLKdF/uhQkWT4IYfJAPcyVsCOXVHSE9jDiYh" crossorigin="anonymous">

Starter template

You can see the above requirements reflected in this modified RTL starter template.

<!doctype html>
<html lang="ar" dir="rtl">
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Preconnect to CDN: remove if not needed -->
    <link href="https://cdn.jsdelivr.net" rel="preconnect" crossorigin="anonymous">

      Neue Helvetica is a trademark of Monotype Imaging Inc. registered in the U.S.
      Patent and Trademark Office and may be registered in certain other jurisdictions.
      Copyright © 2014 Monotype Imaging Inc. All rights reserved.
      Orange has purchased the right to use Helvetica in its websites and mobile applications.
      If you are not authorized to used it, don't include the orange-helvetica.css
      See NOTICE.txt for more information.
    <!-- Option 1: Use a CDN -->
    <link href="https://cdn.jsdelivr.net/npm/boosted@5.3.3/dist/fonts/HelveticaNeueW20-55Roman.woff2" rel="preload" as="font" type="font/woff2" integrity="sha384-3JzHT24JpS8epPIAdqo7AcCNQcr5VxQi8FClxBayyd/6BLTIFbJLiGD4CIf8FtRl" crossorigin="anonymous">
    <link href="https://cdn.jsdelivr.net/npm/boosted@5.3.3/dist/fonts/HelveticaNeueW20-75Bold.woff2" rel="preload" as="font" type="font/woff2" integrity="sha384-vpoGPps82D7bRdHnBlcsNi/WGJMOyFhPA9+NEonxOo5bYJGzIAjfIJ9tuZ0fPyKr" crossorigin="anonymous">
    <link href="https://cdn.jsdelivr.net/npm/boosted@5.3.3/dist/css/orange-helvetica.rtl.min.css" rel="stylesheet" integrity="sha384-4MlBAYgNr+UGO/cACkdFashk4EWyneAX3fp0OWoM7k8/smSl1irUexFx6u83N9Em" crossorigin="anonymous">

    <!-- Option 2: Embed the fonts
    <link href="dist/fonts/HelveticaNeueW20-55Roman.woff2" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
    <link href="dist/fonts/HelveticaNeueW20-75Bold.woff2" rel="preload" as="font" type="font/woff2" crossorigin="anonymous">
    <link href="dist/css/orange-helvetica.rtl.min.css" rel="stylesheet" crossorigin="anonymous">

    <!-- Boosted CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/boosted@5.3.3/dist/css/boosted.rtl.min.css" integrity="sha384-u0MzeBZZtzZYfm2QxXQbV14lVgTHrLKdF/uhQkWT4IYfJAPcyVsCOXVHSE9jDiYh" crossorigin="anonymous">

    <title>مرحبًا بالعالم!</title>
    <h1>مرحبًا بالعالم!</h1>

    <!-- Optional JavaScript; choose one of the two! -->

    <!-- Option 1: Boosted Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/boosted@5.3.3/dist/js/boosted.bundle.min.js" integrity="sha384-3RoJImQ+Yz4jAyP6xW29kJhqJOE3rdjuu9wkNycjCuDnGAtC/crm79mLcwj1w2o/" crossorigin="anonymous"></script>

    <!-- Option 2: Separate Popper and Boosted JS -->
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/boosted@5.3.3/dist/js/boosted.min.js" integrity="sha384-TfjOlWccrKKSEc/hJqxs6Tofoh4+tlm//VJYb92Ow7aPNtgfaKuuLsnFqObi3xmp" crossorigin="anonymous"></script>

RTL examples

Get started with one of our RTL examples.


Our approach to building RTL support into Boosted comes with two important decisions that impact how we write and use our CSS:

  1. First, we decided to build it with the RTLCSS project. This gives us some powerful features for managing changes and overrides when moving from LTR to RTL. It also allows us to build two versions of Bootstrap from one codebase.

  2. Second, we’ve renamed a handful of directional classes to adopt a logical properties approach. Most of you have already interacted with logical properties thanks to our flex utilities—they replace direction properties like left and right in favor start and end. That makes the class names and values appropriate for LTR and RTL without any overhead.

For example, instead of .ml-3 for margin-left, use .ms-3.

Working with RTL, through our source Sass or compiled CSS, shouldn’t be much different from our default LTR though.

Customize from source

When it comes to customization, the preferred way is to take advantage of variables, maps, and mixins. This approach works the same for RTL, even if it’s post-processed from the compiled files, thanks to how RTLCSS works.

Custom RTL values

Using RTLCSS value directives, you can make a variable output a different value for RTL. For example, to decrease the weight for $font-weight-bold throughout the codebase, you may use the /*rtl: {value}*/ syntax:

$font-weight-bold: 700 #{/* rtl:600 */} !default;

Which would output to the following for our default CSS and RTL CSS:

/* boosted.css */
dt {
  font-weight: 700 /* rtl:600 */;

/* boosted.rtl.css */
dt {
  font-weight: 600;

Alternative font stack

In the case you’re using a custom font, be aware that not all fonts support the non-Latin alphabet. To switch from Pan-European to Arabic family, you may need to use /*rtl:insert: {value}*/ in your font stack to modify the names of font families.

For example, to switch from Helvetica Neue font for LTR to Helvetica Neue Arabic for RTL, your Sass code could look like this:

  Helvetica Neue#{"/* rtl:insert:Arabic */"},
  // Cross-platform generic font family (default user interface font)
  // Safari for macOS and iOS (San Francisco)
  // Chrome < 56 for macOS (San Francisco)
  // Windows
  "Segoe UI",
  // Android
  // Basic web fallback
  // Linux
  "Noto Sans",
  // Sans serif fallback
  // Emoji fonts
  "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default;

Boosted RTL assumes Arabic content

Helvetica Neue Arabic is used as default font in Boosted RTL: if you need our RTL files for a non-arabic content, you’ll need to override the font-family and ensure to include a more appropriate font.

LTR and RTL at the same time

Need both LTR and RTL on the same page? Thanks to RTLCSS String Maps, this is pretty straightforward. Wrap your @imports with a class, and set a custom rename rule for RTLCSS:

/* rtl:begin:options: {
  "autoRename": true,
  "stringMap":[ {
    "name": "ltr-rtl",
    "priority": 100,
    "search": ["ltr"],
    "replace": ["rtl"],
    "options": {
      "scope": "*",
      "ignoreCase": false
  } ]
} */
.ltr {
  @import "../node_modules/boosted/scss/boosted";

After running Sass then RTLCSS, each selector in your CSS files will be prepended by .ltr, and .rtl for RTL files. Now you’re able to use both files on the same page, and simply use .ltr or .rtl on your components wrappers to use one or the other direction.

Edge cases and known limitations to consider when working with a combined LTR and RTL implementation:

  1. When switching .ltr and .rtl, make sure you add dir and lang attributes accordingly.
  2. Loading both files can be a real performance bottleneck: consider some optimization, and maybe try to load one of those files asynchronously.
  3. Nesting styles this way will prevent our form-validation-state() mixin from working as intended, thus require you tweak it a bit by yourself. See #31223.

The breadcrumb case

The breadcrumb separator is the only case requiring its own brand-new variable— namely $breadcrumb-divider-flipped —defaulting to $breadcrumb-divider.

Additional resources