دليل RTL في Tailwind CSS: الخصائص المنطقية والمتغيّر rtl:
ابنِ واجهات RTL باستخدام Tailwind CSS 4 وnext-intl: ضبط dir وlang لكل لغة، والأدوات المنطقية، والمتغيّر rtl:، والخطوط العربية، وقائمة اختبار.

هذا دليل عملي لدعم RTL في Tailwind CSS 4: كيف تجعل مجموعة واحدة من المكوّنات تعمل بالإنجليزية التي تُقرأ من اليسار إلى اليمين، وبالعربية التي تُقرأ من اليمين إلى اليسار، في مشروع Next.js يعتمد App Router وnext-intl. المقتطفات البرمجية مأخوذة من قوالب Aniq UI التي تأتي بالإنجليزية والعربية، وكل صنف Tailwind مذكور هنا راجعناه في توثيق Tailwind CSS 4.3 وفي مُترجِمه.
الفكرة كلها في جملة واحدة: ضع dir على <html>، واكتب المسافات والمواضع بالأدوات المنطقية (ms-4 لا ml-4)، واترك المتغيّر rtl: للحالات القليلة التي لا تعبّر عنها الخصائص المنطقية. وبقية الصفحة تشرح ما تتركه هذه الجملة: ما ينقلب وحده، وما يحتاج إلى متغيّر، وما يجب ألا ينقلب أبدًا، وما يختلف في الخط العربي، وكيف تختبر ذلك كله.
1. ضبط lang وdir على <html> لكل لغة
كل ما في هذا الدليل يعتمد على سمة واحدة. الخصائص المنطقية تقرأ اتجاه العنصر، والمتغيّر rtl: في Tailwind يبحث عنه، وقارئات الشاشة تستخدم lang لاختيار الصوت. ومكانها الصحيح هو <html> لا حاوية داخل <body>: فالنوافذ الحوارية والقوائم والإشعارات تُعرض غالبًا في portal مباشرة تحت <body>، ولا ترث الاتجاه إلا إذا كان مضبوطًا فوقها.
مع next-intl وApp Router، أنظف طريقة أن يتولّى app/[locale]/layout.tsx عرض <html> و<body>، فيرسل الخادم الاتجاه الصحيح من أول بايت في HTML. الملفات التالية من قالب صفحة الهبوط في Aniq UI بعد اختصارها إلى ما يهمّنا. أولًا التوجيه والـ proxy (غيّر Next.js 16 اسم middleware.ts إلى proxy.ts، وفي Next.js 15 يوضع الكود نفسه في middleware.ts):
// src/i18n/routing.ts
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["en", "ar"],
defaultLocale: "en",
});
// src/proxy.ts (named middleware.ts before Next.js 16)
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
// Skip /api, /_next, /_vercel and any path with a dot (files)
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};
إعداد الطلب يحمّل رسائل اللغة الحالية. القالب يحمّل مجلدًا لكل صفحة، وهنا ملف واحد لكل لغة للاختصار:
// src/i18n/request.ts
import { getRequestConfig } from "next-intl/server";
import { hasLocale } from "next-intl";
import { routing } from "./routing";
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
};
});
دالة صغيرة تحوّل اللغة إلى اتجاه، وهي من قالب العقارات في Aniq UI. وإذا أضفت العبرية أو الفارسية أو الأردية لاحقًا، فهذا هو المكان الوحيد الذي تعدّله:
// src/config/i18n.ts
export const directionOf = (locale: string): "rtl" | "ltr" =>
locale === "ar" ? "rtl" : "ltr";
ثم يعرض تخطيط اللغة السمتين من الخادم:
// src/app/[locale]/layout.tsx
import { NextIntlClientProvider, hasLocale } from "next-intl";
import { getMessages, setRequestLocale } from "next-intl/server";
import { notFound } from "next/navigation";
import { routing } from "@/i18n/routing";
import { directionOf } from "@/config/i18n";
import "@/styles/globals.css";
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
if (!hasLocale(routing.locales, locale)) notFound();
// Enable static rendering
setRequestLocale(locale);
const messages = await getMessages();
return (
<html lang={locale} dir={directionOf(locale)} suppressHydrationWarning>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
ويحتفظ القالب بتخطيط جذري يمرّر محتواه فقط، لأن كل الصفحات تقع تحت [locale]:
// src/app/layout.tsx
// Every page lives under [locale], whose layout renders <html> and <body>.
export default function RootLayout({ children }: { children: React.ReactNode }) {
return children;
}
ويغلّف next.config الإعداد بـ createNextIntlPlugin("./src/i18n/request.ts"). ولتفاصيل Next.js 16 (root params والتخزين المؤقت) اقرأ دليلنا عن next-intl وNext.js 16.
لماذا لا نضبط dir في المتصفح؟
بعض التطبيقات تضبط document.documentElement.dir داخل useEffect. عندها يصل HTML من الخادم باتجاه من اليسار إلى اليمين، فتُرسم الصفحة كذلك ثم تنقلب بعد الـ hydration: قفزة ظاهرة في كل تحميل لصفحة عربية. وإذا كان لا بد أن يبقى <html> في تخطيط جذري فوق مقطع [locale]، فالبديل سكربت صغير مضمَّن في <head> يقرأ أول مقطع من المسار ويضبط lang وdir قبل أول رسم للصفحة، وهذا ما تفعله واجهتا المتجر الإلكتروني ومنصة التعليم في Aniq UI. أما ما يجب تجنّبه فهو الـ effect الذي يعمل بعد الـ hydration.
2. الأدوات المنطقية في Tailwind CSS 4
الأداة الفيزيائية تسمّي جهة من الشاشة. ml-4 تعني margin-left في اللغتين، فالمسافة بين الأيقونة ونصّها في الإنجليزية تنتهي في الجهة الخطأ بالعربية. أما الأداة المنطقية فتسمّي جهة من النص. ms-4 تعني margin-inline-start، أي اليسار في الإنجليزية واليمين في العربية: صنف واحد، واتجاهان، ولا حاجة إلى أي تعديل. وTailwind CSS يوفّرها منذ الإصدار 3.3، والإصدار 4 يوسّعها.
| فيزيائية (جهة ثابتة) | منطقية (تتبع dir) | خاصية CSS |
|---|---|---|
ml-4 / mr-4 | ms-4 / me-4 | margin-inline-start / margin-inline-end |
pl-4 / pr-4 | ps-4 / pe-4 | padding-inline-start / padding-inline-end |
left-0 / right-0 | inset-s-0 / inset-e-0 (أو start-0 / end-0) | inset-inline-start / inset-inline-end |
text-left / text-right | text-start / text-end | text-align: start / end |
border-l / border-r | border-s / border-e | border-inline-start-width / border-inline-end-width |
rounded-l-lg / rounded-r-lg | rounded-s-lg / rounded-e-lg | الزاويتان في جهة البداية أو النهاية |
rounded-tl-lg | rounded-ss-lg | border-start-start-radius |
float-left / float-right | float-start / float-end | float: inline-start / inline-end |
scroll-ml-4 | scroll-ms-4 | scroll-margin-inline-start |
start-* أم inset-s-*؟ أضاف Tailwind CSS 4.2 الأدوات inset-s-* وinset-e-* وinset-bs-* وinset-be-*، وجعل start-* وend-* مهجورتين لصالحها. لكن الاسمين القديمين ما زالا يُترجَمان إلى inset-inline-start نفسها (تحققنا من ذلك في الإصدار 4.3.3)، وقوالب Aniq UI ما زالت تستخدم start-*، فلا داعي للاستعجال في إعادة الكتابة. ويمكن للكود الجديد أن يستخدم inset-s-*. وأضاف الإصدار 4.2 أيضًا أدوات المحور الكتلي مثل mbs-* وpbs-* وborder-bs-*، وهي تخص أنماط الكتابة العمودية لا العربية، فالعربية تُكتب أفقيًا.
ما يتبع الاتجاه أصلًا
جزء كبير من التخطيط ينقلب من دون أي صنف جديد. فـ flexbox وgrid يرتّبان العناصر على المحور السطري، لذا يبدأ صف flex من اليمين في العربية، وتتبعه justify-start وjustify-end وorder-*. وmx-* وpx-* وinset-x-* تضبط الجهتين معًا. وفي Tailwind CSS 4 تُكتب space-x-* وdivide-x بالخصائص المنطقية أيضًا. والقيمة الافتراضية لـ text-align هي start، فالنص الذي لم تحدد محاذاته صحيح أصلًا. هذا ما يُخرجه المُترجِم:
/* ms-4 */
.ms-4 {
margin-inline-start: calc(var(--spacing) * 4);
}
/* space-x-4: logical in Tailwind CSS 4, so it follows the direction */
:where(.space-x-4 > :not(:last-child)) {
--tw-space-x-reverse: 0;
margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
}
/* translate-x is physical: -100% always means "towards the left" */
.-translate-x-full {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
القاعدة العملية: لا تضف أبدًا flex-row-reverse أو rtl:flex-row-reverse "لإصلاح" العربية. فالصف انقلب بالفعل عندما تغيّر الاتجاه، وقلبه مرة ثانية يعيده إلى ترتيب الإنجليزية. ولاحظ الجزء الأخير: translate-x فيزيائية، ولهذا تعود في القسم التالي.
حقل إدخال بأيقونة
حقل النص في قالب العقارات من Aniq UI يُظهر الفكرة في مكوّن واحد. الأيقونة موضوعة بـ start-4 والنص يأخذ ps-11، فينتقل الاثنان إلى اليمين في العربية من دون أي تعديل:
// Input.tsx (trimmed): the icon sits at the inline start,
// and the text gets padding on the same side
<div className="relative">
{startIcon && (
<span className="pointer-events-none absolute start-4 top-1/2 -translate-y-1/2">
{startIcon}
</span>
)}
<input
className={cn(
"h-12",
startIcon && "ps-11",
// A left-to-right field (a phone number) on a right-to-left page
// starts on the other side, so the icon's side needs the same room.
startIcon && props.dir === "ltr" && "rtl:pe-11",
className,
)}
{...props}
/>
</div>
الشرط الأخير يعالج حالة دقيقة نشرحها في القسم 3: حقل هاتف بـ dir="ltr" داخل صفحة عربية.
الفواصل والحواف
الخطوط بين العناصر، وعلامات الشريط الجانبي، وحافة عمود في التذييل، هي الأماكن التي يختبئ فيها border-l عادة. والقالب نفسه يستخدم border-s في كل مكان:
// A row of facts: a rule before every item except the first
<div className="flex flex-col items-center gap-1.5 border-s border-[var(--border)] px-2 first:border-s-0">
// A footer column: rule and padding on the side the text starts from
<div className="xl:border-s xl:border-[var(--border)] xl:ps-10 xl:pe-11">
متى تفشل الأدوات الفيزيائية، ومتى تكون صحيحة
هذه أكثر الأصناف الفيزيائية التي تبدو سليمة بالإنجليزية ثم تفشل بالعربية:
ml-autoلدفع زر إلى نهاية الصف. استخدمms-auto.pl-10لترك مكان لأيقونة البحث. استخدمps-10، مع وضع الأيقونة فيstart-3أوinset-s-3.left-0أوright-1لشارة أو زر إغلاق. استخدم الصيغ المنطقية من inset.text-leftفي خلايا الجداول وتسميات النماذج. استخدمtext-start، أو لا شيء.rounded-l-mdعلى أول زر في مجموعة أزرار. استخدمrounded-s-md.border-l-2كعلامة للعنصر النشط في الشريط الجانبي. استخدمborder-s-2.
وتبقى الأدوات الفيزيائية صحيحة حين يكون ما تضعه فيزيائيًا. فـ left-1/2 -translate-x-1/2 يوسّط العنصر في الاتجاهين، وinset-0 وinset-x-0 متناظرتان. وشريط المقارنة "قبل وبعد" في قالب العقارات يُبقي تسمية "الأصل" في left-4 و"بعد التأثيث" في right-4 في اللغتين، لأن الشريط كما يقول تعليقه مقارنة فيزيائية بين قبل وبعد، لا ترتيب قراءة. قرّر لكل عنصر على حدة، واكتب السبب في تعليق حتى لا "يصلحه" من يأتي بعدك.
3. المتغيّران rtl: وltr:
الخصائص المنطقية تغطي المسافات والمواضع والحدود والزوايا والمحاذاة، لكنها لا تغطي التحويلات والتدرّجات ونقطة أصل التحويل والـ keyframes ومعنى الأيقونة. لهذه الحالات يوفّر Tailwind المتغيّرين rtl: وltr:. وهذا ما يُخرجه اثنان منهما في Tailwind CSS 4.3:
/* rtl:-scale-x-100 */
.rtl\:-scale-x-100:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) {
--tw-scale-x: calc(100% * -1);
scale: var(--tw-scale-x) var(--tw-scale-y);
}
/* ltr:-translate-x-full */
.ltr\:-translate-x-full:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
هناك تفصيلتان مهمتان. المحدِّد مغلّف بـ :where()، فلا يضيف أي أولوية (specificity): صنف rtl: يتغلّب على الصنف الأساسي فقط لأنه يأتي لاحقًا في ملف الأنماط (وقواعد ltr: تأتي قبل قواعد rtl:). وهو يطابق بثلاث طرق: الاتجاه المحسوب للعنصر نفسه، أو سمة dir="rtl" على العنصر، أو أي عنصر أب يحمل dir="rtl". تذكّر الطريقة الأخيرة عند الحديث عن الجزر أدناه.
الأيقونات الاتجاهية
السهم الذي يعني "التالي" يشير إلى اليمين في الإنجليزية وإلى اليسار في العربية. ويُظهر قالبان من Aniq UI الطريقتين المعتادتين لقلبه:
// An arrow at the end of a button: mirrored in Arabic
endIcon={<IconArrowRight aria-hidden className="h-5 w-5 rtl:-scale-x-100" />}
// A back link: the same result with a rotation
<IconArrowLeft size={16} className="me-1 rtl:rotate-180" />
rtl:-scale-x-100 صورة مرآة حقيقية. أما rtl:rotate-180 فيقلب الأيقونة رأسًا على عقب أيضًا، وهذا لا يظهر على سهم أو شيفرون متناظر لكنه خطأ في كل ما له أعلى وأسفل (سهم "رد" منحنٍ، أو أيقونة لها ذيل). وعند الشك اختر المرآة.
اقلب أسهم الرجوع والتقدّم، والشيفرونات في مسار التنقل والترقيم، وروابط "عرض الكل"، وكل ما يدل على حركة إلى الأمام. وإرشادات Material Design للاتجاه الثنائي لا تقلب أزرار تشغيل الوسائط ولا شريط تقدّمها، لأنها تدل على اتجاه الشريط لا اتجاه القراءة، وتترك الساعات وأيقونات التحديث باتجاه عقارب الساعة كما هي، لأن الساعات تدور مع عقاربها في كل اللغات. أما الشعارات فلا تنقلب أبدًا.
التحويلات وtranslate-x
translate-x فيزيائية، لذا يحتاج كل ما ينزلق خارج الشاشة إلى متغيّر. قائمة الجوال في قالب منصة التعليم من Aniq UI موضوعة بأدوات منطقية (start-0 وrounded-e-xl) وتختفي بإزاحة تراعي الاتجاه:
// A mobile menu that slides in from the inline start
<div
className={cn(
"absolute top-0 start-0 h-full w-64 rounded-e-xl transition-transform duration-300",
isOpen ? "translate-x-0" : "ltr:-translate-x-full rtl:translate-x-full",
)}
>
والتوسيط هو الحالة الشائعة الأخرى. فإذا وُضع العنصر في start-1/2 وجب أن تنقلب إشارة إزاحة نصف العرض في العربية، وإذا وُضع في left-1/2 فلا حاجة إلى أي متغيّر. السطران التاليان من قالبي المطعم والمتجر الإلكتروني في Aniq UI:
// start-1/2 moves with the direction, so the half-width shift has to as well
"absolute -top-12 start-1/2 h-10 w-10 ltr:-translate-x-1/2 rtl:translate-x-1/2 rounded-full"
// left-1/2 with -translate-x-1/2 centres in both directions, no variant needed
"absolute bottom-6 left-1/2 -translate-x-1/2"
اتجاه التدرّجات
لا يملك Tailwind CSS 4.3 اتجاه تدرّج منطقيًا: الأدوات الموثّقة هي bg-linear-to-r وbg-linear-to-l وبقية الجهات الفيزيائية، إضافة إلى الزوايا مثل bg-linear-65، ولا شيء منها ينقلب. لذا تحتاج الطبقة الداكنة خلف النص إلى المتغيّر. وقالب المتجر الإلكتروني يستخدم الأسماء القديمة bg-gradient-to-* التي ما زالت تعمل في الإصدار 4:
// A banner scrim: dark behind the text, transparent towards the picture
<div className="absolute inset-0 bg-gradient-to-r from-black/85 via-black/45 to-transparent rtl:bg-gradient-to-l" />
// The same with the Tailwind CSS 4 names
<div className="absolute inset-0 bg-linear-to-r from-black/85 to-transparent rtl:bg-linear-to-l" />
نقطة أصل التحويل
origin-left فيزيائية كذلك. شريط التقدّم الذي يكبر بـ scale-x يجب أن يبدأ من الجهة التي يبدأ منها النص، كما في معرض الوجبات في قالب المطعم:
// A progress bar that grows from the side the text starts on
"absolute inset-x-0 bottom-0 h-1 bg-[var(--primaryColor)] ltr:origin-left rtl:origin-right"
حركات الدخول الجانبية
الـ keyframes لا تقرأ أصناف الأدوات، لذا تحتاج اللوحة التي تنزلق إلى الشاشة إلى keyframe لكل اتجاه. اللوحة الجانبية في قالب العقارات موضوعة في inset-y-0 end-0 وتبدّل اسم الحركة تحت [dir="rtl"]:
/* A sheet enters from the inline end: the right in English, the left in Arabic */
.animate-sheet-in {
animation: velra-sheet-in-ltr 240ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
[dir="rtl"] .animate-sheet-in {
animation-name: velra-sheet-in-rtl;
}
@keyframes velra-sheet-in-ltr {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes velra-sheet-in-rtl {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
أسهم لوحة المفاتيح
الاتجاه سلوك أيضًا. في قائمة تبويبات أو شريط تمرير، يجب أن ينقل سهم اليمين في لوحة المفاتيح إلى العنصر التالي على الشاشة، وهو على اليسار في العربية. وتبويبات لوحة تحكم المطعم تبدّل المفتاحين:
// Arrow keys follow what the user sees, not the DOM order
const isRtl = useLocale() === "ar";
const forward = vertical ? "ArrowDown" : isRtl ? "ArrowLeft" : "ArrowRight";
const backward = vertical ? "ArrowUp" : isRtl ? "ArrowRight" : "ArrowLeft";
تنبيه: rtl: يبقى فعّالًا داخل جزيرة dir="ltr"
لأن rtl: يطابق أي عنصر له أب يحمل dir="rtl"، فإنه يبقى فعّالًا داخل عنصر dir="ltr" في صفحة عربية. على هذا العنصر يتطابق ltr: (اتجاهه الخاص) وrtl: (اتجاه الأب) معًا، وتفوز قاعدة rtl: لأنها تأتي لاحقًا. وأحيانًا هذا بالضبط ما تريده: حقل الهاتف في مقتطف حقل الإدخال أعلاه يحمل dir="ltr"، فيضيف ps-11 المسافة على اليسار، بينما الأيقونة على يمين الصفحة العربية، فيضيف rtl:pe-11 المسافة على اليمين أيضًا. وإذا احتجت إلى اتجاه العنصر نفسه، فاستخدم متغيّرًا اعتباطيًا مبنيًا على الصنف الزائف :dir()، الذي تدعمه كل المتصفحات الحالية:
// Matches when this element itself is right to left,
// not when any ancestor has dir="rtl"
<IconChevronRight className="[&:dir(rtl)]:-scale-x-100" />
4. ما يجب ألا ينقلب
خوارزمية الاتجاه الثنائي في المتصفح تعرض الكلمات اللاتينية والأرقام من اليسار إلى اليمين داخل النص العربي من تلقاء نفسها. المشكلة تبدأ مع السلاسل التي تخلط الأرقام بالرموز: رقم هاتف مثل +1 555 010 2030، أو سعر مع رمز عملة، أو نطاق، أو عدّاد مثل 3 / 12. فالمسافات والرموز فيها تأخذ اتجاه النص العربي المحيط، وقد يخرج جزء من السلسلة بترتيب خاطئ. والحل أن تعزل السلسلة في جزيرة مستقلة من اليسار إلى اليمين.
الأرقام والأرقام اللاتينية
كثير من الواجهات العربية، ومنها قوالب Aniq UI المذكورة هنا، تستخدم الأرقام اللاتينية (0 إلى 9) في العربية. وهل يُخرجها Intl أم لا؟ هذا يعتمد على وسم اللغة وعلى بيانات ICU في المتصفح، لذا اطلبها صراحة بالامتداد -u-nu-latn. هذه دالة التنسيق من لوحة تحكم المطعم:
// Figures are Latin in every locale, so Arabic asks for Latin numbering explicitly.
const resolveNumberLocale = (locale: string): string =>
locale === "ar" ? "ar-u-nu-latn" : locale || "en";
export const formatNumber = (
value: number,
locale: string,
options?: Intl.NumberFormatOptions,
): string => new Intl.NumberFormat(resolveNumberLocale(locale), options).format(value);
في Node.js 24 (ICU 78) تحدد المنطقة نوع الأرقام، والامتداد يتقدّم عليها:
new Intl.NumberFormat("ar-SA").format(1248.6); // "١٬٢٤٨٫٦" (Arabic-Indic digits)
new Intl.NumberFormat("ar-SA-u-nu-latn").format(1248.6); // "1,248.6"
new Intl.NumberFormat("ar-u-nu-latn").format(1248.6); // "1,248.6"
استخدم الوسم نفسه للتواريخ والأوقات والعملات، وانتبه إلى أن Intl يضيف علامات اتجاه غير مرئية (U+200F) حول العملات والتواريخ العربية، فراعِ ذلك عند مقارنة النصوص المنسّقة في الاختبارات.
الأسعار
يغلّف قالب العقارات كل سعر بـ <bdi dir="ltr">، فيعزله عن النص العربي المحيط ويثبّت ترتيبه. وtabular-nums تجعل عرض كل الأرقام متساويًا لتصطف المبالغ في القوائم:
// Every price on the site: "AED 4,850,000" keeps its order inside Arabic text
<bdi dir="ltr" className="tabular-nums">
{formatPrice(price, currency)}
</bdi>
أرقام الهواتف والبريد الإلكتروني والروابط
أرقام الهواتف وعناوين البريد الإلكتروني والروابط ورموز اللغات والعدّادات تُكتب دائمًا من اليسار إلى اليمين. والقالب نفسه يحدّد كلًا منها، ويعطي حقل الهاتف rtl:text-end حتى يُكتب الرقم من اليسار إلى اليمين ويبقى محاذيًا للتسميات العربية فوقه:
// A phone field: typed left to right, aligned with the Arabic form
<Input
label={t("phone")}
startIcon={<IconPhone aria-hidden className="h-5 w-5" />}
type="tel"
autoComplete="tel"
dir="ltr"
className="rtl:text-end"
{...register("phone")}
/>
// An email address
<a href={`mailto:${email}`} dir="ltr" className="block truncate">{email}</a>
// The language switcher's "EN" and "AR"
<span dir="ltr">{locale.toUpperCase()}</span>
// A gallery counter such as "3 / 12"
<p aria-live="polite" dir="ltr">
{t("counter", { index: String(index + 1), total: String(images.length) })}
</p>
الشيفرة والشعارات
الشيفرة دائمًا من اليسار إلى اليمين. في المحتوى العربي اعرض <pre> و<code> السطري مع dir="ltr" (أو direction: ltr في ملف الأنماط الذي يعرض محتواك). والشعار النصي اللاتيني كذلك: اسم العلامة في قالب العقارات عنصر span يحمل dir="ltr"، فلا تتحرك حروفه ولا مسافاته أبدًا. ولا تُقلب الصور والرسوم أيضًا، فالصورة المقلوبة تُظهر نصًا معكوسًا ومنتجًا معكوسًا.
// The brand name is a Latin word mark: it never mirrors
<span dir="ltr" className="font-semibold tracking-[-0.02em]">
{BRAND_CONFIG.name.en}
</span>
المخططات ومحور الزمن
المخططات قرار تصميمي. بعض المنتجات العربية تُبقي الزمن من اليسار إلى اليمين مثل الأرقام على المحور، وبعضها يجعله من اليمين إلى اليسار ليتبع اتجاه القراءة. والخياران مقبولان، والمهم أن تحتفظ مكتبة المخططات نفسها بنظام إحداثيات ثابت من اليسار إلى اليمين. لوحة تحكم المطعم في Aniq UI تضع المخطط في حاوية dir="ltr" ثم تعكس محور الزمن في العربية عن قصد، وتنقل محور القيم إلى اليمين:
const isRtl = locale === "ar";
<div dir="ltr" role="img" aria-label={label} className="relative w-full flex-1">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data}>
<XAxis dataKey="label" reversed={isRtl} />
<YAxis orientation={isRtl ? "right" : "left"} />
{/* grid, tooltip and area */}
</AreaChart>
</ResponsiveContainer>
</div>

جزر dir="ltr" و<bdi> وdir="auto" وunicode-bidi
dir="ltr"على عنصر ينشئ جزيرة من اليسار إلى اليمين. والمتصفحات تعزل أصلًا العنصر الذي يحمل سمةdirعن النص المحيط (ملف الأنماط الافتراضي في HTML يعطيهunicode-bidi: isolate)، فنادرًا ما تحتاج إلى أكثر من ذلك.<bdi>يعزل سلسلة سطرية قد لا تعرف اتجاهها، مثل اسم مستخدم داخل جملة. أضفdir="ltr"حين تعرفه، كما في الأسعار.dir="auto"يترك للمتصفح اختيار الاتجاه بحسب أول حرف قوي. استخدمه للنصوص التي يكتبها المستخدمون، لأنها قد تكون بأي من اللغتين:
// Text written by users can be in either language: let the browser decide
<p dir="auto">{review.body}</p>
لا يملك Tailwind CSS أداة لـ unicode-bidi، والصنف isolate شيء مختلف تمامًا (isolation: isolate الذي ينشئ سياق تراكب). يمكنك كتابة خاصية اعتباطية مثل [unicode-bidi:isolate]، لكن MDN ينصح المؤلفين بعدم تعديل unicode-bidi، وسمة dir أو <bdi> تؤديان المهمة نفسها في الترميز.
5. الخطوط العربية وارتفاع السطر وتباعد الحروف
حمّل خطًا عربيًا
كثير من خطوط الواجهات اللاتينية لا تحتوي على حروف عربية، فيلجأ المتصفح إلى خط النظام وتبدو الصفحات العربية كأنها منتج آخر. حمّل خطًا لكل نظام كتابة. قالب المطعم يجمع Inter مع IBM Plex Sans Arabic عبر next/font:
// src/app/layout.tsx (trimmed)
import { Inter, IBM_Plex_Sans_Arabic } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
weight: "variable",
display: "swap",
variable: "--font-inter",
});
const plexArabic = IBM_Plex_Sans_Arabic({
subsets: ["arabic"],
weight: ["300", "400", "500", "600", "700"],
display: "swap",
variable: "--font-arabic",
});
// <html className={`${inter.variable} ${plexArabic.variable}`}>
ثم يبدّل عائلة الخط تحت [dir="rtl"]. الخط العربي أولًا ثم Inter، فالكلمات اللاتينية داخل النص العربي (الأسعار وأسماء العلامات والبريد الإلكتروني) تعود إلى Inter وتبدو كما في الصفحات الإنجليزية:
/* src/styles/base.css */
[dir="rtl"] body,
[dir="rtl"] {
font-family: var(--font-arabic), var(--font-inter), Arial, ui-sans-serif, system-ui, sans-serif;
letter-spacing: 0;
}
ويفعل قالب العقارات الشيء نفسه بملفات Noto Sans Arabic وManrope مستضافة في الموقع نفسه، كل منها معرّف بـ unicode-range، فلا تنزّل الصفحة خطًا إلا إذا احتوت نصًا بنظام كتابته. والمحدِّد على [dir="rtl"] يغني عن إضافة صنف إلى كل مكوّن.
ارتفاع السطر
للحروف العربية صواعد عالية ونوازل عميقة، والتشكيل يقع فوقها وتحتها. وارتفاع السطر المضبوط لعنوان لاتيني كبير (1.1 قيمة شائعة) قد يجعل الأسطر العربية تتلامس أو تُقصّ. لوحة تحكم المطعم توسّع عناوين صفحاتها في العربية فقط:
// A page title: tight in English, more room for Arabic
<h1 className="text-[28px] font-extrabold leading-[1.1] tracking-[-0.02em] lg:text-[36px] rtl:leading-[1.4]">
// An eyebrow label: wide tracking in English, none in Arabic
"text-[13px] font-semibold uppercase tracking-[0.16em] rtl:tracking-normal"
تباعد الحروف
العربية خط متصل، وملاحظة MDN عن letter-spacing تقولها صراحة: تباعد الحروف في العربية قد يجعل النص يبدو مكسورًا. ومن الشائع تضييق التباعد في العناوين اللاتينية وتوسيعه في التسميات الصغيرة بالأحرف الكبيرة، لذا أعده إلى الصفر بـ rtl:tracking-normal كما في سطر التسمية أعلاه (من قالب العقارات)، أو بـ letter-spacing: 0 في قاعدة [dir="rtl"]. وليس في العربية أحرف كبيرة أصلًا، فلا أثر لـ uppercase فيها: التسمية التي تعتمد على الأحرف الكبيرة والتباعد في الإنجليزية تحتاج إلى وزن خط أو لون لتبرز في العربية.
6. قائمة اختبار RTL
طبّقها على كل صفحة بالعربية، في الوضعين الفاتح والداكن، وبعرض الجوال وسطح المكتب:
- السمة موجودة في HTML القادم من الخادم. اعرض مصدر الصفحة (لا أداة الفحص) وابحث عن
<html lang="ar" dir="rtl">. ثم أعد التحميل بشبكة بطيئة وراقب أي انقلاب بعد التحميل. - التخطيط معكوس. الشريط الجانبي والشعار والتنقل الرئيسي والإجراء الرئيسي على اليمين، ولا شيء مقلوب مرتين.
- اللوحات والقوائم والإشعارات تفتح من الجهة الصحيحة وتُعرض من اليمين إلى اليسار، بما فيها ما يُعرض في portal.
- الأسهم والشيفرونات تشير إلى الاتجاه الصحيح في مسار التنقل والترقيم والعروض المتحركة وأزرار "التالي" وروابط "عرض الكل". وأزرار التشغيل والساعات لا تنقلب.
- حقول الإدخال. الأيقونات وأزرار المسح وأيقونات التحقق على اليمين، وحقول الهاتف والبريد والروابط والشيفرة تُكتب من اليسار إلى اليمين.
- الأرقام. الأرقام هي التي اخترتها، والأسعار وأرقام الهواتف تُقرأ بالترتيب الصحيح، والعدّادات مثل 3 / 12 غير مبعثرة.
- النص المختلط. اسم علامة إنجليزي داخل جملة عربية يُبقي علامات الترقيم في مكانها.
- الخط. الخط العربي يُحمَّل (تُظهر أدوات المطوّر الخط المعروض)، والعناوين لا تُقصّ، ولا يوجد تباعد حروف في أي نص عربي.
- لوحة المفاتيح. الأسهم في التبويبات وأشرطة التمرير والعروض المتحركة تتحرك كما تُقرأ الشاشة.
- المخططات تتبع الاصطلاح الذي اخترته، في كل مخطط.
- الأصناف الفيزيائية. ابحث في الكود عن أدوات اليسار واليمين وقرّر في كل نتيجة:
# List physical left/right classes in your components.
# Each hit is a question to answer, not always a bug.
grep -rnE --include='*.tsx' \
'(^|[^a-z0-9-])-?(ml|mr|pl|pr|left|right)-([0-9]|\[|\(|px|auto|full)|(^|[^a-z0-9-])(rounded|border)-(l|r|tl|tr|bl|br)([^a-z0-9]|-|$)|(^|[^a-z0-9-])(text|float)-(left|right)([^a-z0-9-]|$)' \
src
على تطبيقات Aniq UI التي جرّبناه عليها يعيد بين 2 و10 أسطر لكل تطبيق: عناصر موسّطة، وأزواج متناظرة left-0 right-0، وشريط مقارنة فيزيائي بين قبل وبعد، وأسطر عليها تعديل rtl: مسبقًا. وهذا هو حجم القائمة المطلوب: قصيرة بما يكفي لقراءة كل سطر فيها.
الأسئلة الشائعة
هل يدعم Tailwind CSS اتجاه RTL؟
نعم. في Tailwind CSS أدوات منطقية مثل ms-* وpe-* وinset-s-* وtext-start وborder-s وrounded-e-* تتبع سمة dir، إضافة إلى المتغيّرين rtl: وltr: للأنماط التي يجب أن تختلف بحسب الاتجاه. وكلاهما مدمج في الإطار.
هل أحتاج إلى إضافة RTL مع Tailwind CSS 4؟
لا. الأدوات المنطقية جزء من Tailwind CSS منذ الإصدار 3.3، والمتغيّران rtl: وltr: جزء من النواة. اضبط dir على <html> واستخدمها.
هل أستخدم start-* أم inset-s-*؟
جعل Tailwind CSS 4.2 الأداتين start-* وend-* مهجورتين لصالح inset-s-* وinset-e-*. وكلها تُترجَم إلى inset-inline-start وinset-inline-end، فالكود الحالي يستمر في العمل. استخدم inset-s-* في الكود الجديد إذا كنت على الإصدار 4.2 أو أحدث.
الأدوات المنطقية أم المتغيّر rtl:؟
الأدوات المنطقية أولًا. ms-3 تحل محل ltr:ml-3 rtl:mr-3 بصنف واحد لا يمكن أن يختل. واترك rtl: لما لا تعبّر عنه الخصائص المنطقية: الأيقونات المعكوسة، وtranslate-x، واتجاه التدرّجات، ونقطة أصل التحويل، وضبط الخط مثل ارتفاع السطر وتباعد الحروف.
كيف أقلب أيقونة في RTL باستخدام Tailwind؟
أضف rtl:-scale-x-100 إلى الأيقونة لتعكسها أفقيًا. ويعمل rtl:rotate-180 أيضًا مع الأسهم والشيفرونات المتناظرة. اقلب فقط الأيقونات التي تدل على اتجاه، أما أزرار الوسائط والساعات والشعارات فتبقى كما هي.
كيف أُبقي الأرقام اللاتينية في العربية؟
نسّق الأرقام بوسم لغة يتضمن -u-nu-latn، مثل new Intl.NumberFormat("ar-u-nu-latn") أو "ar-SA-u-nu-latn"، واستخدم الوسم نفسه للتواريخ والعملات.
هل يكفي dir="rtl" على <body> أو على حاوية div؟
ضعه على <html>. المحتوى المعروض في portal، مثل النوافذ الحوارية والإشعارات، يقع خارج حاويتك ولا يرث إلا اتجاهًا مضبوطًا على <html> أو <body>، كما أن السمة على الجذر تعطي قارئات الشاشة والمحدِّد :dir() مصدرًا واحدًا للحقيقة.
قوالب فيها RTL جاهز
كل نمط في هذا الدليل مأخوذ من كود موجود في قوالب Aniq UI. وكلها مبنية بـ Next.js وTailwind CSS 4 وnext-intl، بالإنجليزية والعربية مع تخطيط حقيقي من اليمين إلى اليسار، وعروض حية يمكنك تحويلها إلى العربية. وإذا فضّلت أن تبدأ من قاعدة كود أُنجز فيها هذا العمل، فستجد لوحات التحكم العربية التسع التي تدعم RTL مقارنة جنبًا إلى جنب، وبقية القوالب (المطاعم والعقارات ومنصات التعليم والمتاجر الإلكترونية وصفحات الهبوط) في صفحة القوالب.
سعر كل نوع، بدفعة واحدة، مع الكود المصدري وعروض حية يمكنك تحويلها إلى العربية:
- المواقع والمتاجر (مطاعم، عقارات، منصات تعليم، متاجر إلكترونية، تأجير سياحي، بيع تذاكر الفعاليات): من $39.
- صفحات الهبوط (SaaS والشركات): $39.
- لوحات التحكم: من $49. مقارنة اللوحات التسع.
- Full Stack (الموقع ولوحة التحكم والـ API معًا): $149.
- القوالب العشرة كلها: الحزمة، بجزء من مجموع أسعارها منفردة.
تصفّح القوالب وافتح أي عرض حي بالعربية قبل الشراء.
أدلة ذات صلة
- قوالب لوحات تحكم عربية بـ Next.js تدعم RTL: 9 لوحات من Aniq UI
- أفضل قوالب لوحة تحكم RTL تدعم اللغة العربية (2026)
- كيف تطلق موقع SaaS بالإنجليزية والعربية باستخدام Next.js
- next-intl مع Next.js 16: إعداد proxy.ts وroot params والتخزين المؤقت
- موارد المطورين لقوالب Next.js
المصادر
- Tailwind CSS: دعم RTL (المتغيّران rtl وltr)
- Tailwind CSS: الهوامش والخصائص المنطقية
- Tailwind CSS: top وright وbottom وleft (inset-s وinset-e)
- Tailwind CSS: سماكة الحدود
- Tailwind CSS: استدارة الزوايا
- Tailwind CSS: صورة الخلفية (التدرّجات الخطية)
- سجل تغييرات Tailwind CSS (الإصدار 4.2.0: inset-s وinset-e وهجر start وend)
- Tailwind CSS v3.3: دعم RTL أبسط بالخصائص المنطقية
- next-intl: إعداد App Router مع توجيه اللغات
- MDN: الصنف الزائف :dir()
- MDN: العنصر bdi
- MDN: unicode-bidi
- MDN: letter-spacing (اعتبارات تعدد اللغات)
- MDN: Intl.NumberFormat
- معيار HTML: عرض النص ثنائي الاتجاه
- Material Design: الاتجاه الثنائي
تمت المراجعة في نوفمبر 2026.
قد يعجبك أيضًا

حل خطأ proxy.ts في Next.js 16: Missing Expected Function Export
حل خطأ «Proxy is missing expected function export name» في Next.js 16: كل الأسباب التي جربناها على 16.4.5، والتصدير الصحيح، وإعداد next-intl.

قوالب لوحات تحكم عربية بـ Next.js تدعم RTL: 9 لوحات من Aniq UI
تسع لوحات تحكم بـ Next.js من Aniq UI بالعربية والإنجليزية، بتخطيط RTL حقيقي ووضعين فاتح وداكن، مع عروض تجريبية مباشرة بالعربية.

أفضل قوالب لوحة تحكم RTL تدعم اللغة العربية (2026)
قارن بين 10 قوالب لوحة تحكم RTL تدعم العربية: خيارات Next.js وMUI وTailwind، مجانية ومدفوعة، مع اختبار RTL للعرض التجريبي في خمس دقائق.