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.
Install the stylis plugin and emotion's cache
npm i stylis stylis-plugin-rtl @emotion/cache
# or
yarn add stylis stylis-plugin-rtl @emotion/cacheCreate the RTL provider using
CacheProviderfrom emotionsrc/components/rtl-provider.jsimport { 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} />;
}Add RTL provider to the application's root
pages/_app.jsimport { 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;Add the
dirandlangattributes to thehtmltag.You'll need to make a few changes to your markup. In the
<html>tag, you'll need to add adirattribute with a value ofrtlorltr. 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.jsfile and using this API:pages/_document.jsimport 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 DocumentAdd a way to switch between LTR and RTL
For example, in
Next.js, you can change thelocaleon the route by leveraging the built-inuseRouterhook.src/components/lang-switcher.jsfunction 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,Tooltipto match RTL. - The need to install extra packages like
stylis,stylis-plugin-rtlmight 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:
Add a
directionkey to the themeYou can use the
extendThemefunction or any other preferred approach to adddirectionkey to the theme. Then, add the custom theme toChakraProvider.noteDue to the fact that some CSS logical properties aren't supported in all browsers, we flip the styles based on the
directionas a temporary polyfill.src/components/chakra-rtl-provider.jsfunction 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>;
}Add the
dirandlangattributes to thehtmltag.In Next.js, you can achieve this by adding a
pages/_document.jsfile and using this API:pages/_document.jsimport 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 DocumentReplace style props with their RTL-aware equivalent
To get our internal RTL system working, you need to replace all physical
*-leftor*-rightstyles (passed as props or in thesxprop) to their bi-directional versions (*-startor*-end).For example:
- Replace
paddingLeftorplprop withpaddingStartorps - Replace
marginRightormrprop withmarginEndorme - Replace
borderLeftRadiuswithborderStartRadius
Here's a list of the RTL-aware style props you can use alongside other CSS logical properties:
Style prop Replace with Description 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 - Replace
Add a way to switch between LTR and RTL
For example, in
Next.js, you can change thelocaleon the route by leveraging the built-inuseRouterhook.src/components/lang-switcher.jsfunction 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.