Material-Ui TextField not affected with the RTL direction

The documentation contains four steps for rtl support:

  1. Set the dir attribute on the body element.

In my examples below, this is handled by the following:

  React.useLayoutEffect(() => {
    document.body.setAttribute("dir", isRtl ? "rtl" : "ltr");
  }, [isRtl]);
  1. 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>
  1. Install the rtl plugin.

    • For v4 (using JSS), this means installing jss-rtl.
    • For v5 (using Emotion), this means installing stylis-plugin-rtl.
  2. 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>
  );
}

Edit rtl example

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>
  );
}

Edit rtl example

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 😉