diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..8c27274
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: Nadav011
+custom: ["https://mcpize.com/mcp/rtl-fixer"]
diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml
new file mode 100644
index 0000000..5b3fa48
--- /dev/null
+++ b/.github/workflows/security-scan.yml
@@ -0,0 +1,28 @@
+name: Security Scan
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+concurrency:
+ group: security-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ semgrep:
+ runs-on: [self-hosted, linux, x64, pop-os]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Semgrep SAST
+ run: |
+ pip3 install --user semgrep 2>/dev/null || true
+ semgrep scan . --config=auto --error --severity ERROR
+
+ trivy:
+ runs-on: [self-hosted, linux, x64, pop-os]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Trivy vulnerability scan
+ run: trivy fs . --severity HIGH,CRITICAL --exit-code 1
diff --git a/CI/rtl-check.yml b/CI/rtl-check.yml
new file mode 100644
index 0000000..1bf1d67
--- /dev/null
+++ b/CI/rtl-check.yml
@@ -0,0 +1,174 @@
+name: RTL Check
+
+on:
+ pull_request:
+ paths:
+ - "**/*.tsx"
+ - "**/*.jsx"
+ - "**/*.css"
+ - "**/*.vue"
+
+jobs:
+ rtl-lint:
+ name: RTL Logical Properties Check
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install ripgrep
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install -y ripgrep
+
+ - name: Scan for physical direction classes
+ id: rtl-scan
+ run: |
+ # Pattern: physical direction classes that must be replaced with logical equivalents
+ # Suppression: any line containing "// rtl-ok" is skipped
+ #
+ # Classes checked:
+ # ml-{n} mr-{n} pl-{n} pr-{n} — physical margin/padding
+ # text-left text-right — physical text alignment
+ # border-l- border-r- — physical border sides
+ # rounded-tl rounded-tr — physical border radius (corners)
+ # rounded-bl rounded-br
+ # float-left float-right — physical float
+ # left-{n} right-{n} — physical inset positioning (when directional)
+
+ PATTERN='\bml-[0-9a-z\[]|\bmr-[0-9a-z\[]|\bpl-[0-9a-z\[]|\bpr-[0-9a-z\[]|text-left\b|text-right\b|border-l-|border-r-|\brounded-tl\b|\brounded-tr\b|\brounded-bl\b|\brounded-br\b|float-left\b|float-right\b'
+
+ # Run ripgrep:
+ # - search only changed files in the PR (fallback: all source files)
+ # - exclude lines containing "rtl-ok" suppression comment
+ # - show file, line number, and matching content
+ VIOLATIONS=$(rg --glob "*.tsx" --glob "*.jsx" --glob "*.css" --glob "*.vue" \
+ --line-number \
+ --no-heading \
+ --color never \
+ "$PATTERN" \
+ src/ components/ pages/ app/ styles/ 2>/dev/null \
+ | grep -v "rtl-ok" \
+ | grep -v "//.*rtl-ok" \
+ || true)
+
+ if [ -z "$VIOLATIONS" ]; then
+ echo "violations_found=false" >> "$GITHUB_OUTPUT"
+ echo "violation_count=0" >> "$GITHUB_OUTPUT"
+ else
+ COUNT=$(echo "$VIOLATIONS" | wc -l | tr -d ' ')
+ echo "violations_found=true" >> "$GITHUB_OUTPUT"
+ echo "violation_count=$COUNT" >> "$GITHUB_OUTPUT"
+ # Store violations in a file for use in later steps
+ echo "$VIOLATIONS" > /tmp/rtl-violations.txt
+ fi
+
+ - name: Generate violation report
+ if: steps.rtl-scan.outputs.violations_found == 'true'
+ id: report
+ run: |
+ COUNT="${{ steps.rtl-scan.outputs.violation_count }}"
+ VIOLATIONS=$(cat /tmp/rtl-violations.txt)
+
+ echo "## RTL Violations Found — Fix Before Merge" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "**${COUNT} violation(s) detected.** Physical direction classes break Arabic and Hebrew layouts." >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Violations" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+
+ # Format each violation with the recommended replacement
+ while IFS= read -r line; do
+ FILE_LINE=$(echo "$line" | cut -d: -f1-2)
+ CONTENT=$(echo "$line" | cut -d: -f3-)
+
+ # Determine the replacement hint
+ HINT=""
+ if echo "$CONTENT" | grep -qE '\bml-'; then HINT="→ use ms-* (margin-inline-start)"; fi
+ if echo "$CONTENT" | grep -qE '\bmr-'; then HINT="→ use me-* (margin-inline-end)"; fi
+ if echo "$CONTENT" | grep -qE '\bpl-'; then HINT="→ use ps-* (padding-inline-start)"; fi
+ if echo "$CONTENT" | grep -qE '\bpr-'; then HINT="→ use pe-* (padding-inline-end)"; fi
+ if echo "$CONTENT" | grep -qE 'text-left'; then HINT="→ use text-start"; fi
+ if echo "$CONTENT" | grep -qE 'text-right'; then HINT="→ use text-end"; fi
+ if echo "$CONTENT" | grep -qE 'border-l-'; then HINT="→ use border-s-* (border-inline-start)"; fi
+ if echo "$CONTENT" | grep -qE 'border-r-'; then HINT="→ use border-e-* (border-inline-end)"; fi
+ if echo "$CONTENT" | grep -qE 'rounded-tl'; then HINT="→ use rounded-ss-* (border-start-start-radius)"; fi
+ if echo "$CONTENT" | grep -qE 'rounded-tr'; then HINT="→ use rounded-se-* (border-start-end-radius)"; fi
+ if echo "$CONTENT" | grep -qE 'rounded-bl'; then HINT="→ use rounded-es-* (border-end-start-radius)"; fi
+ if echo "$CONTENT" | grep -qE 'rounded-br'; then HINT="→ use rounded-ee-* (border-end-end-radius)"; fi
+ if echo "$CONTENT" | grep -qE 'float-left'; then HINT="→ use float-start"; fi
+ if echo "$CONTENT" | grep -qE 'float-right'; then HINT="→ use float-end"; fi
+
+ echo "${FILE_LINE} ${HINT}" >> $GITHUB_STEP_SUMMARY
+ done <<< "$VIOLATIONS"
+
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### How to fix" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "# In Claude Code:" >> $GITHUB_STEP_SUMMARY
+ echo "/rtl-fix" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "# Or with sed (preview first):" >> $GITHUB_STEP_SUMMARY
+ echo "# grep -rn 'ml-[0-9]' src/ | grep -v rtl-ok" >> $GITHUB_STEP_SUMMARY
+ echo '```' >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "> To suppress a line intentionally: add \`// rtl-ok — reason\` comment. Requires \`dir=\"ltr\"\` on the element or ancestor." >> $GITHUB_STEP_SUMMARY
+
+ # Also write to console for visibility in raw logs
+ echo ""
+ echo "=========================================="
+ echo "RTL violations found — fix before merge:"
+ echo "=========================================="
+ echo ""
+ cat /tmp/rtl-violations.txt
+ echo ""
+ echo "${COUNT} violation(s). Run /rtl-fix to auto-convert."
+ echo "Add // rtl-ok to suppress intentionally directional lines."
+ echo ""
+
+ - name: Post PR comment on first violation
+ if: steps.rtl-scan.outputs.violations_found == 'true' && github.event_name == 'pull_request'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const count = ${{ steps.rtl-scan.outputs.violation_count }};
+ const body = [
+ `## RTL Check Failed — ${count} violation(s) found`,
+ '',
+ 'Physical direction classes (`ml-`, `mr-`, `pl-`, `pr-`, `text-left`, `text-right`, `border-l-`, `border-r-`, `float-left`, `float-right`) break Arabic and Hebrew layouts.',
+ '',
+ '**Fix:** Run `/rtl-fix` in Claude Code, or see the [full class mapping](https://github.com/${{ github.repository }}/blob/main/cheatsheet/tailwind-rtl.md).',
+ '',
+ '**Suppress a specific line** by adding `// rtl-ok — reason` comment (requires `dir="ltr"` on element or ancestor).',
+ '',
+ 'See the **Checks** tab for the full violation list with line numbers.',
+ ].join('\n');
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body,
+ });
+
+ - name: Fail if violations found
+ if: steps.rtl-scan.outputs.violations_found == 'true'
+ run: |
+ echo "RTL check failed with ${{ steps.rtl-scan.outputs.violation_count }} violation(s)."
+ echo "See the step summary for the full list and fix instructions."
+ exit 1
+
+ - name: Pass — no violations
+ if: steps.rtl-scan.outputs.violations_found == 'false'
+ run: |
+ echo "RTL check passed — all direction classes use logical properties."
+ echo "## RTL Check Passed" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "All direction classes use logical properties. No physical direction classes detected." >> $GITHUB_STEP_SUMMARY
diff --git a/LICENSE b/LICENSE
index f6b8aef..d2b9ca7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 Nadav011
+Copyright (c) 2026 Nadav Cohen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index d2520a2..7e8c034 100644
--- a/README.md
+++ b/README.md
@@ -1,61 +1,265 @@
-# RTL First Dev Kit
-
-Local draft for a reusable RTL toolkit covering Tailwind, Flutter, Next.js, Biome policy, and CI validation.
-
-## Status
-
-This repository now contains a first practical pass of the core guidance. It is still a draft, but the main files are no longer placeholders only.
-
-## What It Covers
-
-- Tailwind logical-direction mapping and examples
-- Flutter directional primitives and common replacement patterns
-- Next.js 16 `proxy.ts` and `dir` propagation examples
-- Biome policy rules for blocking physical-direction regressions
-- CI workflow sketch for RTL validation in pull requests
-
-## Layout
-
-```text
-rtl-first-dev-kit/
-├── README.md
-├── SKILL.md
-├── tailwind/
-│ ├── README.md
-│ ├── logical-properties.md
-│ └── mapping.md
-├── flutter/
-│ ├── README.md
-│ ├── directional-primitives.md
-│ └── patterns.md
-├── nextjs/
-│ ├── README.md
-│ ├── dir-propagation.md
-│ └── proxy-and-dir.md
-├── biome/
-│ ├── README.md
-│ ├── policy.md
-│ └── rtl-policy.md
-└── ci/
- ├── README.md
- ├── rtl-validator-workflow.md
- └── workflow-sketch.md
-```
-
-## Intended Use
-
-Use this repo as a source pack for teams that already know they need RTL-safe defaults but want one place to copy patterns from.
-
-Start here:
-
-1. Tailwind apps: [mapping.md](/home/nadavcohen/Desktop/rtl-first-dev-kit/tailwind/mapping.md)
-2. Flutter apps: [patterns.md](/home/nadavcohen/Desktop/rtl-first-dev-kit/flutter/patterns.md)
-3. Next.js apps: [proxy-and-dir.md](/home/nadavcohen/Desktop/rtl-first-dev-kit/nextjs/proxy-and-dir.md)
-4. Repo policy: [policy.md](/home/nadavcohen/Desktop/rtl-first-dev-kit/biome/policy.md)
-5. CI enforcement: [rtl-validator-workflow.md](/home/nadavcohen/Desktop/rtl-first-dev-kit/ci/rtl-validator-workflow.md)
-
-## Draft Constraints
-
-- No publication claim yet
-- No benchmark or adoption claim yet
-- No promise of automated fixes beyond the policy sketches already written
+# RTL-First Dev Kit — The Only Production-Grade RTL Toolkit for AI Coding
+
+> **Tailwind 4.x logical properties. CSS logical properties. Flutter directional APIs. Zero `ml-4` survivors.**
+
+[](LICENSE)
+[](https://claude.ai/code)
+[](https://ai.google.dev/gemini-api/docs/gemini-cli)
+[](.)
+[](https://tailwindcss.com)
+
+---
+
+## The Problem: `ml-4` Silently Breaks Your RTL Layout
+
+Every RTL tutorial gives you `dir="rtl"` and calls it a day. The real problem is subtler and far more widespread: **physical direction classes are RTL-blind**.
+
+```jsx
+// BEFORE — ml-4, pl-3, text-left are physical. They are always left.
+// In Arabic or Hebrew, "left" is the END of the line — the wrong side.
+
+ {/* margin is still on the LEFT — in RTL this is the trailing edge, not the leading one */}
+ {/* padding anchored to the LEFT — shifts content in the wrong direction */}
+ {/* text-left forces alignment — ignores the document direction entirely */}
+ {/* border-l-2 puts the accent border on the wrong side */}
+ {/* rounded-tl-lg rounds the top-left corner, not the reading-start corner */}
+
+
+// AFTER — ms-4, ps-3, text-start are logical. They follow dir="rtl" automatically.
+// One class. Two languages. Zero overrides.
+
+ {/* ms-4 = margin-inline-start → right in RTL, left in LTR */}
+ {/* ps-3 = padding-inline-start → same automatic flip */}
+ {/* text-start → right-aligned in RTL, left-aligned in LTR */}
+ {/* border-s-2 → border on the reading-start edge, correct in both directions */}
+ {/* rounded-ss-lg → border-start-start-radius, rounds the correct corner */}
+
+```
+
+`ml-4` means "16px from the physical left edge". Always. Permanently. RTL-blind.
+`ms-4` means "16px from the inline-start edge". Automatically correct in every language.
+
+**CSS logical properties are the only correct abstraction for multilingual layout.**
+This kit enforces them everywhere — in your AI assistant, in code review, and in CI.
+
+---
+
+## What Is Included
+
+| Path | Purpose |
+|------|---------|
+| `skills/rtl-validator/SKILL.md` | AI skill: validates RTL compliance during code review |
+| `skills/rtl-fix/SKILL.md` | AI skill: auto-converts physical to logical properties |
+| `cheatsheet/tailwind-rtl.md` | Complete Tailwind 4.x logical property reference |
+| `CI/rtl-check.yml` | GitHub Actions: blocks PRs containing physical direction classes |
+| `tailwind/` | Tailwind config patterns and utility mapping tables |
+| `flutter/` | Flutter directional primitives and migration reference |
+| `nextjs/` | Next.js 15/16 locale routing and `dir` propagation patterns |
+| `biome/` | Biome lint policy for physical-direction regressions |
+
+---
+
+## Platforms
+
+| Platform | How to use |
+|----------|-----------|
+| **Claude Code** | Native skill — copy `skills/rtl-validator/` and `skills/rtl-fix/` to `~/.claude/skills/` |
+| **Gemini CLI** | Use `SKILL.md` content as system prompt prefix for any code review session |
+| **Kiro** | Paste `SKILL.md` into agent instructions |
+| **Cursor** | Paste `SKILL.md` contents into `.cursorrules` or `.mdc` rules file |
+
+---
+
+## Quick Install
+
+```bash
+# Copy skills into your Claude Code global skills directory
+cp -r skills/rtl-validator ~/.claude/skills/
+cp -r skills/rtl-fix ~/.claude/skills/
+
+# Copy the CI workflow into your project
+cp CI/rtl-check.yml .github/workflows/
+
+# Copy the cheatsheet to your docs
+cp cheatsheet/tailwind-rtl.md docs/rtl-reference.md
+```
+
+```bash
+# Coming soon — scaffold an RTL-first Next.js project in one command
+npx create-rtl-app
+```
+
+### Claude Code
+
+```
+/rtl-validator — scan current file or PR diff for RTL violations
+/rtl-fix — auto-convert physical direction classes to logical equivalents
+```
+
+### Gemini CLI
+
+```bash
+gemini -p "$(cat skills/rtl-validator/SKILL.md)" -- review src/components/Card.tsx
+```
+
+### Cursor / Kiro (MDC rules)
+
+Paste the contents of `skills/rtl-validator/SKILL.md` into your `.cursorrules` or `.kiro/rules/rtl.md` file.
+
+---
+
+## Why Logical Properties
+
+There are **50M+ Arabic and Hebrew developers** building apps that serve over **400 million native RTL speakers**. The vast majority of "RTL-supported" codebases still contain hundreds of `ml-`, `mr-`, `pl-`, `pr-` violations that silently produce wrong layouts at production scale.
+
+The problem is that `text-right` is not RTL. It is "align to the physical right edge". That is coincidentally correct in Arabic but catastrophically wrong in left-to-right contexts — and it produces untestable, direction-coupled code.
+
+Logical properties (`text-start`, `ms-*`, `ps-*`, `border-s-*`) express **reading-direction intent**, not physical position. They are the W3C standard, supported in all modern browsers, and the only correct foundation for bidirectional apps.
+
+**117+ RTL-first components in production across 18 Israeli projects** — zero `ml-`/`mr-` in the codebase.
+
+---
+
+## Full Class Mapping
+
+| Physical class (never use) | Logical class (always use) | CSS property generated |
+|---------------------------|---------------------------|------------------------|
+| `ml-{n}` | `ms-{n}` | `margin-inline-start` |
+| `mr-{n}` | `me-{n}` | `margin-inline-end` |
+| `pl-{n}` | `ps-{n}` | `padding-inline-start` |
+| `pr-{n}` | `pe-{n}` | `padding-inline-end` |
+| `left-{n}` | `inset-s-{n}` | `inset-inline-start` |
+| `right-{n}` | `inset-e-{n}` | `inset-inline-end` |
+| `text-left` | `text-start` | `text-align: start` |
+| `text-right` | `text-end` | `text-align: end` |
+| `border-l-{n}` | `border-s-{n}` | `border-inline-start-width` |
+| `border-r-{n}` | `border-e-{n}` | `border-inline-end-width` |
+| `rounded-l-{n}` | `rounded-s-{n}` | both inline-start radii |
+| `rounded-r-{n}` | `rounded-e-{n}` | both inline-end radii |
+| `rounded-tl-{n}` | `rounded-ss-{n}` | `border-start-start-radius` |
+| `rounded-tr-{n}` | `rounded-se-{n}` | `border-start-end-radius` |
+| `rounded-bl-{n}` | `rounded-es-{n}` | `border-end-start-radius` |
+| `rounded-br-{n}` | `rounded-ee-{n}` | `border-end-end-radius` |
+| `float-left` | `float-start` | `float: inline-start` |
+| `float-right` | `float-end` | `float: inline-end` |
+| `scroll-ml-{n}` | `scroll-ms-{n}` | `scroll-margin-inline-start` |
+| `scroll-pl-{n}` | `scroll-ps-{n}` | `scroll-padding-inline-start` |
+
+Full reference with edge cases: [cheatsheet/tailwind-rtl.md](cheatsheet/tailwind-rtl.md)
+
+---
+
+## Real-World Example: Hebrew Product Card
+
+```jsx
+// WRONG — every directional class is physically anchored
+function ProductCard({ product }) {
+ return (
+
+
+
+
{product.name}
+
{product.description}
+ {product.price}
+
+
+
+ );
+}
+
+// CORRECT — fully logical, works in he-IL, ar-SA, ar-EG, and en-US with zero overrides
+function ProductCard({ product }) {
+ return (
+
+
+
+
{product.name}
+
{product.description}
+ {/* Numbers are LTR even in RTL context — wrap explicitly */}
+
+ {product.price}
+
+
+ {/* Horizontal chevron flips in RTL — arrow direction carries meaning */}
+
+
+ );
+}
+```
+
+---
+
+## CI Enforcement
+
+Add `CI/rtl-check.yml` to `.github/workflows/`. Every pull request against `*.tsx`, `*.jsx`, `*.css`, and `*.vue` files is scanned for physical direction classes. Violations block merge.
+
+Example output when violations are found:
+
+```
+RTL violations found — fix before merge:
+
+ src/components/Card.tsx:14 ml-4 → use ms-4
+ src/components/Card.tsx:15 pl-3 → use ps-3
+ src/components/Nav.tsx:8 text-left → use text-start
+ src/pages/Profile.tsx:22 border-l-2 → use border-s-2
+
+4 violation(s) across 3 files.
+Run /rtl-fix to auto-convert, or add // rtl-ok on lines that are intentionally directional.
+```
+
+---
+
+## Flutter RTL
+
+```dart
+// WRONG — left/right are physical, always pinned to physical edges
+Padding(
+ padding: EdgeInsets.only(left: 16, right: 8),
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: Text('שלום עולם'),
+ ),
+)
+
+// CORRECT — Directional APIs respond to the Directionality widget
+Padding(
+ padding: EdgeInsetsDirectional.only(start: 16, end: 8),
+ child: Align(
+ alignment: AlignmentDirectional.centerStart,
+ child: Text('שלום עולם'),
+ ),
+)
+
+// CORRECT — positioned widget with directional offset
+Stack(
+ children: [
+ PositionedDirectional(
+ start: 16,
+ top: 8,
+ child: Icon(Icons.check),
+ ),
+ ],
+)
+```
+
+See `flutter/` for the full Flutter RTL reference.
+
+---
+
+## Contributing
+
+1. Zero `ml-`, `mr-`, `pl-`, `pr-`, `text-left`, `text-right`, `float-left`, `float-right` in any example
+2. All examples must render correctly with both `` and ``
+3. Test by running `document.documentElement.dir = 'rtl'` in DevTools before submitting
+4. Flutter examples must compile without `left`, `right`, `topLeft`, `topRight` in widget APIs
+5. If an element is genuinely LTR-only (e.g., a map widget), wrap in `dir="ltr"` and add a `// rtl-ok` comment
+
+---
+
+## License
+
+MIT — use it, extend it, ship it.
+
+---
+
+*Built by [Nadav Cohen](https://github.com/nadavcohen) — shipping Hebrew-first since before it was a thing.*
diff --git a/SKILL.md b/SKILL.md
index 9e6217c..450f234 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,7 +1,13 @@
---
name: rtl-first-dev-kit
-description: Local scaffold for an RTL-first toolkit covering Tailwind, Flutter, Next.js, Biome, and CI patterns.
+description: Production-grade RTL-first toolkit — Tailwind 4.x logical properties, Flutter directional APIs, Next.js dir propagation, Biome RTL policy, CI validation
+triggers:
+ - "rtl toolkit"
+ - "rtl first"
+ - "logical properties"
+ - "rtl dev kit"
---
+
# RTL First Dev Kit
diff --git a/cheatsheet/tailwind-rtl.md b/cheatsheet/tailwind-rtl.md
new file mode 100644
index 0000000..c4d317c
--- /dev/null
+++ b/cheatsheet/tailwind-rtl.md
@@ -0,0 +1,560 @@
+# Tailwind 4.x RTL Cheatsheet — Complete Logical Properties Reference
+
+> The definitive reference for building bidirectional apps with Tailwind 4.x.
+> Physical classes are banned. Logical classes are the only correct foundation.
+
+**Related:** [Tailwind CSS docs — Logical Properties](https://tailwindcss.com/docs/hover-focus-and-other-states#rtl-support) | [W3C CSS Logical Properties spec](https://www.w3.org/TR/css-logical-1/)
+
+---
+
+## The Core Principle
+
+Physical classes (`ml-`, `mr-`, `text-left`) are anchored to the physical screen.
+They never change, regardless of document direction.
+
+Logical classes (`ms-`, `me-`, `text-start`) are anchored to the reading direction.
+They automatically flip when `dir="rtl"` is set on any ancestor element.
+
+```
+LTR (dir="ltr"): start = left, end = right
+RTL (dir="rtl"): start = right, end = left
+```
+
+---
+
+## Spacing — Margin
+
+| Physical class (NEVER) | Logical class (ALWAYS) | CSS property generated |
+|-----------------------|----------------------|------------------------|
+| `ml-0` | `ms-0` | `margin-inline-start: 0` |
+| `ml-1` | `ms-1` | `margin-inline-start: 0.25rem` |
+| `ml-2` | `ms-2` | `margin-inline-start: 0.5rem` |
+| `ml-4` | `ms-4` | `margin-inline-start: 1rem` |
+| `ml-8` | `ms-8` | `margin-inline-start: 2rem` |
+| `ml-auto` | `ms-auto` | `margin-inline-start: auto` |
+| `ml-px` | `ms-px` | `margin-inline-start: 1px` |
+| `ml-[1.75rem]` | `ms-[1.75rem]` | arbitrary value |
+| `-ml-4` | `-ms-4` | `margin-inline-start: -1rem` |
+| `mr-0` | `me-0` | `margin-inline-end: 0` |
+| `mr-1` | `me-1` | `margin-inline-end: 0.25rem` |
+| `mr-2` | `me-2` | `margin-inline-end: 0.5rem` |
+| `mr-4` | `me-4` | `margin-inline-end: 1rem` |
+| `mr-auto` | `me-auto` | `margin-inline-end: auto` |
+| `-mr-4` | `-me-4` | `margin-inline-end: -1rem` |
+
+Already logical (no change needed):
+- `mt-*` — `margin-block-start` — already logical
+- `mb-*` — `margin-block-end` — already logical
+- `mx-*` — `margin-inline` (both sides) — already logical
+- `my-*` — `margin-block` (both sides) — already logical
+- `m-*` — all four sides — already logical
+
+---
+
+## Spacing — Padding
+
+| Physical class (NEVER) | Logical class (ALWAYS) | CSS property generated |
+|-----------------------|----------------------|------------------------|
+| `pl-0` | `ps-0` | `padding-inline-start: 0` |
+| `pl-1` | `ps-1` | `padding-inline-start: 0.25rem` |
+| `pl-2` | `ps-2` | `padding-inline-start: 0.5rem` |
+| `pl-4` | `ps-4` | `padding-inline-start: 1rem` |
+| `pl-6` | `ps-6` | `padding-inline-start: 1.5rem` |
+| `pl-8` | `ps-8` | `padding-inline-start: 2rem` |
+| `pl-[1.5rem]` | `ps-[1.5rem]` | arbitrary value |
+| `pr-0` | `pe-0` | `padding-inline-end: 0` |
+| `pr-2` | `pe-2` | `padding-inline-end: 0.5rem` |
+| `pr-4` | `pe-4` | `padding-inline-end: 1rem` |
+| `pr-6` | `pe-6` | `padding-inline-end: 1.5rem` |
+
+Already logical (no change needed):
+- `pt-*` — `padding-block-start`
+- `pb-*` — `padding-block-end`
+- `px-*` — `padding-inline` (both sides)
+- `py-*` — `padding-block` (both sides)
+- `p-*` — all four sides
+
+---
+
+## Borders
+
+| Physical class (NEVER) | Logical class (ALWAYS) | CSS property generated |
+|-----------------------|----------------------|------------------------|
+| `border-l` | `border-s` | `border-inline-start-width: 1px` |
+| `border-r` | `border-e` | `border-inline-end-width: 1px` |
+| `border-l-0` | `border-s-0` | `border-inline-start-width: 0` |
+| `border-l-2` | `border-s-2` | `border-inline-start-width: 2px` |
+| `border-l-4` | `border-s-4` | `border-inline-start-width: 4px` |
+| `border-l-8` | `border-s-8` | `border-inline-start-width: 8px` |
+| `border-r-2` | `border-e-2` | `border-inline-end-width: 2px` |
+| `border-r-4` | `border-e-4` | `border-inline-end-width: 4px` |
+| `border-l-blue-500` | `border-s-blue-500` | `border-inline-start-color` |
+| `border-r-gray-200` | `border-e-gray-200` | `border-inline-end-color` |
+
+Already logical (no change needed):
+- `border-t-*` / `border-b-*` — block-axis borders
+
+---
+
+## Positioning — Inset
+
+In Tailwind 4.2+, use `inset-s-*` and `inset-e-*` for logical positioning.
+Do not use the deprecated `start-*` / `end-*` standalone utilities.
+
+| Physical class (NEVER) | Logical class (ALWAYS) | CSS property generated |
+|-----------------------|----------------------|------------------------|
+| `left-0` | `inset-s-0` | `inset-inline-start: 0` |
+| `left-1` | `inset-s-1` | `inset-inline-start: 0.25rem` |
+| `left-2` | `inset-s-2` | `inset-inline-start: 0.5rem` |
+| `left-4` | `inset-s-4` | `inset-inline-start: 1rem` |
+| `left-full` | `inset-s-full` | `inset-inline-start: 100%` |
+| `left-1/2` | `inset-s-1/2` | `inset-inline-start: 50%` |
+| `left-[16px]` | `inset-s-[16px]` | arbitrary value |
+| `-left-4` | `-inset-s-4` | `inset-inline-start: -1rem` |
+| `right-0` | `inset-e-0` | `inset-inline-end: 0` |
+| `right-2` | `inset-e-2` | `inset-inline-end: 0.5rem` |
+| `right-4` | `inset-e-4` | `inset-inline-end: 1rem` |
+| `right-full` | `inset-e-full` | `inset-inline-end: 100%` |
+| `-right-4` | `-inset-e-4` | `inset-inline-end: -1rem` |
+
+Special case — full width stretch (both edges):
+
+```jsx
+// BEFORE — both physical edges, full width
+
+
+// AFTER — use inset-x-0 (covers both inline edges)
+
+```
+
+---
+
+## Border Radius
+
+The logical radius classes use the `ss` / `se` / `es` / `ee` naming convention:
+`s` = start, `e` = end, first letter = block axis, second = inline axis.
+
+| Physical class (NEVER) | Logical class (ALWAYS) | CSS property |
+|-----------------------|----------------------|--------------|
+| `rounded-l` | `rounded-s` | both inline-start corners |
+| `rounded-r` | `rounded-e` | both inline-end corners |
+| `rounded-l-sm` | `rounded-s-sm` | both inline-start corners, sm |
+| `rounded-l-md` | `rounded-s-md` | both inline-start corners, md |
+| `rounded-l-lg` | `rounded-s-lg` | both inline-start corners, lg |
+| `rounded-l-xl` | `rounded-s-xl` | both inline-start corners, xl |
+| `rounded-l-full` | `rounded-s-full` | both inline-start corners, full |
+| `rounded-r-lg` | `rounded-e-lg` | both inline-end corners, lg |
+| `rounded-tl` | `rounded-ss` | `border-start-start-radius` |
+| `rounded-tl-sm` | `rounded-ss-sm` | border-start-start-radius, sm |
+| `rounded-tl-md` | `rounded-ss-md` | border-start-start-radius, md |
+| `rounded-tl-lg` | `rounded-ss-lg` | border-start-start-radius, lg |
+| `rounded-tl-xl` | `rounded-ss-xl` | border-start-start-radius, xl |
+| `rounded-tr` | `rounded-se` | `border-start-end-radius` |
+| `rounded-tr-lg` | `rounded-se-lg` | border-start-end-radius, lg |
+| `rounded-bl` | `rounded-es` | `border-end-start-radius` |
+| `rounded-bl-lg` | `rounded-es-lg` | border-end-start-radius, lg |
+| `rounded-br` | `rounded-ee` | `border-end-end-radius` |
+| `rounded-br-lg` | `rounded-ee-lg` | border-end-end-radius, lg |
+
+Already logical (no change needed):
+- `rounded-t-*` — both top corners
+- `rounded-b-*` — both bottom corners
+- `rounded-*` — all four corners
+
+---
+
+## Text Alignment
+
+| Physical class (NEVER) | Logical class (ALWAYS) | CSS generated |
+|-----------------------|----------------------|---------------|
+| `text-left` | `text-start` | `text-align: start` |
+| `text-right` | `text-end` | `text-align: end` |
+
+`text-center` and `text-justify` are already direction-neutral.
+
+---
+
+## Float
+
+| Physical class (NEVER) | Logical class (ALWAYS) | CSS generated |
+|-----------------------|----------------------|---------------|
+| `float-left` | `float-start` | `float: inline-start` |
+| `float-right` | `float-end` | `float: inline-end` |
+
+`float-none` is already direction-neutral.
+
+---
+
+## Scroll Margin and Scroll Padding
+
+| Physical class (NEVER) | Logical class (ALWAYS) |
+|-----------------------|----------------------|
+| `scroll-ml-{n}` | `scroll-ms-{n}` |
+| `scroll-mr-{n}` | `scroll-me-{n}` |
+| `scroll-pl-{n}` | `scroll-ps-{n}` |
+| `scroll-pr-{n}` | `scroll-pe-{n}` |
+
+---
+
+## Flexbox — Already Logical in Tailwind 4
+
+Flexbox direction utilities are already based on flex-direction, not physical screen axes.
+When `flex-row` is combined with `dir="rtl"`, the main axis reverses automatically.
+
+```jsx
+// This is already RTL-correct — no changes needed
+
+
+
+
+
+// In RTL: UserMenu appears on the left, Logo on the right — correct reading order
+```
+
+`justify-start`, `justify-end`, `items-start`, `items-end`,
+`self-start`, `self-end`, `place-content-start`, `place-content-end`
+are all logical — they respond to `flex-direction` and `dir`.
+
+---
+
+## Grid — Logical Considerations
+
+Grid column ordering does not automatically reverse in RTL unless you use
+`direction: rtl` at the grid container level or `grid-template-columns` with
+logical named lines.
+
+```jsx
+// Manual RTL handling for grid layouts
+
+
+
+
+```
+
+---
+
+## Common Mistakes
+
+### Mistake 1: `text-right` is not RTL alignment
+
+`text-right` forces text to the physical right edge in ALL documents, regardless
+of `dir`. In an LTR document inside an RTL wrapper, this is the wrong edge.
+
+```jsx
+// WRONG — text-right means "physical right", not "reading end"
+
+
هذا النص محاذى لليمين الفعلي فقط
+
+
+// CORRECT — text-end means "end of the reading direction"
+
+
هذا النص محاذى لنهاية اتجاه القراءة
+
+```
+
+### Mistake 2: `dir="rtl"` alone is not enough
+
+Setting `dir="rtl"` on the `` element tells the browser the document reading
+direction, but it does NOT flip physical margin/padding/border classes. Those remain
+physically anchored.
+
+```html
+
+
+
תוכן
+
+
+
+
+
תוכן
+
+```
+
+### Mistake 3: Using `rtl:ml-4` as an override
+
+Some developers add `rtl:ml-4` to flip the margin for RTL, doubling the maintenance burden.
+This is an anti-pattern — you end up with two classes to maintain instead of one.
+
+```jsx
+// ANTI-PATTERN — duplicated logic, easy to get out of sync
+
...
+
+// CORRECT — single logical class, zero overrides
+
...
+```
+
+### Mistake 4: Forgetting to flip horizontal icons
+
+A `ChevronRight` pointing right means "go forward" in LTR. In RTL, "forward" is
+to the left — so the chevron must flip.
+
+```jsx
+// WRONG — arrow always points right, meaning changes in RTL
+
+
+// CORRECT — arrow flips to point left in RTL, preserving "forward" meaning
+
+```
+
+### Mistake 5: Not wrapping LTR islands in mixed BiDi content
+
+Numbers, phone numbers, percentages, dates, and code strings are always LTR,
+even in RTL documents. Without explicit `dir="ltr"`, the Unicode BiDi algorithm
+may render them incorrectly.
+
+```jsx
+// WRONG — browser BiDi algorithm may render this incorrectly
+
מחיר: ₪1,234.56
+
+// CORRECT — explicit LTR island
+
+ מחיר: ₪1,234.56
+
+```
+
+---
+
+## Complete Hebrew UI Component Example
+
+This is a full form component with all logical properties applied correctly.
+
+```tsx
+// rtl-fix applied — all direction classes use logical properties
+// Works in he-IL, ar-SA, ar-EG, and en-US with zero overrides
+
+interface FormFieldProps {
+ label: string;
+ value: string;
+ onChange: (v: string) => void;
+ error?: string;
+ required?: boolean;
+}
+
+export function FormField({ label, value, onChange, error, required }: FormFieldProps) {
+ return (
+
+ {/* Label row — icon on the reading-start side */}
+
+
+ {/* Input with logical padding */}
+
+ onChange(e.target.value)}
+ className={[
+ "w-full rounded-lg border px-3 py-2", // px is symmetric — fine
+ "ps-3 pe-10", // logical padding for icon space
+ "text-start", // text aligns to reading direction
+ "border-gray-300 bg-white",
+ "focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20",
+ error ? "border-red-400" : "",
+ ].join(" ")}
+ />
+
+ {/* Validation icon — positioned on reading-END side */}
+ {error && (
+
+
+
+ )}
+
+
+ {/* Error message — text aligns to reading start */}
+ {error && (
+
+```
+
+Requirements for a valid suppression:
+1. The element or its ancestor must have `dir="ltr"` explicitly set
+2. The `// rtl-ok` comment must include a reason (one sentence minimum)
+
+---
+
+## Before / After Example
+
+```jsx
+// BEFORE — 6 RTL violations in a single component
+function SidebarItem({ label, icon, badge }) {
+ return (
+
+ {icon}
+ {label}
+ {badge && (
+ {badge}
+ )}
+
+ );
+}
+
+// AFTER — zero RTL violations, works in he-IL, ar-SA, ar-EG, and en-US
+function SidebarItem({ label, icon, badge }) {
+ return (
+
+ {icon}
+ {label}
+ {badge && (
+ {badge}
+ )}
+
+ );
+}
+```
+
+---
+
+## Tailwind 4.x Inset Class Notes
+
+In Tailwind 4.2+:
+- `inset-s-{n}` → `inset-inline-start` — correct, use this
+- `inset-e-{n}` → `inset-inline-end` — correct, use this
+- `start-{n}` / `end-{n}` — deprecated in Tailwind 4.2 but still compile; migrate to `inset-s-` / `inset-e-`
+- `inline-s-{n}` / `inline-e-{n}` — do NOT exist, generate zero CSS — never suggest these
+
+---
+
+## Icon Flip Rule
+
+Horizontal icons (arrows, chevrons pointing left/right, back/forward buttons) flip
+in RTL because their direction carries semantic meaning. Vertical and decorative
+icons never flip.
+
+```jsx
+// Horizontal icons — flip in RTL
+
+
+
+
+
+// Vertical icons — never flip
+ // up/down has no RTL mirror
+ // no change in RTL
+
+
+// Non-directional icons — never flip
+
+
+
+
+```
+
+---
+
+## BiDi Mixed Content Rule
+
+Numbers, phone numbers, percentages, dates, currency, code identifiers, and
+brand names are LTR even inside RTL text. Always wrap them explicitly.
+
+```jsx
+// Hebrew UI with embedded LTR numbers
+
+ יעד חודשי: 42%
+
+
+// Phone number in Arabic context
+
+ هاتف: +972-50-123-4567
+
+
+// User-generated content — let the browser detect direction
+
{userInput}
+
+// Brand names embedded in RTL sentence
+
+ הפרויקט נבנה עם Next.js ו-Supabase
+
+```
+
+---
+
+## Animation Warning (Non-Blocking)
+
+`translateX` values do not automatically flip in RTL. Flag these as warnings,
+not hard violations. Provide the correct pattern.
+
+```css
+/* WARNING — translateX does not flip with dir="rtl" */
+.drawer { transform: translateX(-100%); }
+
+/* CORRECT — use a CSS variable that flips with the dir attribute */
+:root { --drawer-offset: -100%; }
+:root[dir="rtl"] { --drawer-offset: 100%; }
+.drawer { transform: translateX(var(--drawer-offset)); }
+```
+
+```tsx
+// CORRECT in Framer Motion / motion-react
+const { dir } = useDocumentDirection();
+const isRTL = dir === 'rtl';
+
+
+```
+
+---
+
+## Flutter Equivalents
+
+| Physical Flutter API (VIOLATION) | Directional API (correct) |
+|----------------------------------|---------------------------|
+| `EdgeInsets.only(left: n)` | `EdgeInsetsDirectional.only(start: n)` |
+| `EdgeInsets.only(right: n)` | `EdgeInsetsDirectional.only(end: n)` |
+| `EdgeInsets.fromLTRB(...)` | `EdgeInsetsDirectional.fromSTEB(...)` |
+| `Alignment.centerLeft` | `AlignmentDirectional.centerStart` |
+| `Alignment.centerRight` | `AlignmentDirectional.centerEnd` |
+| `Alignment.topLeft` | `AlignmentDirectional.topStart` |
+| `Alignment.topRight` | `AlignmentDirectional.topEnd` |
+| `Positioned(left: n)` | `PositionedDirectional(start: n)` |
+| `Positioned(right: n)` | `PositionedDirectional(end: n)` |
+| `TextAlign.left` | `TextAlign.start` |
+| `TextAlign.right` | `TextAlign.end` |
+
+---
+
+## False Positive Exceptions — Do NOT Flag
+
+1. `left-0 right-0` (both physical edges set) — this is centering / full-width stretch, not directional
+2. `margin-left: auto` combined with `margin-right: auto` — this is horizontal centering
+3. `translate-x-{n}` in animation without context — warn, do not block
+4. `float: left` inside a container that has `direction: ltr` explicitly set
+5. Storybook decorators that intentionally test LTR-only variants
+6. Any line with `// rtl-ok` comment (suppressed)
+
+---
+
+## Code Review Output Format
+
+When violations are found:
+
+```
+RTL violation — {file}:{line}
+ Found: {violating class or property}
+ Replace: {logical equivalent}
+ Why: {one sentence explaining the RTL impact}
+```
+
+When no violations are found:
+
+```
+RTL check passed — all direction classes use logical properties.
+```
+
+Summary footer (always include when reviewing a full file or PR):
+
+```
+RTL scan complete: {n} violation(s) in {m} file(s).
+```