Direction

Reading direction for everything below it.

Sets the reading direction for everything below it.

Rows, start/end insets and horizontal padding mirror natively, so a right-to-left locale needs one wrapper rather than a mirrored copy of every screen.

The flip is the layout engine's own, which is why this is a component rather than a bare context — direction is a style, so a subtree is the unit it applies to. Unlike forcing the whole process right-to-left, it takes effect on the next frame, can be scoped to part of a screen, and can be nested: an identifier or a phone number can stay left-to-right inside a right-to-left page.

Installation

Direction ships with the library — no separate install.

import { Direction, useDirection, useDirectionSign, Item, ChevronRightIcon, BellIcon } from 'panelui-native';

Or copy the source into your project, to own and edit it:

npx panelui-cli@latest add direction

Usage

<Direction dir="rtl">
  <App />
</Direction>

Examples

At the root

Wrap the app once. The wrapper takes no layout of its own, so give it flex-1 here — it is standing in for the screen.

<PanelUIProvider>
  <Direction dir={locale.rtl ? 'rtl' : 'ltr'} className="flex-1">
    <App />
  </Direction>
</PanelUIProvider>

Around one section

Inside a screen it needs no size at all: the view is as big as the rows in it. Directional glyphs in the trailing slot mirror with the row.

<Direction dir="rtl" className="w-full">
  <Item.Group>
    <Item>
      <Item.Media variant="icon">
        <BellIcon size={16} />
      </Item.Media>
      <Item.Content>
        <Item.Title>الإشعارات</Item.Title>
        <Item.Description>Badges, sounds, banners</Item.Description>
      </Item.Content>
      <Item.Actions>
        <ChevronRightIcon size={16} />
      </Item.Actions>
    </Item>
  </Item.Group>
</Direction>

An island that must not flip

Nesting resets the direction for one subtree and nothing else. A phone number, an account ID or a code snippet reads the same way in every locale — mirroring it makes it wrong rather than localised.

<Direction dir="rtl">
  <Text>رقم الهاتف</Text>

  <Direction dir="ltr">
    <Text>+1 (555) 010-4477</Text>
  </Direction>
</Direction>

Flipping maths the layout cannot

Yoga mirrors boxes. It cannot mirror a number — a drag translation, an indicator offset, the direction a sweep travels — because none of those is a laid-out edge. useDirectionSign() is -1 in a right-to-left subtree and 1 otherwise, which is the form that maths actually wants.

function Scrubber({ onSeek }) {
  const sign = useDirectionSign();

  const pan = Gesture.Pan().onChange((event) => {
    onSeek((event.changeX * sign) / width);
  });

  return <GestureDetector gesture={pan}>…</GestureDetector>;
}

Versions

A whole screen, both ways

The parts Yoga cannot flip on its own, side by side: a slider’s drag, a switch’s thumb, a shimmer’s sweep, a chevron’s glyph and a paragraph’s alignment.

<Direction dir={dir} className="flex-1">
  <ScrollView contentContainerClassName="gap-4 p-4">
    <Item>
      <Item.Content>
        <Item.Title>Notifications</Item.Title>
      </Item.Content>
      <Item.Actions>
        <ChevronRightIcon size={16} />
      </Item.Actions>
    </Item>

    <Slider value={volume} onValueChange={setVolume} />
    <Switch value={sync} onValueChange={setSync} />
  </ScrollView>
</Direction>

API Reference

Direction

PropTypeDefaultDescription
classNamestring
dirDirectionValueReading direction for this subtree. Defaults to the nearest enclosing Direction, or to the device when there is none — so a nested provider with no dir inherits rather than resetting to left-to-right.

Every part also accepts the underlying React Native props (ViewProps or TextProps) and a className for Tailwind utilities.

Notes

What it mirrors for you

The wrapper carries Yoga’s direction, so every row, start/end inset and logical padding underneath it mirrors natively — that is the whole reason this is a component and not a bare context. direction is a style, and a subtree is the unit a style applies to.

It is also why this beats flipping the process with I18nManager.forceRTL, which needs an app restart, cannot be scoped to part of a screen, and cannot be previewed side by side. Here the value is a prop: change it and the next frame is mirrored.

What the library flips on top of that

Yoga only moves boxes. Everything below is a number or a glyph rather than a laid-out edge, and each one reads the direction itself:

  • Text alignment. React Native resolves a paragraph’s alignment from the process-wide I18nManager.isRTL, not from an ancestor’s Yoga direction — so without this an Arabic paragraph would mirror the furniture around it and then sit left-aligned inside it. Every Text in the library sets its writingDirection from the nearest Direction.
  • Chevrons and arrows. Yoga moves a chevron to the other end of its row but cannot turn the glyph around, which leaves an RTL list row pointing back at its own text. The glyphs whose meaning is a horizontal direction mirror — the two chevrons, the outward arrow and the send plane. A pencil, a magnifier or a play triangle does not, because those mean the same thing either way round, and the vertical arrows do not either, since the vertical axis has no direction to read. The mirror is written as a transform on every render rather than only in right-to-left, so an app that switches direction at runtime gets its arrows back when it switches away again.
  • Slider, Switch, Progress and Shimmer. A drag translation, a thumb’s travel, a loop and a sweep are all transforms, and a transform is not laid out. Each multiplies through the direction’s sign.

Anything of your own doing the same kind of maths needs useDirectionSign(), which is the whole of the escape hatch.

Inheritance

A nested Direction with no dir inherits rather than resetting to left-to-right, so wrapping a section in one to add a className does not silently unflip it. Pass dir explicitly for an island that must not flip — an identifier, a phone number, a code block — since those read the same way in every locale and mirroring them makes them wrong rather than localised.

It takes no layout of its own

The view is as big as what is inside it; className says otherwise. Wrapping a whole app therefore wants flex-1 explicitly. It used to carry flex-1 by default and every use inside a screen had to undo it with flex-none — a default that is wrong for one of its two uses is worse than none, because the wrong one fails silently by swallowing the rest of the screen.

What it does not do

It mirrors the furniture. The content is still yours to translate, and dates, numbers and currency still need formatting for the locale — a mirrored screen reading English is not a localised app.

Public exports

Values: Direction, useDirection, useDirectionSign

Types: DirectionProps, DirectionValue

On this page