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.