How to support right-to-left (RTL) languages in Next.js (Pages Router) v16

Mirror layouts for Arabic and Hebrew

Problem

Most web layouts assume left-to-right text flow. Navigation menus anchor to the left, content reads from left to right, and spacing is applied with properties like margin-left or padding-right. When an application is translated into Arabic or Hebrew, these languages read from right to left, and the entire visual layout should mirror to match. Without mirroring, users experience a disorienting interface where the visual hierarchy contradicts their natural reading direction, making navigation confusing and the application feel unpolished.

The challenge extends beyond text alignment. Physical CSS properties like left, right, margin-left, and padding-right are tied to fixed screen positions rather than content flow. When the text direction changes, these properties remain anchored to the same physical edges, preventing the layout from adapting naturally.

Solution

Set the document's text direction attribute to match the current locale, allowing browsers to automatically reverse layout flow for RTL languages. Replace physical CSS properties with logical properties that reference content flow rather than screen position. Logical properties like margin-inline-start and padding-inline-end automatically map to the correct physical edge based on text direction, enabling layouts to mirror themselves without additional code or conditional styling.

This approach creates direction-agnostic layouts that work correctly for both LTR and RTL languages without duplicating styles or adding complex logic.

Steps

1. Create a helper to determine text direction from locale

Logical properties adapt based on text direction, with margin-inline-start equivalent to margin-left in LTR contexts and margin-right in RTL contexts. Build a utility function that maps locales to their text direction.

export function getDirection(locale: string): "ltr" | "rtl" {
  const rtlLocales = ["ar", "he", "fa", "ur"];
  return rtlLocales.includes(locale) ? "rtl" : "ltr";
}

This function identifies RTL languages and returns the appropriate direction value for use in HTML and CSS.

2. Set the document direction attribute

Custom Document classes can override the render method to customize the HTML element with attributes like lang and dir. Create a custom Document to set the dir attribute on the root HTML element based on the current locale.

import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from "next/document";
import { getDirection } from "../utils/direction";

class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);
    return initialProps;
  }

  render() {
    const locale = this.props.__NEXT_DATA__.locale || "en";
    const dir = getDirection(locale);

    return (
      <Html lang={locale} dir={dir}>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

The Next.js router provides the currently active locale via the locale property. The document reads the locale from Next.js routing data and applies the corresponding direction to the HTML element, enabling browser-level RTL support.

3. Replace physical CSS properties with logical equivalents

Logical properties like margin-inline-start, padding-inline-start, and their shorthand forms margin-inline and padding-inline automatically map to physical properties based on writing mode and direction. Update your component styles to use flow-relative properties.

export default function Navigation() {
  return (
    <nav className="nav">
      <ul className="nav-list">
        <li className="nav-item">Home</li>
        <li className="nav-item">About</li>
        <li className="nav-item">Contact</li>
      </ul>
    </nav>
  );
}
.nav {
  padding-inline: 1rem;
  border-inline-start: 4px solid blue;
}

.nav-list {
  display: flex;
  gap: 1rem;
  margin-block: 0;
  padding-inline-start: 0;
}

.nav-item {
  margin-inline-end: 1.5rem;
}

Logical properties for margin, padding and inset make positioning elements easier and more efficient across writing modes. These properties automatically reverse in RTL contexts without additional styles or media queries.

4. Use logical properties for positioning

For absolutely or relatively positioned elements, replace left and right with inset-inline-start and inset-inline-end.

export default function Sidebar() {
  return (
    <aside className="sidebar">
      <button className="close-button">×</button>
      <p>Sidebar content</p>
    </aside>
  );
}
.sidebar {
  position: fixed;
  inset-inline-start: 0;
  inset-block-start: 0;
  inline-size: 250px;
  block-size: 100vh;
  padding-inline: 1rem;
}

.close-button {
  position: absolute;
  inset-inline-end: 0.5rem;
  inset-block-start: 0.5rem;
}

The width property is replaced by inline-size and height by block-size in logical property methodology. These properties ensure positioned elements appear on the correct side for the current text direction.

5. Apply logical text alignment

Replace text-align: left and text-align: right with text-align: start and text-align: end.

.content {
  text-align: start;
}

.metadata {
  text-align: end;
  margin-inline-start: auto;
}

Logical properties refer to edges of a box as they relate to the flow of content rather than physical viewport dimensions. Text alignment values of start and end automatically adapt to text direction, aligning content appropriately for both LTR and RTL languages.