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

Last updated

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 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:

<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:

Show code
<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 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-labels 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.