<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: amine ben moussa</title>
    <description>The latest articles on DEV Community by amine ben moussa (@amine_benmoussa_5d7d5a82).</description>
    <link>https://dev.to/amine_benmoussa_5d7d5a82</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2214822%2Fbcd59a8d-48ec-4543-a4f9-cb3226ce4f98.jpg</url>
      <title>DEV Community: amine ben moussa</title>
      <link>https://dev.to/amine_benmoussa_5d7d5a82</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amine_benmoussa_5d7d5a82"/>
    <language>en</language>
    <item>
      <title>RTL Is Not Just dir="rtl": What I Learned Shipping Vue Templates for the Arab Web</title>
      <dc:creator>amine ben moussa</dc:creator>
      <pubDate>Wed, 06 May 2026 08:39:00 +0000</pubDate>
      <link>https://dev.to/amine_benmoussa_5d7d5a82/rtl-is-not-just-dirrtl-what-i-learned-shipping-vue-templates-for-the-arab-web-1gbb</link>
      <guid>https://dev.to/amine_benmoussa_5d7d5a82/rtl-is-not-just-dirrtl-what-i-learned-shipping-vue-templates-for-the-arab-web-1gbb</guid>
      <description>&lt;p&gt;I spent the last several months building Vue 3 templates with native RTL support. Along the way I learned that "RTL support" is one of the most overpromised, underdelivered claims in the frontend ecosystem.&lt;/p&gt;

&lt;p&gt;Most templates that advertise Arabic or RTL support do roughly this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rtl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ship it. Tweet it. Add a flag emoji to the README.&lt;/p&gt;

&lt;p&gt;Then a real Arabic-speaking user opens the page and within 5 seconds notices the gradient flowing the wrong way, the chevron icon pointing into a wall, the date showing as &lt;code&gt;12/05/2024&lt;/code&gt; when their entire region writes it as &lt;code&gt;٢٠٢٤/٠٥/١٢&lt;/code&gt;, and a card layout where the avatar floats off into nothingness because someone wrote &lt;code&gt;margin-left: 16px&lt;/code&gt; six months ago and forgot.&lt;/p&gt;

&lt;p&gt;This article is about &lt;strong&gt;what actually breaks&lt;/strong&gt;, why a 2-line CSS rule cannot fix it, and the concrete patterns I now use in production with Vue 3 and Tailwind v4.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Illusion: dir="rtl" Is Step Zero, Not the Solution
&lt;/h2&gt;

&lt;p&gt;Setting &lt;code&gt;dir="rtl"&lt;/code&gt; on the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag does flip text direction. That is approximately 15% of the work.&lt;/p&gt;

&lt;p&gt;The other 85% lives in things the browser cannot guess:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Directional icons&lt;/strong&gt;: chevrons, arrows, "send" buttons, breadcrumb separators&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradients with direction&lt;/strong&gt;: &lt;code&gt;linear-gradient(to right, ...)&lt;/code&gt; is now wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transforms&lt;/strong&gt;: &lt;code&gt;translateX(-100%)&lt;/code&gt; slides the wrong way&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animations&lt;/strong&gt;: a sidebar that slides in from the left is now coming out of the screen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Box shadows with offsets&lt;/strong&gt;: &lt;code&gt;box-shadow: 4px 0 ...&lt;/code&gt; casts the shadow in the wrong direction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Border radius on specific corners&lt;/strong&gt;: &lt;code&gt;border-top-left-radius&lt;/code&gt; is now visually wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asymmetric layouts&lt;/strong&gt;: anything with &lt;code&gt;position: absolute; left: 0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed-direction content&lt;/strong&gt;: Arabic with embedded numbers, URLs, code, brand names
None of these are fixed by &lt;code&gt;dir="rtl"&lt;/code&gt;. All of them are visible to a real user. And every single one of them is in your production code right now if you've never thought about it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Tailwind v4 Changes (Actually a Big Deal)
&lt;/h2&gt;

&lt;p&gt;If you are still on Tailwind v3 and writing &lt;code&gt;ml-4&lt;/code&gt;, &lt;code&gt;pr-2&lt;/code&gt;, &lt;code&gt;text-left&lt;/code&gt;, you are writing direction-dependent CSS. Every single one of those utilities has a logical equivalent that should be your default:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction-dependent&lt;/th&gt;
&lt;th&gt;Logical (use these)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ml-4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ms-4&lt;/code&gt; (margin-start)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mr-4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;me-4&lt;/code&gt; (margin-end)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pl-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ps-2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pr-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pe-2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-left&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text-start&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-right&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;text-end&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;left-0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;start-0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;right-0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;end-0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;border-l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;border-s&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rounded-tl-lg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rounded-ss-lg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tailwind v4 promotes these to first-class citizens with cleaner naming. The &lt;code&gt;s&lt;/code&gt; is for &lt;em&gt;start&lt;/em&gt; (left in LTR, right in RTL) and &lt;code&gt;e&lt;/code&gt; is for &lt;em&gt;end&lt;/em&gt; (right in LTR, left in RTL).&lt;/p&gt;

&lt;p&gt;The migration cost is real but mechanical. A regex sweep gets you 80% of the way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In your editor: find and replace, with regex enabled&lt;/span&gt;
&lt;span class="se"&gt;\b&lt;/span&gt;ml-          →  ms-
&lt;span class="se"&gt;\b&lt;/span&gt;mr-          →  me-
&lt;span class="se"&gt;\b&lt;/span&gt;pl-          →  ps-
&lt;span class="se"&gt;\b&lt;/span&gt;pr-          →  pe-
&lt;span class="se"&gt;\b&lt;/span&gt;text-left    →  text-start
&lt;span class="se"&gt;\b&lt;/span&gt;text-right   →  text-end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the diff carefully. Some &lt;code&gt;ml-&lt;/code&gt; should &lt;em&gt;not&lt;/em&gt; become &lt;code&gt;ms-&lt;/code&gt; (e.g., when the design genuinely requires "left" regardless of direction, like a dedicated LTR debug panel). But those cases are rare and identifiable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vue 3: Reactive Direction Without Page Reload
&lt;/h2&gt;

&lt;p&gt;Switching language should never require a reload. Here's the minimum viable setup using &lt;code&gt;vue-i18n&lt;/code&gt; plus a composable that exposes the direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// composables/useDirection.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;computed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useI18n&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-i18n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RTL_LOCALES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;he&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fa&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useDirection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useI18n&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;RTL_LOCALES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rtl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ltr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isRTL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rtl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isRTL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your root component (or a Nuxt plugin):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;watchEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useDirection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;~/composables/useDirection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDirection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;watchEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the entire infrastructure. The &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag updates reactively, Tailwind's logical utilities respond, and your components don't need to know about direction — they just use &lt;code&gt;ms-*&lt;/code&gt; and &lt;code&gt;pe-*&lt;/code&gt; and trust the cascade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Directional Icons: The Pattern That Saves You
&lt;/h2&gt;

&lt;p&gt;Icons that point in a direction (arrows, chevrons, send buttons, breadcrumb separators) need to flip in RTL. Doing this with conditional rendering is verbose. Doing it with CSS is cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center gap-2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Continue
    &lt;span class="nt"&gt;&amp;lt;ChevronRightIcon&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"h-4 w-4 rtl:rotate-180"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tailwind v4 ships RTL variants out of the box. &lt;code&gt;rtl:rotate-180&lt;/code&gt; applies only when &lt;code&gt;dir="rtl"&lt;/code&gt; is in scope. Same for &lt;code&gt;ltr:&lt;/code&gt; if you need to scope something to LTR only.&lt;/p&gt;

&lt;p&gt;For SVG icons that come from a library, this works for most. For icons with internal asymmetric details (like a "user with arrow" combined glyph), you may need to swap the asset entirely. But for the 90% case of simple directional indicators, &lt;code&gt;rtl:rotate-180&lt;/code&gt; is the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Non-CSS Trap: Numbers, Dates, Currency
&lt;/h2&gt;

&lt;p&gt;This is the section that separates real Arabic support from fake Arabic support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Numerals
&lt;/h3&gt;

&lt;p&gt;Arabic-speaking users in many regions read both Western numerals (&lt;code&gt;123&lt;/code&gt;) and Arabic-Indic numerals (&lt;code&gt;١٢٣&lt;/code&gt;). Some prefer one over the other depending on context. Hardcoding &lt;code&gt;123&lt;/code&gt; in your dashboard charts will look correct to many users, but &lt;code&gt;2,500.50 SAR&lt;/code&gt; looks alien when displayed alongside Arabic text — the number reads in one direction, the currency code in another.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;Intl.NumberFormat&lt;/code&gt; with locale awareness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatCurrency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NumberFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;formatCurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2500.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar-SA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SAR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// "٢٬٥٠٠٫٥٠ ر.س.‏"&lt;/span&gt;

&lt;span class="nf"&gt;formatCurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2500.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// "$2,500.50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser knows where the currency symbol goes, which separators to use, and which numeral system fits the locale. Don't reinvent this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dates
&lt;/h3&gt;

&lt;p&gt;Same story. &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; handles the entire Arabic, Persian, Hebrew, Hindi calendar landscape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar-SA&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dateStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;// "٢٠ ربيع الآخر ١٤٤٧ هـ"  (Hijri calendar, used in Saudi Arabia)&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ar-EG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dateStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;// "٦ نوفمبر ٢٠٢٥"  (Gregorian calendar, but Arabic numerals + Arabic month names)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, the same Arabic-speaking user in Riyadh and Cairo on the same day will see two different dates, because they use different calendars by default. Hardcoding &lt;code&gt;format: 'DD/MM/YYYY'&lt;/code&gt; is a tell to your users that you didn't think about them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Currency placement
&lt;/h3&gt;

&lt;p&gt;The Saudi Riyal symbol (ر.س) goes after the number in some contexts and before in others. The browser handles this. Stop fighting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed-Direction Content (the BiDi Problem)
&lt;/h2&gt;

&lt;p&gt;Arabic mixed with English, numbers, URLs, or code is where the Bidirectional Algorithm enters your life. By default, browsers handle this reasonably with the Unicode BiDi algorithm — but ambiguous cases break.&lt;/p&gt;

&lt;p&gt;Example: a user-generated comment in Arabic that contains a phone number with a dash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"اتصل بي على 555-1234 الآن"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without explicit BiDi marks, the dash and digits can render in unexpected order. Use Unicode Right-to-Left and Left-to-Right marks (&lt;code&gt;\u200F&lt;/code&gt; and &lt;code&gt;\u200E&lt;/code&gt;) or, in CSS, the &lt;code&gt;unicode-bidi&lt;/code&gt; and &lt;code&gt;direction&lt;/code&gt; properties on inline spans.&lt;/p&gt;

&lt;p&gt;In practice, for the 95% case where you're rendering trusted content, browsers do this correctly. For user-generated content with embedded LTR sequences, test on actual devices with actual users.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Migration Path That Works
&lt;/h2&gt;

&lt;p&gt;If you have an existing Vue project that you want to make properly RTL-friendly, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audit your spacing utilities.&lt;/strong&gt; Run that regex sweep from &lt;code&gt;ml-/mr-&lt;/code&gt; to &lt;code&gt;ms-/me-&lt;/code&gt;. Review the diff. Ship it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit your text alignment.&lt;/strong&gt; &lt;code&gt;text-left&lt;/code&gt; to &lt;code&gt;text-start&lt;/code&gt;. Same routine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit your transforms and animations.&lt;/strong&gt; Look for &lt;code&gt;translateX&lt;/code&gt;, &lt;code&gt;translate-x-*&lt;/code&gt;, &lt;code&gt;slide-from-left&lt;/code&gt; keyframes. Add &lt;code&gt;rtl:&lt;/code&gt; variants or rewrite using logical positioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit your icons.&lt;/strong&gt; Search for chevron, arrow, send, share icons. Add &lt;code&gt;rtl:rotate-180&lt;/code&gt; where direction matters semantically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace hardcoded date and number formatting.&lt;/strong&gt; Move everything to &lt;code&gt;Intl.NumberFormat&lt;/code&gt; and &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; with locale awareness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a locale switcher and test the whole app at &lt;code&gt;dir="rtl"&lt;/code&gt;&lt;/strong&gt; with a real Arabic translation, not Lorem Ipsum.
That last point matters. The Latin-script Lorem Ipsum used in development hides 90% of the issues that show up with actual Arabic glyphs, line height, and word breaking. Test with real translated content.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Closing: This Is Plumbing, Not Marketing
&lt;/h2&gt;

&lt;p&gt;The reason most templates ship broken RTL is not malice or laziness — it's that the developers never spent a day using their own template in Arabic. If you have, you'll fix the issues. If you haven't, you'll keep shipping &lt;code&gt;dir="rtl"&lt;/code&gt; and wondering why the Saudi market doesn't convert.&lt;/p&gt;

&lt;p&gt;If you want a starting point that has already gone through this audit (Vue 3, Tailwind v4, 7 languages, real Arabic content tested by real Arabic speakers), I built &lt;a href="https://qalamui.com" rel="noopener noreferrer"&gt;QalamUI&lt;/a&gt; for exactly this reason. There's also a free live demo if you want to poke at the components.&lt;/p&gt;

&lt;p&gt;But more than anything: the next time you read "RTL support" on a template page, ask the seller to send you a screenshot of the dashboard with &lt;code&gt;dir="rtl"&lt;/code&gt; and Arabic content. The answer will tell you everything.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this article saved you a debugging session, drop a comment with what you're building for the Arabic-speaking web. I'm collecting case studies of teams shipping in MENA and would love to learn from yours.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vue</category>
      <category>tailwindcss</category>
      <category>i18n</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
