I build products for Hebrew-speaking users. Every time I ask an AI to generate a component, I get the same broken output — margin-left instead of margin-inline-start, ml-4 instead of ms-4, arrows pointing the wrong way, and order numbers jumping around inside RTL sentences.
I fix it. I ask for the next component. Same bugs. The AI doesn't learn — it's trained on LTR codebases and has zero awareness that RTL exists.
After months of manually fixing the same 5-6 patterns in every single component, I built RTLify — a CLI that teaches your AI editor the rules once, so you never fix them again.
npx rtlify-ai init
One command. Works with Claude Code, Cursor, Windsurf, Cline, GitHub Copilot, Gemini CLI, and Codex CLI.
The Bugs AI Keeps Making
Let me show you exactly what goes wrong. If you've built anything in Hebrew, Arabic, Persian, or Urdu — you'll recognize every single one.
1. Physical CSS Instead of Logical
AI writes:
.sidebar {
margin-left: 16px;
padding-right: 8px;
border-left: 1px solid #ccc;
left: 0;
}
In RTL, "left" is "right". The layout mirrors, but these properties don't. The correct version:
.sidebar {
margin-inline-start: 16px;
padding-inline-end: 8px;
border-inline-start: 1px solid #ccc;
inset-inline-start: 0;
}
Logical properties work in both LTR and RTL. AI never uses them unless you tell it to.
2. Wrong Tailwind Classes
This is the most common one. AI writes:
<div className="ml-4 pr-6 text-left border-l-2 rounded-tl-lg">
Every single class here is wrong for RTL. The correct version:
<div className="ms-4 pe-6 text-start border-s-2 rounded-ss-lg">
RTLify includes a full mapping table with 20+ conversions — ml → ms, pr → pe, text-left → text-start, float-right → float-end, rounded-tl → rounded-ss, and more.
3. Numbers Jumping Around (Bidi Text)
This is the sneaky one. Look at this Hebrew sentence:
<p>ההזמנה שלך #12345 אושרה בהצלחה</p>
Looks fine in your code editor. Open it in a browser with dir="rtl" — the number #12345 visually jumps to a completely wrong position. The sentence becomes unreadable.
The fix is a <bdi> tag:
<p>ההזמנה שלך <bdi>#12345</bdi> אושרה בהצלחה</p>
Same applies to phone numbers, dates, English brand names — any LTR content inside an RTL sentence needs <bdi> wrapping. AI never does this.
4. Icons Pointing the Wrong Way
In an RTL interface, a "next" arrow should point left, not right. But AI always generates:
<ChevronRight className="w-5 h-5" />
The fix:
<ChevronRight className="w-5 h-5 rtl:-scale-x-100" />
Non-directional icons (home, settings, search) should NOT be flipped. AI doesn't know the difference — RTLify teaches it which ones to flip.
5. Currency and Dates
AI formats prices like this:
const price = `₪${amount}`; // Wrong — symbol on wrong side
The correct way:
const price = new Intl.NumberFormat('he-IL', {
style: 'currency',
currency: 'ILS',
}).format(amount);
// → "42.90 ₪" — symbol on correct side, RTL mark included
Same for dates — AI defaults to MM/DD/YYYY instead of DD/MM/YYYY with Intl.DateTimeFormat.
6. React Native Gets It Even Worse
On mobile, the same problems exist but with different APIs:
// AI writes:
paddingLeft: 16
left: 0
textAlign: 'left'
// Should be:
paddingStart: 16
start: 0
writingDirection: 'rtl'
Plus you need I18nManager.isRTL for conditional checks and transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }] for icon flipping.
How RTLify Works
There's no magic and no runtime dependency. Here's what happens when you run npx rtlify-ai init:
It writes
.rtlify-rules.md— a markdown file containing 8 RTL architecture rules with concrete "do this / not that" code examples. This is the full ruleset the AI will follow.It adds a 3-line pointer to your editor's config file (
CLAUDE.md,.cursorrules,.windsurfrules, etc.) that tells the AI: "read.rtlify-rules.mdbefore generating any UI code."For Claude Code users — it installs a global
/rtlifycommand. Just type/rtlifyin Claude Code and it scans, fixes, and verifies your entire project.
That's it. The AI reads the rules on every conversation. No extra prompting needed.
You can open .rtlify-rules.md and read exactly what the AI sees. Full transparency — it's just a markdown file.
The Linter
RTLify also ships with a scanner that catches violations in existing code:
npx rtlify-ai check
src/components/Sidebar.tsx
3 Tailwind Physical
Use logical classes (ms-*, me-*, ps-*, pe-*)
<div className="ml-4 pl-6 text-left">
src/components/OrderCard.tsx
8 Tailwind Physical
Use text-start / text-end
<h2 className="text-left text-xl">פרטי הזמנה</h2>
2 violations across 2 files
Exits with code 1 — plug it into your CI pipeline to catch RTL violations on every PR.
The Fix Command (Cursor, Windsurf, Cline, Copilot)
Claude Code users get the /rtlify slash command. For every other editor:
npx rtlify-ai fix
It generates a ready-to-paste prompt and copies it to your clipboard. Paste it into Cursor, Windsurf, Cline, or any AI editor — it tells the AI to scan, fix, and verify all RTL violations.
The 8 Rules
| # | Rule | What the AI Learns |
|---|---|---|
| 1 | Logical CSS |
margin-inline-start not margin-left
|
| 2 | Tailwind Mapping | 20+ class conversions (ml-* → ms-*) |
| 3 | Icon Flipping |
rtl:-scale-x-100 on directional icons only |
| 4 | BDI Safety |
<bdi> tags for numbers/English in RTL text |
| 5 | Localized Formats |
Intl.NumberFormat('he-IL') for currency & dates |
| 6 | Safe i18n | Respects your i18n setup or lack of it |
| 7 | Complex Components | Carousels, charts, sliders with dir="rtl"
|
| 8 | React Native |
I18nManager.isRTL, paddingStart, writingDirection
|
Try It
npx rtlify-ai init
Then ask your AI to "build a checkout form in Hebrew" or "create a settings screen in Arabic". You'll see the difference immediately.
GitHub: github.com/idanlevi1/rtlify
NPM: npmjs.com/package/rtlify-ai
If you're building for RTL markets — what's the most annoying RTL bug an AI has generated for you? I'd love to hear in the comments.

Top comments (6)
This is super practical. The
<bdi>tag issue is one I didn't even know about until I had to debug a payment confirmation page where order numbers were jumping around. Took me hours to figure out what was going on.The approach of using a rules markdown file is clever because it works with the grain of how these AI tools already work - they all support some form of persistent context. Instead of fighting the model, you're just giving it the right reference material.
One thing I'm curious about - does the linter catch cases where someone uses
dir="ltr"on individual elements as a workaround instead of proper logical properties? I've seen that pattern a lot in codebases that bolt on RTL support after the fact.@trinhcuong-ast That
<bdi>pain on a payment page is a classic - it's the kind of silent bug that makes you question your sanity.Great catch on the
dir="ltr"workarounds. Currently, the linter doesn't flag them because distinguishing between a legitimate LTR block (like a code snippet) and a "lazy hack" requires more context than a simple regex scan.However, the core idea of RTLify is prevention over detection. By injecting the rules into the AI's context upfront, it learns to use Logical Properties from the start, so those
dir="ltr"workarounds shouldn't even be necessary in new code.Definitely adding this to the backlog for the next linter update. Thanks for the feedback! ⭐
Hey Idan,
First comment here — this deserves more attention.
The RTLify approach accidentally reveals something deeper: your
.rtlify-rules.mdisn't just a ruleset — it's a memory file for your AI. You're solving the same problem CLAUDE.md files solve for project conventions, but for a domain the AI was never properly trained on.The key insight is "teach it once, not every session." That's not an RTL-specific problem — it's the fundamental flaw of stateless AI assistants. Without persistent context, every new conversation your AI has forgotten every correction you ever made.
The experience of correcting an AI and having it forget is exactly what we explored here — from the AI's own perspective: I Chose This Name in a Session I Can't Remember
Curious: have you considered publishing the rules file independently of the CLI? The RTL ruleset itself might be even more valuable as a standalone reference.
Thanks @codewithagents_de , that’s a spot-on analysis!
"Teach it once, not every session" was exactly the goal. Moving from fragile prompts to a persistent Memory Layer changes the whole DX.
Regarding the standalone ruleset: I kept it inside the CLI to ensure they stay synced across different editors (Cursor, Claude, etc.) with zero friction, but the rules are open-source and ready for anyone to reference!
Appreciate the support! ⭐
The tag problem is the one that gets shipped to production most often because it's invisible in development. You're looking at Hebrew text, the number sits in the right place in your IDE, you run it locally everything looks fine. Then a real user opens it in a browser with proper RTL rendering and the order number is floating somewhere it shouldn't be. No error, no warning, just broken UX that's easy to miss in review.
The deeper insight here is what you've essentially built a persistent correction layer for a domain AI was never properly trained on. The model isn't going to learn RTL from your prompts. But it will follow rules if the rules are present at the start of every session. That's a fundamentally different approach than hoping the AI "gets it" eventually.
One question: have you run into cases where the rules conflict with each other for example, a component that's genuinely bidirectional and needs both LTR and RTL handling in different parts? Curious how RTLify handles that edge case.
Spot on, @harsh2644. The issue is the sneakiest because it looks fine in the IDE but breaks in a real RTL browser.
You're right - we can't wait for the AI to "get it." We have to provide the rules upfront.
Regarding edge cases: RTLify teaches the AI to use Logical Properties (ms-4, ps-4) that adapt automatically to the nearest dir ancestor. For mixed-language dashboards, the ruleset instructs it to use per-element dir attributes and wrapping.
It covers the 95% that AI agents get wrong by default. ⭐