RTL Support
v1.1+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# oryarn add stylis stylis-plugin-rtl @emotion/cache
- Create the RTL provider using
CacheProvider
from emotion
// src/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 } = propsreturn (<ChakraProvider><RtlProvider><Component {...pageProps} /></RtlProvider></ChakraProvider>)}export default App
- Add the
dir
andlang
attributes to thehtml
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.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 Document
- 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.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
,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:
- 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
.
Due to the fact the 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.jsfunction ChakraRTLProvider({ children }) {const { locale } = useRouter()const direction = locale === "ar" ? "rtl" : "ltr"// 👇🏻 Here's the place we add direction to the themeconst theme = extendTheme({ direction })return <ChakraProvider theme={theme}>{children}</ChakraProvider>}
- Add the
dir
andlang
attributes to thehtml
tag.
In Next.js, you can achieve this by adding a pages/_document.js
file 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 Document
- 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
orpl
prop withpaddingStart
orps
- Replace
marginRight
ormr
prop withmarginEnd
orme
- Replace
borderLeftRadius
withborderStartRadius
Here's a list of the RTL-aware style props you can use alongside other CSS logical properties:
Style prop | Replace with | Description |
---|---|---|
paddingLeft , pl | paddingStart , ps | padding in start direction |
paddingRight , pr | paddingEnd , pe | padding in end direction |
marginLeft , ml | marginStart , ms | margin in start direction |
marginRight , mr | marginEnd , me | margin in end direction |
roundedLeft , borderLeftRadius | roundedStart , borderStartRadius | rounded borders in start direction |
roundedRight , borderRightRadius | roundedEnd , borderEndRadius | rounded borders in end direction |
roundedTopLeft , borderTopLeftRadius | roundedTopStart , borderTopStartRadius | rounded borders in top start direction |
roundedTopRight , borderTopRightRadius | roundedTopEnd , borderTopEndRadius | rounded borders in top end direction |
roundedBottomLeft , borderBottomLeftRadius | roundedBottomStart , borderBottomStartRadius | rounded borders in bottom start direction |
roundedBottomRight , borderBottomRightRadius | roundedBottomEnd , borderBottomEndRadius | rounded borders in bottom end direction |
borderLeft | borderStart , borderInlineStart | border width in start direction |
borderRight | borderEnd , borderInlineEnd | border width in end direction |
- 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.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.
Additional resources#
If you'd like to share your RTL setup with the community, feel free to add a PR that updates this section.