Skip to content

Foundations

Internationalisation

Locales, right-to-left layouts, dates, times, numbers and calendars, all handled by React Aria's I18nProvider and @internationalized/date.

React Aria formats and parses dates, times and numbers for the user's locale, mirrors keyboard navigation in right-to-left languages and ships translated strings for its built-in labels (such as "Next month" or "Clear search") in 30+ languages.

Setting the locale

By default React Aria uses the browser's language. To set it explicitly, for example from your router or user settings, wrap the app in I18nProvider:

app/[lang]/layout.tsxtsx
import { I18nProvider } from "react-aria-components";

export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ lang: string }>;
}) {
  const { lang } = await params;
  return (
    <html lang={lang} dir={rtlLocales.has(lang.split("-")[0]) ? "rtl" : "ltr"}>
      <body>
        <I18nProvider locale={lang}>{children}</I18nProvider>
      </body>
    </html>
  );
}

const rtlLocales = new Set(["ar", "fa", "he", "ur"]);

I18nProvider is a client component; using it in a server layout is fine because its children stay server-rendered.

Server rendering

On the server there's no browser language, so React Aria falls back to en-US. Set I18nProvider from the request (route segment, cookie or Accept-Language) to avoid a hydration mismatch in date and number output.

You can also nest providers to localise one part of a page:

tsx
<I18nProvider locale="de-DE">
  <DatePicker label="Lieferdatum" />
</I18nProvider>

Right-to-left

Set dir="rtl" on <html> (as above) so the browser lays out text right to left. React Aria reads the locale's direction and mirrors keyboard behaviour: in Arabic or Hebrew, → moves to the previous item in tabs, radio groups and calendars, and sliders fill from the right.

Tailwind handles layout through logical utilities. Some component spacing uses physical classes (pl-*, mr-*), so check RTL screens and swap them for logical equivalents in your copy where needed:

PhysicalLogical
ml-2 / mr-2ms-2 / me-2
pl-3 / pr-3ps-3 / pe-3
left-0 / right-0start-0 / end-0
text-lefttext-start
rounded-l-mdrounded-s-md

Use the rtl: variant for anything that should flip visually, such as directional icons: <ChevronRightIcon className="rtl:rotate-180" />.

Dates and times

Date components use @internationalized/date values instead of JavaScript Date, so a date is a calendar date with no hidden time zone.

Terminal
npm install @internationalized/date
tsx
"use client";

import { getLocalTimeZone, parseDate, today } from "@internationalized/date";
import { useState } from "react";
import { DatePicker } from "@/components/ui/date-picker";

export function DueDate() {
  const [value, setValue] = useState(parseDate("2026-11-02"));
  return (
    <DatePicker
      label="Due date"
      value={value}
      onChange={(v) => v && setValue(v)}
      minValue={today(getLocalTimeZone())}
    />
  );
}
Value typeCreate it withUse for
CalendarDateparseDate("2026-11-02"), today(tz)Birthdays, due dates
CalendarDateTimeparseDateTime("2026-11-02T09:30")Local appointments
ZonedDateTimeparseZonedDateTime("2026-11-02T09:30[Europe/Berlin]"), now(tz)Events across time zones
TimeparseTime("09:30")Time Field

The field adapts to the locale: segment order (dd.mm.yyyy in German, yyyy/mm/dd in Japanese), 12- or 24-hour time and the first day of the week. Override the week start with firstDayOfWeek="mon".

Convert at the edges of your app: value.toString() gives an ISO string for your API, and value.toDate(getLocalTimeZone()) gives a Date when a library needs one.

Other calendars

Add a Unicode calendar extension to the locale and the Calendar, Date Field and Date Picker render in that system while your value stays convertible:

tsx
<I18nProvider locale="th-TH-u-ca-buddhist">
  <Calendar aria-label="Date" />
</I18nProvider>

Try the Arabic, Japanese imperial and Thai Buddhist locales in the Calendar locales example.

Numbers

Number Field formats with Intl.NumberFormat options and parses what users type in their locale, including the decimal separator:

tsx
<NumberField label="Price" defaultValue={1299.5}
  formatOptions={{ style: "currency", currency: "EUR" }} />

<NumberField label="Discount" defaultValue={0.15}
  formatOptions={{ style: "percent" }} />

<NumberField label="Weight" defaultValue={12}
  formatOptions={{ style: "unit", unit: "kilogram" }} />

In de-DE the first one reads "1.299,50 €"; in en-US, "€1,299.50".

For display-only numbers and dates, use React Aria's formatter hooks so output matches the provider's locale:

tsx
"use client";

import { useDateFormatter, useNumberFormatter } from "react-aria";

export function Total({ amount, paidAt }: { amount: number; paidAt: Date }) {
  const money = useNumberFormatter({ style: "currency", currency: "USD" });
  const date = useDateFormatter({ dateStyle: "medium" });
  return <p>{money.format(amount)} paid on {date.format(paidAt)}</p>;
}

react-aria is a separate package from react-aria-components; install it if you use these hooks.

Your own strings

React Aria translates its built-in strings only. Your labels, placeholders and messages come from your app, so pass them through your i18n library (next-intl, react-i18next, Lingui) like any other text:

tsx
<TextField label={t("billing.email")} errorMessage={t("errors.email")} />