Westy Design Systemv0.2.7
OverviewComponentsExamplesStorybook

Foundations

  • Typography
  • Colors
  • Spacing
  • Radius
  • Shadows
  • Icons

Components

  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Avatar
  • Badge
  • Breadcrumb
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Data Table
  • Date Picker
  • Dialog
  • Direction
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Menubar
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Questionnaire
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Sonner
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toggle
  • Toggle Group
  • Tooltip

Chat

  • Attachment
  • Bubble
  • Marker
  • Message
  • Message Scroller

Components

Direction

A provider component that sets the text direction for your application.

Playground

تسجيل الدخول إلى حسابك
أدخل بريدك الإلكتروني أدناه لتسجيل الدخول إلى حسابك

useDirection() → "rtl"

—lang
—direction
—provider

The dir attribute mirrors the layout; the provider is what Radix primitives — the Select here — read. Turn the provider off and the popup keeps aligning as if the app were LTR.

Usage
DirectionProvider renders no markup — it publishes "ltr" or "rtl" on a React context that every Radix primitive reads. Mount it once, high in the tree, alongside the dir attribute on <html>: the attribute drives CSS, the provider drives behavior.
import { DirectionProvider } from "@workspace/ui/components/direction"

<html dir="rtl">
  <body>
    <DirectionProvider dir="rtl">
      {/* Your app content */}
    </DirectionProvider>
  </body>
</html>
Provider vs. the dir attribute
The two are not interchangeable. A dir="rtl" wrapper flips the layout because the browser resolves logical properties from the DOM — but Radix never reads the attribute, so without the provider its primitives still behave as LTR: popups align from the wrong edge, and arrow keys move against the reading order. Both panels below carry the attribute; only the second one is wrapped in the provider. Open each Select to see the difference.

dir="rtl"

ltr

dir + provider

rtl
{/* layout mirrors, Radix context stays "ltr" */}
<div dir="rtl">
  <Select>{/* … */}</Select>
</div>

{/* layout mirrors and Radix context becomes "rtl" */}
<DirectionProvider dir="rtl">
  <div dir="rtl">
    <Select>{/* … */}</Select>
  </div>
</DirectionProvider>
Logical properties
The design system is authored RTL-first (rtl: true in components.json) — components use logical utilities rather than physical ones: ms-* / me-* for margins, ps-* / pe-* for padding, border-s / border-e for edges. They mirror on their own. Directional glyphs do not — flip those explicitly with the rtl: variant.

LTR

ms-auto

border-s-2 ps-3

RTL

ms-auto

border-s-2 ps-3

<div className="flex items-center">
  <p className="text-sm font-medium">Label</p>
  <Badge variant="outline" className="ms-auto font-mono">ms-auto</Badge>
</div>
<p className="border-s-2 ps-3 text-sm text-muted-foreground">border-s-2 ps-3</p>
<Button variant="outline" size="sm">
  Next
  <CaretRightIcon className="rtl:rotate-180" />
</Button>
Nested direction
Providers nest, so a subtree can opt back out. Content that is inherently left-to-right inside an RTL page — identifiers, code, IBANs, URLs — belongs in its own DirectionProvider dir="ltr" with a matching dir attribute, while its label keeps the surrounding direction.
<DirectionProvider dir="rtl">
  <div dir="rtl">
    <Label htmlFor="name">الاسم الكامل</Label>
    <Input id="name" placeholder="أدخل اسمك" />

    <DirectionProvider dir="ltr">
      <div dir="ltr">
        <Label htmlFor="account" dir="rtl">معرّف الحساب</Label>
        <Input id="account" className="font-mono" defaultValue="acct_4821-KX90" />
      </div>
    </DirectionProvider>
  </div>
</DirectionProvider>
useDirection
Read the current direction from your own components with useDirection(). It returns the nearest provider value, falling back to "ltr" when there is none — useful when the decision is not expressible in CSS, such as picking a caret or swapping a copy string. The first button below sits outside any provider, the second inside an RTL one.
import { useDirection } from "@workspace/ui/components/direction"

function NextButton() {
  const direction = useDirection()

  return (
    <Button variant="outline">
      {direction === "rtl" ? "التالي" : "Next"}
      <CaretRightIcon className={direction === "rtl" ? "rotate-180" : undefined} />
    </Button>
  )
}
Accessibility
The provider carries no semantics of its own — assistive technology reads direction from the DOM. Set dir on <html> together with lang, so screen readers pick the right voice and bidirectional text resolves correctly; the provider then keeps keyboard behavior aligned with what is on screen. Arrow keys in Tabs, Select, Menubar and the other roving-focus primitives follow the reading order only when the context says "rtl", so a page that mirrors visually but not in context is navigable backwards.
// app/layout.tsx — lang and dir belong on the document element
<html lang="ar" dir="rtl">
  <body>
    <DirectionProvider dir="rtl">{children}</DirectionProvider>
  </body>
</html>
API Reference
A thin wrapper over Radix's Direction.DirectionProvider. It renders no element and consumes no design token — the tokens listed below come from the components used in the examples on this page. The upstream docs show a direction prop; it exists as an alias and wins when both are set, but dir is the one the type requires, so pass dir.
ExportSignatureDefault
DirectionProviderdir: "ltr" | "rtl"required
DirectionProviderdirection?: "ltr" | "rtl"—
DirectionProviderchildren?: React.ReactNode—
useDirection(localDir?: "ltr" | "rtl") => "ltr" | "rtl""ltr"

Tokens used

8 design tokens consumed by the Direction examples.

  • --border

    Border

    borderborder-s-2
  • --card

    Card surface

    bg-card
  • --input

    Input border

    border-input
  • --ring

    Focus ring

    focus-visible:ring-ring/50
  • --primary

    Primary

    bg-primarytext-primary-foreground
  • --muted-foreground

    Muted foreground

    text-muted-foreground
  • --radius

    Radius

    rounded-lg
  • --spacing

    Spacing unit (4px)

    gap-2p-4ps-3