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
CacheProvider
from 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
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 adir
attribute with a value ofrtl
orltr
. 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 DocumentAdd a way to switch between LTR and RTL
For example, in
Next.js
, you can change thelocale
on the route by leveraging the built-inuseRouter
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 themeYou can use the
extendTheme
function or any other preferred approach to adddirection
key 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
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 theme
const 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 DocumentReplace 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 thesx
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 left
insetStart
,horizontal position in start direction right
insetEnd
,horizontal position in end direction - Replace
Add a way to switch between LTR and RTL
For example, in
Next.js
, you can change thelocale
on the route by leveraging the built-inuseRouter
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.