Skip to main content

RTL Support

For right-to-left (RTL) languages such as Arabic and Hebrew to be semantically appropriate, in addition to translating the texts, you need to also mirror the layout.

Chakra UI makes it possible to support RTL languages and LTR languages in the same app. There are 2 methods of adding RTL support:

  • Using the RTL Stylis Plugin
  • Using RTL-aware style props

Using RTL Stylis Plugin

Since Chakra UI is built on top of @emotion/react, you can leverage stylis plugins like stylis-plugin-rtl to automatically transform the generated styles to their RTL equivalent.

Here's how to set it up.

  1. Install the stylis plugin and emotion's cache

    npm i stylis stylis-plugin-rtl @emotion/cache

    # or

    yarn add stylis stylis-plugin-rtl @emotion/cache
  2. Create the RTL provider using CacheProvider from emotion

    src/components/rtl-provider.js
    import { CacheProvider } from '@emotion/react';
    import createCache from '@emotion/cache';
    import rtl from 'stylis-plugin-rtl';

    // NB: A unique `key` is important for it to work!
    const options = {
    rtl: { key: 'css-ar', stylisPlugins: [rtl] },
    ltr: { key: 'css-en' },
    };

    export function RtlProvider({ children }) {
    const { locale } = useRouter();
    const dir = locale == 'ar' ? 'rtl' : 'ltr';
    const cache = createCache(options[dir]);
    return <CacheProvider value={cache} children={children} />;
    }
  3. Add RTL provider to the application's root

    pages/_app.js
    import { ChakraProvider } from '@chakra-ui/react';
    import { RtlProvider } from '@/components/rtl-provider';

    function App(props) {
    const { Component, pageProps } = props;
    return (
    <ChakraProvider>
    <RtlProvider>
    <Component {...pageProps} />
    </RtlProvider>
    </ChakraProvider>
    );
    }

    export default App;
  4. Add the dir and lang attributes to the html tag.

    You'll need to make a few changes to your markup. In the <html> tag, you'll need to add a dir attribute with a value of rtl or ltr. Here's what your <html> tag should look like:

    /**
    * `lang` can be "ar", "en", "he", etc.
    * `dir` can be "rtl" or "ltr"
    */
    <html lang='ar' dir='rtl'>
    {/* Content */}
    </html>

    In Next.js, you can achieve this by adding a pages/_document.js file and using this API:

    pages/_document.js
    import NextDocument, { Html, Head, Main, NextScript } from "next/document"

    class Document extends NextDocument {
    static async getInitialProps(ctx) {
    return await NextDocument.getInitialProps(ctx)
    }

    render() {
    const { locale } = this.props.__NEXT_DATA__
    const dir = locale === "ar" ? "rtl" : "ltr"
    return (
    {/* 👇🏻 Here's the place to change the `dir` and `lang` */}
    <Html dir={dir} lang={locale}>
    <Head />
    <body >
    <Main />
    <NextScript />
    </body>
    </Html>
    )
    }
    }

    export default Document
  5. Add a way to switch between LTR and RTL

    For example, in Next.js, you can change the locale on the route by leveraging the built-in useRouter hook.

    src/components/lang-switcher.js
    function LangSwitcher() {
    const { locale, push, reload, pathname } = useRouter();
    const nextLocale = locale === 'en' ? 'ar' : 'en';

    const onClick = async () => {
    await push(pathname, { locale: nextLocale });
    // force a reload for it to work correctly.
    reload();
    };

    return <button onClick={onClick}>Change to {nextLocale}</button>;
    }

    export default LangSwitcher;

Caveats of this approach

  • You might need to force a reload of the page to get it working correctly.
  • You might need to change the placement of components like Popover, Drawer, Tooltip to match RTL.
  • The need to install extra packages like stylis, stylis-plugin-rtl might increase your final bundle.

Using RTL-aware style props

This is the recommended way to support RTL in Chakra UI. With this approach we use the appropriate CSS logical properties, and manage the placements of components like Popover, Drawer, Tooltip to match RTL.

Here's how to set it up:

  1. Add a direction key to the theme

    You can use the extendTheme function or any other preferred approach to add direction key to the theme. Then, add the custom theme to ChakraProvider.

    note

    Due to the fact that some CSS logical properties aren't supported in all browsers, we flip the styles based on the direction as a temporary polyfill.

    src/components/chakra-rtl-provider.js
    function ChakraRTLProvider({ children }) {
    const { locale } = useRouter();
    const direction = locale === 'ar' ? 'rtl' : 'ltr';

    // 👇🏻 Here's the place we add direction to the theme
    const theme = extendTheme({ direction });

    return <ChakraProvider theme={theme}>{children}</ChakraProvider>;
    }
  2. Add the dir and lang attributes to the html tag.

    In Next.js, you can achieve this by adding a pages/_document.js file and using this API:

    pages/_document.js
    import NextDocument, { Html, Head, Main, NextScript } from "next/document"

    class Document extends NextDocument {
    static async getInitialProps(ctx) {
    return await NextDocument.getInitialProps(ctx)
    }

    render() {
    const { locale } = this.props.__NEXT_DATA__
    const dir = locale === "ar" ? "rtl" : "ltr"
    return (
    {/* 👇🏻 Here's the place to change the `dir` and `lang` */}
    <Html dir={dir} lang={locale}>
    <Head />
    <body >
    <Main />
    <NextScript />
    </body>
    </Html>
    )
    }
    }

    export default Document
  3. Replace style props with their RTL-aware equivalent

    To get our internal RTL system working, you need to replace all physical *-left or *-right styles (passed as props or in the sx prop) to their bi-directional versions (*-start or *-end).

    For example:

    • Replace paddingLeft or pl prop with paddingStart or ps
    • Replace marginRight or mr prop with marginEnd or me
    • Replace borderLeftRadius with borderStartRadius

    Here's a list of the RTL-aware style props you can use alongside other CSS logical properties:

    Style propReplace withDescription
    paddingLeft, plpaddingStart, pspadding in start direction
    paddingRight, prpaddingEnd, pepadding in end direction
    marginLeft, mlmarginStart, msmargin in start direction
    marginRight, mrmarginEnd, memargin in end direction
    roundedLeft, borderLeftRadiusroundedStart, borderStartRadiusrounded borders in start direction
    roundedRight, borderRightRadiusroundedEnd, borderEndRadiusrounded borders in end direction
    roundedTopLeft, borderTopLeftRadiusroundedTopStart, borderTopStartRadiusrounded borders in top start direction
    roundedTopRight, borderTopRightRadiusroundedTopEnd, borderTopEndRadiusrounded borders in top end direction
    roundedBottomLeft, borderBottomLeftRadiusroundedBottomStart, borderBottomStartRadiusrounded borders in bottom start direction
    roundedBottomRight, borderBottomRightRadiusroundedBottomEnd, borderBottomEndRadiusrounded borders in bottom end direction
    borderLeftborderStart, borderInlineStartborder width in start direction
    borderRightborderEnd, borderInlineEndborder width in end direction
    leftinsetStart,horizontal position in start direction
    rightinsetEnd,horizontal position in end direction
  4. Add a way to switch between LTR and RTL

    For example, in Next.js, you can change the locale on the route by leveraging the built-in useRouter hook.

    src/components/lang-switcher.js
    function LangSwitcher() {
    const { locale, push, reload, pathname } = useRouter();
    const nextLocale = locale === 'en' ? 'ar' : 'en';

    const onClick = async () => {
    await push(pathname, { locale: nextLocale });
    };

    return <button onClick={onClick}>Change to {nextLocale}</button>;
    }

    export default LangSwitcher;

    Asides updating the style props you use in your application, we think this is the best approach. In the end it's up to your team to decide which approach to go with.

Additional resources