# RTL & internationalization

> How Apogee components mirror automatically under a right-to-left ancestor, using logical CSS properties.

URL: https://apogee.avaui.dev/docs/rtl
Last updated: 2026-10-15

How Apogee components mirror automatically under a right-to-left ancestor, using logical CSS properties.

Last updated October 15, 2026

Apogee components are **direction-agnostic**. They’re built from CSS logical properties, so they mirror automatically under a right-to-left ancestor — no per-component RTL prop, no duplicate styles to maintain.

## Turn it on

Set `dir` on the document, or any subtree:

```html
<html lang="ar" dir="rtl">
```

Everything inside mirrors. A single LTR island inside an RTL page — a code snippet, a phone number — can opt back out with `dir="ltr"`.

## Logical properties, never physical

Components use logical utilities exclusively:

| Logical | Physical (never used) | Purpose |
| --- | --- | --- |
| `ms-*` / `me-*` | `ml-*` / `mr-*` | margin start/end |
| `ps-*` / `pe-*` | `pl-*` / `pr-*` | padding start/end |
| `start-*` / `end-*` | `left-*` / `right-*` | inset start/end |
| `text-start` / `text-end` | `text-left` / `text-right` | text alignment |
| `border-s` / `border-e` | `border-l` / `border-r` | border start/end |
| `rounded-s` / `rounded-e` | `rounded-l` / `rounded-r` | corner radius start/end |

Scoped `<style>` blocks follow the same rule with their CSS equivalents — `inset-inline-start`, `padding-inline`, `margin-inline`, `border-inline-start`.

`left-1/2` (centering) is direction-neutral and stays as-is — centering isn’t a directional value.

## Directional icons flip

Chevrons and arrows that imply direction carry `rtl:-scale-x-100` so they still point the right way. `Breadcrumbs`’ separator does this internally — nothing for you to add:

```astro
<ChevronRight class="size-3.5 shrink-0 rtl:-scale-x-100" aria-hidden="true" />
```

The same pattern applies to any directional icon you pass in yourself — for example a leading “continue” arrow on a `Button`.

Both mirror under `dir="rtl"` with no extra prop — try selecting text or tabbing through it:

```astro
<div dir="rtl">
<Breadcrumbs items={[
  { label: 'Home', href: '#' },
  { label: 'Products', href: '#' },
  { label: 'Item' },
]} />
<Button><ArrowRight class="rtl:-scale-x-100" /> Continue</Button>
</div>
```

## Verify on the playground

The full smoke test lives at the playground’s [`/rtl`](https://apogee.avaui.dev/rtl) page — a real `dir="rtl"` document (`lang="ar"`) with Arabic content running Button, Input, Select, Switch, Alert, Dropdown, Blockquote, Steps, Table, and Pagination together, so layout, icon flips, and text alignment can all be checked mirrored at once.

## Content

-   The library ships no user-facing strings — every label, option, and message is passed in by you, so localization is just passing translated content. Keep `aria-label`s translatable too.
-   Format numbers and dates with `Intl` for the locale; `tabular-nums` still keeps digits aligned.

## Known limitations

-   **Carousel** reads the track’s computed `direction` and flips the scroll sign under RTL, so Next always moves toward the end edge. Older engines that report a positive `scrollLeft` in RTL may still disagree; test on your targets.
-   **Dialog `variant="panel"`** takes a logical `position` (`start`/`end`), so an `end` drawer docks to the right in LTR and the left in RTL, sliding in from that edge.
-   **Popover** anchor positioning uses the physical `anchor(left)` CSS function (Chromium-only); it falls back to centering elsewhere. Revisit once logical anchor positioning reaches Baseline.
-   **Toaster** corners are logical (`top-start`, `bottom-end`, …) and mirror with direction.
