Documentation
RTL support

RTL support

If you want to support the dir attribute in your <html> tag, you'll need to add some logic:

From the root layout

// app/[locale]/layout.tsx
 
// If you are using Next.js < 15, you don't need to await `params`:
// export default function Layout({ children, params: { locale } }: { children: ReactElement, params: { locale: string } }) {
export default function Layout({ children, params }: { children: ReactElement, params: Promise<{ locale: string }> }) {
  const { locale } = await params
  const dir = new Intl.Locale(locale).getTextInfo().direction
 
  return (
    <html lang={locale} dir={dir}>
      <body>
        {children}
      </body>
    </html>
  )
}

Caveat

Note that this won't work in Firefox if your root layout is a Client Component. This is because the Intl.Locale.prototype.getTextInfo API is not yet supported in Firefox (opens in a new tab).

To prevent this, you can add a polyfill to guarantee compatibility with all browsers, until this standard is fully adopted. First, install the intl-locale-textinfo-polyfill package:

pnpm install intl-locale-textinfo-polyfill

Then, use it instead of the native Intl.Locale.prototype.getTextInfo API:

// app/[locale]/layout.tsx
import Locale from 'intl-locale-textinfo-polyfill'
 
// If you are using Next.js < 15, you don't need to await `params`:
// export default function Layout({ children, params: { locale } }: { children: ReactElement, params: { locale: string } }) {
export default function Layout({ children, params }: { children: ReactElement, params: Promise<{ locale: string }> }) {
  const { locale } = await params
  const { direction: dir } = new Locale(locale).textInfo
 
  return (
    <html lang={locale} dir={dir}>
      <body>
        {children}
      </body>
    </html>
  )
}

With a useEffect call

You may implement your RTL support with a useEffect as well. For instance, if you have a language switcher component on all your pages, you could also choose to write the language direction detection logic in it:

// LanguageSwitcher.tsx
'use client'
 
import { useChangeLocale, useCurrentLocale } from '../../locales/client'
import { FunctionComponent, useEffect } from 'react'
 
export default function LanguageSwitcher() {
  const currentLocale = useCurrentLocale()
  const changeLocale = useChangeLocale()
 
  useEffect(() => {
    document.documentElement.dir = isLocaleRTL(currentLocale) ? 'rtl' : 'ltr'
  }, [currentLocale])
 
  return (
    <div className="flex flex-col gap-4">
      <button onClick={() => changeLocale('en')}>EN</Button>
      <button onClick={() => changeLocale('fr')}>FR</Button>
    </div>
  )
}

Where isLocaleRTL could be a call to a librarie like rtl-detect (opens in a new tab), or the implementation mentioned just above, based on Intl.Locale.prototype.getTextInfo, including the polyfill currently required to ensure compatibility with Firefox-based browsers.

Caveat

Note that this choice of implementation will cause an UI flickering when your user loads your web pages for the first time: the page will first be mounted with the default dir attribute value of your HTML node, then updates when the component including the useEffect is mounted. This will introduce CLS (Cumulative Layout Shift) issues.


MIT 2024 © next-international contributors.