Material-Ui TextField not affected with the RTL direction
The documentation contains four steps for rtl support:
- Set the
dir
attribute on thebody
element.
In my examples below, this is handled by the following:
React.useLayoutEffect(() => {
document.body.setAttribute("dir", isRtl ? "rtl" : "ltr");
}, [isRtl]);
- Set the
direction
in the theme.
In my examples below, I am toggling between two themes:
const ltrTheme = createTheme({ direction: "ltr" });
const rtlTheme = createTheme({ direction: "rtl" });
...
<ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}>
...
</ThemeProvider>
-
Install the rtl plugin.
- For v4 (using JSS), this means installing
jss-rtl
. - For v5 (using Emotion), this means installing
stylis-plugin-rtl
.
- For v4 (using JSS), this means installing
-
Load the rtl plugin.
Below is a v4 example showing how to load the rtl plugin for JSS (v5 example further down).
For performance reasons it is important to avoid re-rendering StylesProvider
, so this should not be in a component with state that can change and therefore trigger a re-render. In my own app, I have the StylesProvider
element in my index.js
file as the first element inside the call to react-dom render
.
import rtl from "jss-rtl";
import {
StylesProvider,
jssPreset
} from "@material-ui/core/styles";
// Configure JSS
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
export default function App() {
return (
<StylesProvider jss={jss}>
<AppContent />
</StylesProvider>
);
}
The example below includes a TextField
and you can see that the label's position toggles correctly.
import React from "react";
import { create } from "jss";
import rtl from "jss-rtl";
import {
StylesProvider,
jssPreset,
ThemeProvider,
createTheme
} from "@material-ui/core/styles";
import CssBaseline from "@material-ui/core/CssBaseline";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import Box from "@material-ui/core/Box";
// Configure JSS
const jss = create({ plugins: [...jssPreset().plugins, rtl()] });
const ltrTheme = createTheme({ direction: "ltr" });
const rtlTheme = createTheme({ direction: "rtl" });
function AppContent() {
const [isRtl, setIsRtl] = React.useState(false);
const [value, setValue] = React.useState("initial value");
React.useLayoutEffect(() => {
document.body.setAttribute("dir", isRtl ? "rtl" : "ltr");
}, [isRtl]);
return (
<ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}>
<CssBaseline />
<Box m={2}>
<TextField
variant="outlined"
value={value}
onChange={(event) => setValue(event.target.value)}
label={isRtl ? "بريد الكتروني او هاتف" : "Email or Phone"}
/>
<br />
<br />
Current Direction: {isRtl ? "rtl" : "ltr"}
<br />
<Button onClick={() => setIsRtl(!isRtl)}>Toggle direction</Button>
</Box>
</ThemeProvider>
);
}
export default function App() {
return (
<StylesProvider jss={jss}>
<AppContent />
</StylesProvider>
);
}
Below is an equivalent example for v5 using Emotion.
import React from "react";
import rtlPlugin from "stylis-plugin-rtl";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import { prefixer } from "stylis";
const cacheLtr = createCache({
key: "muiltr"
});
const cacheRtl = createCache({
key: "muirtl",
// prefixer is the only stylis plugin by default, so when
// overriding the plugins you need to include it explicitly
// if you want to retain the auto-prefixing behavior.
stylisPlugins: [prefixer, rtlPlugin]
});
const ltrTheme = createTheme({ direction: "ltr" });
const rtlTheme = createTheme({ direction: "rtl" });
export default function App() {
const [isRtl, setIsRtl] = React.useState(false);
const [value, setValue] = React.useState("initial value");
React.useLayoutEffect(() => {
document.body.setAttribute("dir", isRtl ? "rtl" : "ltr");
}, [isRtl]);
return (
<CacheProvider value={isRtl ? cacheRtl : cacheLtr}>
<ThemeProvider theme={isRtl ? rtlTheme : ltrTheme}>
<CssBaseline />
<Box m={2}>
<TextField
value={value}
onChange={(event) => setValue(event.target.value)}
label={isRtl ? "بريد الكتروني او هاتف" : "Email or Phone"}
/>
<br />
<br />
Current Direction: {isRtl ? "rtl" : "ltr"}
<br />
<Button onClick={() => setIsRtl(!isRtl)}>Toggle direction</Button>
</Box>
</ThemeProvider>
</CacheProvider>
);
}
In addition, I have a later answer that discusses flipping icons: material-ui icons won't flip when I change to RTL.
Another solution I've found to set this for specific component is to add it via native JS after component is rendered.
First I created a ref to the input element:
const inputRef = createRef()
<TextField inputRef={inputRef} />
Then added a useEffect hook to perform once on each render:
useEffect(() => {
if(inputRef)
inputRef.current.dir = 'auto'
}, [])
Not the most beautiful code, but it sure works 😉