# RTL & Hebrew UI Layouts

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 81  
**Tier:** All tiers  
**Depends on:** `foundation-design-system`, `app-shell`, `foundation-auth-rbac`  
**Referenced by:** `foundation-design-system`, `foundation-app-shell`

---

## Overview

Full right-to-left layout support for the Hebrew locale. Zync is a product for Israeli businesses — Hebrew is the primary language for most tenants. This spec defines: `dir` attribute strategy, CSS logical-property requirements, component-level RTL rules, icon mirroring, and the locale-to-direction binding.

---

## Direction Strategy

`dir` attribute is set on `<html>` based on user locale preference (stored in `user_preferences.locale`):

| Locale | `dir` |
|--------|-------|
| `he-IL` | `rtl` |
| `en-US`, `en-IL` | `ltr` |
| System default (IL tenant) | `rtl` |

Implementation: `apps/zync-app/src/root.tsx` reads locale from session claims and sets `document.documentElement.dir`. Same logic in `zync-www` Astro pages.

---

## CSS Logical Properties

All layout CSS **must** use logical properties. Physical properties are banned (ESLint rule `prefer-logical-properties`).

| Banned | Required instead |
|--------|-----------------|
| `margin-left` / `margin-right` | `margin-inline-start` / `margin-inline-end` |
| `padding-left` / `padding-right` | `padding-inline-start` / `padding-inline-end` |
| `border-left` / `border-right` | `border-inline-start` / `border-inline-end` |
| `left: X` / `right: X` (positioned) | `inset-inline-start: X` / `inset-inline-end: X` |
| `text-align: left` | `text-align: start` |
| `text-align: right` | `text-align: end` (except for numeric columns) |
| `float: left` | `float: inline-start` |

**Exception:** numeric columns (ILS amounts, percentages, counts) always `text-align: end` regardless of direction — numbers read left-to-right in RTL Hebrew documents.

---

## Tailwind Configuration

Tailwind `v4` logical utilities are preferred. Custom plugin for any missing logical utilities:

```ts
// tailwind.config.ts
plugins: [
  plugin(({ addUtilities }) => {
    addUtilities({
      '.ms-auto': { 'margin-inline-start': 'auto' },
      '.me-auto': { 'margin-inline-end': 'auto' },
      '.ps-4':    { 'padding-inline-start': '1rem' },
      '.pe-4':    { 'padding-inline-end': '1rem' },
      // ... full set
    })
  })
]
```

---

## Sidebar

In RTL, the sidebar is on the **right** side:

```
LTR:  [Sidebar | Main content]
RTL:  [Main content | Sidebar]
```

Sidebar uses `inset-inline-start: 0` (not `left: 0`). The collapsible sidebar button icon mirrors (chevron-right ↔ chevron-left).

---

## Icon Mirroring

Icons with directional meaning mirror in RTL:

| Icon | Mirror in RTL? |
|------|---------------|
| Arrow right / left | Yes |
| Chevron right / left | Yes |
| Back button arrow | Yes |
| Send / forward | Yes |
| Menu hamburger | No |
| Notification bell | No |
| Search magnifier | No |
| Calendar | No |
| Currency shekel ₪ | No |

Implementation: CSS `[dir="rtl"] .mirror-rtl { transform: scaleX(-1); }` — apply `mirror-rtl` class to mirrored icons only.

---

## Form Fields

RTL fields: `text-align: start`. Input icons (clear button, eye toggle on password) are positioned at `inset-inline-end`.

Date pickers, phone inputs with country flag: use component-level `dir` from parent `<html dir>`.

### Numeric Input Fields

All inputs for amounts (ILS), percentages, hours, invoice numbers, phone numbers, and tax IDs (ח.פ. / ע.מ.) must carry `dir="ltr"` explicitly — regardless of document direction:

```tsx
// Correct: numeric input in RTL context
<input type="text" dir="ltr" inputMode="decimal" />
// inputMode="decimal" → numeric keyboard on mobile
// Avoid type="number": suppresses arrow-key navigation in some browsers
```

- Label: document direction (RTL for Hebrew)
- Placeholder text: always LTR (`"0.00"`, `"1234567890"`)
- Helper text / error message: document direction (RTL)

---

## Typography

Hebrew text uses `font-family: 'Rubik', sans-serif` (Rubik supports Hebrew glyphs). Latin text mixed inline uses the same Rubik font (it includes Latin subset).

Line heights for Hebrew: `--leading-relaxed: 1.625` (same as Latin — Hebrew descenders are minimal).

No font stack changes needed; Rubik is already in the design system font stack (foundation-design-system.md).

### Mixed-Language Inline Content

Entity names from the database (customer name, vendor name, project name) are user-generated and may be Hebrew or English regardless of UI locale. Screen readers pronounce text incorrectly without the correct `lang` attribute.

```tsx
function isHebrew(str: string): boolean {
  const heChars = str.match(/[֐-׿יִ-﷿]/g)?.length ?? 0
  return heChars / str.length > 0.5
}

// Wrap user-generated string fields where language is uncertain:
<span dir="auto" lang={isHebrew(name) ? 'he' : 'en'}>{name}</span>
```

- `dir="auto"`: browser detects direction from the first strong character — use even when `lang` is uncertain
- Do NOT apply to: fixed strings (status labels, category names, formatted dates) — these are always translated and carry the document language
- Do NOT apply to: structured data (invoice numbers, tax IDs, amounts) — use `dir="ltr"` explicitly (see Numeric Input Fields above)

---

## Number & Currency Formatting

```ts
const fmt = new Intl.NumberFormat(locale, {
  style: 'currency',
  currency: 'ILS',
  minimumFractionDigits: 0,
})
// he-IL: ₪12,500 (same glyph order as en-US with Intl — LTR digits in RTL context)
```

Dates: `new Intl.DateTimeFormat(locale)`. Hebrew calendar displayed only when explicitly requested (spec 117 handles Hebrew calendar dates).

---

## Component RTL Checklist

Before shipping any new UI component:

- [ ] No `margin-left`/`right`, `padding-left`/`right`, `left`/`right` in source
- [ ] Flex row items: `gap` (not margin between) so RTL flip is automatic
- [ ] Absolute-positioned overlays use `inset-inline-*`
- [ ] Directional icons have `mirror-rtl` class
- [ ] Number columns use `text-align: end` (never `text-align: right`)
- [ ] Tested with `<html dir="rtl">` in Storybook/browser dev tools

---

## App Shell Changes

`apps/zync-app/src/root.tsx`:
```tsx
<html lang={locale} dir={locale === 'he-IL' ? 'rtl' : 'ltr'}>
```

`zync-www` Astro layout:
```astro
<html lang={locale} dir={isHebrew ? 'rtl' : 'ltr'}>
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Logical properties (not `[dir="rtl"]` overrides) | Not direction-conditional CSS | Logical properties are the CSS standard; `[dir="rtl"]` overrides require duplicating every layout rule |
| `dir` on `<html>` | Not per-component | Browser automatic mirroring extends to scrollbars, form controls, focus rings; per-component is incomplete |
| Rubik for Hebrew | Not a separate Hebrew font | Rubik includes Latin + Hebrew subsets; no font-stack switching needed |
| Numbers always LTR-aligned | Not mirrored | Hebrew documents (contracts, invoices) keep numbers in LTR reading order per typography conventions |
