hebrew-rtl-best-practices

Implement right-to-left (RTL) layouts for Hebrew web applications. Use when user asks about RTL layout, Hebrew text direction, bidirectional (bidi) text, Hebrew CSS, "right to left", or needs to build a Hebrew web UI. Covers CSS logical properties, the :dir() pseudo-class, Tailwind RTL, React/Next.j

By skills-il · 558 installs

npx skills add skills-il/localization --skill hebrew-rtl-best-practices

Source repository · Upstream listing

Hebrew RTL Best Practices Instructions Step 1: Set Up Document Direction Always start with the HTML attribute (not just CSS): This tells browsers, screen readers, and CSS to use RTL as the base direction. Step 2: Use CSS Logical Properties NEVER use physical directional properties for layout: Physical (avoid) Logical (use) margin left margin inline start margin right margin inline end padding left padding inline start padding right padding inline end border left border inline start text align: left text align: start text align: right text align: end float: left float: inline start left: 10px inset inline start: 10px This ensures the layout automatically mirrors in RTL mode. When you genuinely need a direction specific rule that logical properties cannot express, prefer the :dir() pseudo class over [dir="rtl"] attribute selectors: :dir() is part of Selectors Level 4 and resolves the computed direction, so it also works for elements whose direction comes from dir="auto" or from an ancestor, where an attribute selector would miss them. Browser support: Chrome and Edge shipped it in version 120 (late 2023), Firefox has supported it for years, and Safari added it in 16.4, so it is now Baseline (widely available). For older browser support, keep an [dir="rtl"] fallback rule or use a logical property instead. Check current support at https://caniuse.com/css dir pseudo. Step 3: Handle Bidirectional Text When mixing Hebrew and English/numbers: Do not use unicode bidi: bidi override for mixed content. It switches off the implicit part of the bidi algorithm and lays every character out strictly in the direction value, so English inside an RTL override renders letter reversed. Reserve it (or <bdo ) for deliberately forcing a known visual order. Common bidi issues: Phone numbers written with spaces ( 050 321 4450 ) or a +972 prefix appearing scrambled: wrap in <bdo dir="ltr" . A plain hyphenated 050 321 4450 already stays intact, because a single hyphen between two numbers joins them into one number run. Punctuation at wrong end of sentence: Use unicode bidi: isolate URLs/emails in Hebrew text: Wrap in <span dir="ltr" Numbers and dates: Standalone numbers and DD/MM/YYYY dates inside Hebrew text usually render fine because digits are weak LTR, but a number that is immediately followed by a sign, currency, or a second number can flip. When a value must keep a fixed visual order, isolate it with <span dir="ltr" or unicode bidi: isolate rather than trusting the default bidi resolution. Format the value, then isolate it. Bidi isolation only stops a correct string from flipping; it does not produce the right string. Use Intl to format, then isolate: Intl.NumberFormat('he IL', { style: 'currency', currency: 'ILS' }) for shekel amounts and Intl.DateTimeFormat('he IL') for dates, and wrap the output in <bdi (or unicode bidi: isolate ) if it sits inline in Hebrew prose. Do not force dir="ltr" on he IL output: the currency string carries its own right to left marks (U+200F) and is laid out for an RTL context, so forcing LTR moves the ₪ to the other side of the number. The distinction: force dir="ltr" on raw digit strings such as phone numbers, which carry no direction marks, and only isolate Intl output, which does. Devs commonly conflate the two and apply bidi fixes to a formatting bug (or vice versa). Hebrew dates need the calendar extension. Intl.DateTimeFormat('he IL') resolves to the Gregorian calendar ( resolvedOptions().calendar === 'gregory' ), so a Hebrew locale alone will not give you a Hebrew date. Request the calendar through the u ca Unicode extension: Intl.DateTimeFormat('he IL u ca hebrew') formats 20 September 2026 as 9 בתשרי 5787 . Use it for holiday, yahrzeit, and dual date displays, and keep the Gregorian format for anything users file with an authority. Form inputs need dir="auto" . Put dir="auto" on every <input and <textarea so each value resolves its own base direction. This is the most visible end user RTL bug: an email or an English word typed into a Hebrew form jumps to the wrong side without it. Note that the placeholder does not trigger auto detection, so set the resting direction with CSS if the empty field look matters. Two exceptions. A type="tel" input is already LTR by default (the HTML spec gives a tel input without dir an LTR directionality), so leave dir off it. For email and url fields most values are Latin, so set dir="ltr" deliberately: with dir="auto" a value that starts with Hebrew, such as an address on the Hebrew script domain .ישראל , flips the field to RTL mid typing. Weigh that against the cost: dir="ltr" also left aligns a Hebrew placeholder. <bdi vs <bdo : use <bdo dir="ltr" only when you want to force a direction (it overrides the bidi algorithm). For user generated or unknown direction content, prefer <bdi , which isolates the content so its direction is auto detected and cannot leak into the surrounding text: Pair dir with lang on embedded runs. dir only fixes visual order. Mark an English term inside Hebrew prose as <span lang="en" dir="ltr" : speech synthesizers and Braille translators use the language tag to switch language mode, and :lang() font and hyphenation rules key on it. Without it, assistive tech has no signal to leave Hebrew mode for that run. For free text fields, dir="auto" (or unicode bidi: plaintext in CSS) lets the browser pick the base direction per value, which is the correct default for comments, names, and search queries where you do not know the language in advance. Shadows and gradients do not auto flip. CSS logical properties mirror layout, but box shadow , text shadow , and linear gradient offsets/angles are physical and stay fixed when direction flips. A shadow offset of 4px 4px that looks correct in LTR will point the "wrong" way relative to an RTL layout. The same physical not logical trap applies to transform origin , background position , and translateX based keyframe animations (slide in drawers, carousels, progress shimmer). Flip each explicitly with a :dir(rtl) (or [dir="rtl"] ) override when its direction is meaningful. Step 4: Mirror Directional Icons Icon mirroring is one of the highest frequency RTL bugs. The rule: mirror icons whose meaning is tied to reading direction, leave everything else alone. Mirror these (their direction encodes "forward/back/next/previous" relative to reading order): Navigation arrows, back/forward buttons, breadcrumb chevrons "Send" / submit arrows, carousel and pagination arrows Indentation, list nesting, and reply arrows Progress indicators that imply forward motion Do NOT mirror these (mirroring makes them wrong or unrecognizable): Logos and brand marks Checkmarks and X / close icons Media play buttons (a play button always points right, it refers to the timeline, not reading direction) Clocks and analog time icons (clockwise is universal) Icons depicting real world objects with a fixed orientation (a phone handset, a magnifying glass with the handle, most product icons) Technique, mirror with a horizontal flip transform: Many icon sets (for example Material Symbols) already ship RTL aware variants, prefer those over flipping when available, because a flipped icon can mis render fine detail or embedded text. Step 5: Hebrew Typography Recommended font stack: Typography settings: Letter spacing is a Hebrew emphasis device, so do not ban it globally. Keep running text at letter spacing: normal , but Hebrew typography deliberately uses letter spacing (tracking) to emphasise names, terms, and concepts. A blanket letter spacing: 0 !important reset in an RTL theme strips that legitimate emphasis. Hebrew has no letter case. The widely used modern scripts with case are Latin, Greek, Armenian, and Cyrillic, Hebrew is not among them, so text transform: uppercase / capitalize and font variant: small caps are no ops on Hebrew letters. They are not no ops on the Latin words embedded in the same element: a shared design system button or heading that uppercases its label leaves the Hebrew untouched while shouting "GMAIL" or "PDF" beside it. Remove case transforms from the RTL theme rather than assuming they do nothing. Nikkud and cantillation marks need vertical room. Vowel points are combining marks that sit below or above the base letter and enlarge the effective glyph box. A tight line height (1.2 or less) clips them or collides them with the line above, which is the practical reason for the 1.7 recommended here. Some Latin first webfonts also ship Hebrew letters without the nikkud glyphs, so test any pointed text (liturgy, children's content, dictionaries) in the actual font before shipping. Step 6: Framework Specific Setup Tailwind CSS RTL (v4, current; logical utilities since v3.3): Prefer logical property utilities over rtl: / ltr: variants: Physical class Logical class CSS property ml 4 ms 4 margin inline start mr 4 me 4 margin inline end pl 4 ps 4 padding inline start pr 4 pe 4 padding inline end left 4 inset s 4 (was start 4 ) inset inline start right 4 inset e 4 (was end 4 ) inset inline end rounded l lg rounded s lg border start start radius + border end start radius rounded r lg rounded e lg border start end radius + border end end radius Reserve rtl: / ltr: variants only for cases logical properties cannot handle (e.g., directional icons, transforms). Some utilities have no logical form and stay physical in v4: translate x (slide in drawers and sheets), origin left / origin right , and bg linear to r / bg linear to l . Pair each with an rtl: override, for example translate x full rtl:translate x full for a drawer that enters from the start edge. Tailwind v4 note: v4 (GA since early 2025, currently v4.3) uses CSS first configuration ( @import "tailwindcss" in CSS) instead of tailwind.config.js . Logical utilities work identically in both v3 and v4. As of v4.2 (February 2026) the logical inset utilities start / end are deprecated in favor of inset s / inset e (the old names still work; no removal date has been announced); the margin/padding utilities ms / me / ps / pe are unaffected. Next.js App Router: next/font self hosts the font (no external Google Fonts requests, zero layout shift). React with MUI: Current MUI (v9 as of 2026) uses the official fork @mui/stylis plugin rtl , not the older community stylis plugin rtl package. The official fork fixes CSS layers issues and supports current Stylis versions; this has been the recommended setup since MUI v6. In the Next.js App Router, pass the same options to MUI's AppRouterCacheProvider (from @mui/material nextjs/v16 appRouter , matching your Next.js major) instead of building a separate cache: <AppRouterCacheProvider options={{ key: 'muirtl', stylisPlugins: [prefixer, rtlPlugin] }} , with <ThemeProvider theme={theme} inside it. Its options prop is passed straight to Emotion's createCache . @mui/stylis plugin rtl exposes only a default export, so a named import { rtlPlugin } compiles but yields undefined , and Emotion silently skips the plugin: the app looks LTR with no error. Confirm the exact import name and setup against the current MUI RTL guide (https://mui.com/material ui/customization/right to left/) for your MUI version. Portalled UI (modals, dropdowns, tooltips, toasts). Components rendered through a portal (React createPortal , Radix, MUI Menu, Floating UI) mount at document.body and inherit direction from there, but many libraries assume LTR. Set dir on <html AND pass the libr