lang="ar" vs dir="rtl": not the same thing

Learn what lang and dir each control on an Arabic page, why neither replaces the other, and how to test both across multilingual interfaces.

Guide16 min read

lang="ar" and dir="rtl" often sit beside each other on the same element, which makes them look like two versions of the same instruction. They are not. One tells software what language the content uses. The other tells the browser which direction establishes the text and layout context.

Set only lang="ar" and the browser can know the page is Arabic while still laying it out left to right. Set only dir="rtl" and the page can flow from the right while assistive technology remains uninformed about the language it should pronounce.

This is an unusually tidy division of responsibility for HTML. It would be a shame to ruin it.

AttributeDeclaresDoes not declare
lang="ar"The content's natural language is ArabicBase direction or RTL layout
dir="rtl"The content's base direction is right to leftLanguage, pronunciation or locale

An Arabic document normally needs both on the root element:

html
<html lang="ar" dir="rtl">

That is the short answer. The rest of the guide explains why the shortcuts fail, because shortcuts are often just bugs wearing comfortable shoes.

Start with both attributes on html#

For an Arabic course portal, the document root should declare the language and direction together:

html
<!doctype html>
<html lang="ar" dir="rtl">
  <head>
    <meta charset="utf-8">
    <title>بوابة المقررات</title>
  </head>
  <body>...</body>
</html>

Put them on html, not only on body. The language declaration then covers text associated with the whole document, including the title in head, while the direction establishes the document's base direction from the root.

W3C's language declaration guidance says to declare the default language on the html element and explicitly warns that language markup cannot set right-to-left context. Its structural direction guidance makes the complementary point: declare dir="rtl" on the root for an RTL document, and do not assume it declares the language.

R002 reports html[lang] that is missing, non-Arabic or contradicts detected content. R001 separately reports document direction that is missing, contradicted or declared only in CSS. Two checks are involved because two contracts are involved. Bureaucracy has, for once, stumbled into clarity.

What lang="ar" actually controls#

The lang attribute associates natural-language metadata with the element and its descendants. On the root, ar becomes the default language for the document unless a nested element declares another language.

That metadata can help:

  • screen readers choose Arabic pronunciation rules and an appropriate voice;
  • browsers and editing tools choose language-aware spellchecking behavior;
  • CSS match Arabic content with :lang(ar);
  • translation, indexing and other language-aware tools identify the content correctly;
  • user agents apply language-sensitive behavior where their implementations support it.

MDN's lang reference defines the value as a BCP 47 language tag and highlights its accessibility role. The important word is language. There is no hidden instruction saying “and please mirror the grid while you are there.”

Use a valid tag that describes the content. ar is suitable for Arabic when no more specific tag is needed. A regional tag such as ar-JO can be appropriate when the distinction matters to the content or processing. Language tags are metadata, not shipping addresses, so adding a region out of habit does not improve the page.

Do not invent ar-RTL. Direction is not a private language subtag. Even a valid script subtag does not make HTML derive direction from lang. W3C explains why direction cannot be safely derived from language: languages and scripts do not have a universal one-to-one relationship, and direction must remain independently controllable.

What dir="rtl" actually controls#

The dir attribute establishes an element's base direction. That base direction gives the Unicode Bidirectional Algorithm the context it needs to order directional runs and neutral characters such as punctuation.

At the document root, direction also affects structural behavior. Blocks align from the RTL side by default, table and grid columns begin from the right, form fields inherit direction unless they have their own rule, and logical CSS properties resolve start and end from the direction context.

html
<html lang="ar" dir="rtl">
css
.course-card {
  margin-inline-start: 1rem;
  text-align: start;
}

In the Arabic document, inline start is the right side and text-align: start resolves accordingly. In an English document with dir="ltr", the same CSS resolves from the left. This is why direction belongs in the document structure and layout should use logical properties.

dir does not declare Arabic. Hebrew, Persian, Urdu and other languages can use RTL scripts too. An RTL container may also hold non-linguistic identifiers or mixed-script data. The browser knows the direction because you told it; it does not become a language scholar as a result.

MDN's dir reference documents the base-direction and isolation behavior. For document-level implementation details beyond this comparison, use the complete guide to dir="rtl".

Keep language and direction together in locale data#

Language and direction are separate, but your application should still configure them in the same locale record. Otherwise one team updates the language map, another maintains a direction helper, and the two eventually disagree in a locale nobody checked on Friday afternoon.

js
const locales = {
  ar: { language: "ar", direction: "rtl" },
  en: { language: "en", direction: "ltr" },
  fa: { language: "fa", direction: "rtl" }
};

// activeLocale is validated against the keys in locales.
const locale = locales[activeLocale];
document.documentElement.lang = locale.language;
document.documentElement.dir = locale.direction;

The mapping is explicit. It does not inspect the first character of translated content, assume that every language written with one script always uses the same direction, or ask CSS to repair the result later.

If your product supports script variants, model them deliberately. A language can be written in more than one script, and scripts can imply different usual directions. The direction belongs to the supported locale configuration, not to a shortcut such as “all language codes in this array are RTL.” Arrays have a habit of becoming undocumented internationalization policy.

Keep the two fields independently testable. A configuration entry with language: "ar" and direction: "ltr" is valid data as far as JavaScript is concerned, but probably wrong for an Arabic interface. Validate supported combinations in tests while preserving the ability to override direction for content that genuinely needs it.

Do not expose an unconstrained direction string from translation files. Translation resources contain copy; locale configuration contains application behavior. Mixing them lets a missing translation value alter the document's layout, which is a large promotion for a small JSON key.

Reproduce the failure when dir is missing#

This markup looks plausible:

html
<!doctype html>
<html lang="ar">
  <head>
    <meta charset="utf-8">
    <title>بوابة المقررات</title>
  </head>
  <body>
    <h2>المقررات المتاحة</h2>
    <p>اختر مقرراً لعرض التفاصيل.</p>
  </body>
</html>

It is correctly labelled as Arabic. It is not an RTL document.

In Chromium 151, measured on 2026-09-24, the root direction computes to ltr and the page lays out left to right. Arabic letters still join and their character runs still read RTL, which can make the page look partly convincing. Blocks, punctuation, controls and layout remain in the wrong base context. This is a very respectable failure. It has the correct language metadata and everything.

The fix is not an Arabic CSS class:

css
html[lang="ar"] {
  direction: rtl;
}

That can change rendering, but it leaves the structural direction undeclared. In the same measured behavior, :dir(rtl) does not match an element whose RTL direction comes only from CSS. Tools inspecting the dir contract still find nothing.

Declare the missing information where it belongs:

html
<html lang="ar" dir="rtl">

The direction property documentation recommends using HTML's dir attribute where possible. CSS can style a direction state. It should not be asked to invent the state because the markup was feeling shy.

Reproduce the failure when lang is missing#

The opposite half-fix also looks plausible:

html
<!doctype html>
<html dir="rtl">
  <head>
    <meta charset="utf-8">
    <title>بوابة المقررات</title>
  </head>
  <body>
    <p>اختر مقرراً لعرض التفاصيل.</p>
  </body>
</html>

The browser has the RTL base direction. The Arabic text, blocks and logical layout can appear visually correct. The document language, however, is unknown unless another valid language declaration supplies it.

That matters most where pixels cannot tell the whole story. A visual regression test cannot hear a screen reader use the wrong pronunciation rules. It cannot show whether a language-aware editing tool received the right metadata. The screenshot passes because screenshots are famously quiet.

Add lang="ar" to the root. Do not treat correct visual flow as evidence that the page language exists.

W3C's accessibility guidance for language of parts explains that assistive technology can use declared language to choose pronunciation rules. Pronunciation quality still varies by screen reader and voice, so test the combinations your product supports rather than promising a flawless recital from one attribute.

Language and direction inherit separately#

Both attributes inherit through the document, but they remain separate inheritance chains. A descendant can change language without changing direction, change direction without changing language, or change both.

An English policy note inside the Arabic course portal needs both overrides:

html
<aside lang="en" dir="ltr">
  <h2>Fieldwork policy</h2>
  <p>Students must complete the safety briefing before departure.</p>
</aside>

If you set only lang="en", the note remains in the inherited RTL base direction. If you set only dir="ltr", it displays from the left but remains labelled Arabic through inheritance. Each half can look acceptable in a quick glance, which is how this bug keeps a full calendar.

The reverse applies on an English page:

html
<article lang="ar" dir="rtl">
  <h2>الاستشعار عن بعد</h2>
  <p>مقدمة في تحليل صور الأقمار الصناعية.</p>
</article>

Do not repeat both attributes on every Arabic paragraph inside an Arabic document. Root defaults are meant to inherit. Add a nested declaration where the content actually changes, not as ceremonial decoration around every div.

R080 reports intentional foreign-language runs missing a lang attribute. Inline bidi ordering has its own details, especially around punctuation and inserted values; the bidirectional text guide covers those without turning this comparison into a small book about punctuation.

Attribute text needs the owning element's metadata#

Not every user-facing string is a text node. Images have alt, controls can have aria-label, form fields have placeholder, and elements may expose title text. Those strings still have a language, and some of them contain mixed-direction punctuation or values.

On an Arabic page, an ordinary Arabic label inherits the root language and direction through its owning element:

html
<button type="button" aria-label="فتح خطة المقرر">
  <svg aria-hidden="true" viewBox="0 0 24 24"></svg>
</button>

The button inherits lang="ar" dir="rtl" from the document. The SVG is hidden from the accessibility tree, so the Arabic accessible name comes from the button's attribute.

An English control embedded in the same page needs its own boundary:

html
<button
  type="button"
  lang="en"
  dir="ltr"
  aria-label="Open fieldwork handbook"
>
  <svg aria-hidden="true" viewBox="0 0 24 24"></svg>
</button>

Setting the attributes on a child span would not necessarily change the flattened accessible name exposed for the button. Put the metadata on the element that owns the accessible text, then test the result in the assistive-technology combinations you support.

The same reasoning applies to alt and title. MDN notes that an image's direction affects the formatting of its natural-language attributes. Language and direction cannot repair an untranslated attribute, of course. R012 reports untranslated placeholder, alt, title, aria-label and button values on an Arabic page. Metadata can describe a string; it cannot quietly translate it while everyone is at lunch.

If an attribute's language differs from the element's visible content, HTML has no separate lang attribute for that one attribute value. Restructure the control or choose an owning element whose language accurately describes its accessible text. Do not rely on a descendant language change to survive the accessible-name computation in every browser and screen reader.

Use :lang() for language and :dir() for direction#

CSS provides different selectors because the states mean different things.

Use :lang() for language-specific typography:

css
:lang(ar) {
  font-family: "Noto Sans Arabic", sans-serif;
}

:lang(en) {
  font-family: Inter, sans-serif;
}

The :lang() pseudo-class follows an element's determined language and understands language ranges. :lang(ar) matches ar, regional forms such as ar-JO, and descendants that inherit Arabic. A selector such as [lang="ar"] only matches elements carrying that exact attribute value. The shorter selector is not merely prettier; it is doing a different job.

Use :dir() when styling depends on direction:

css
.course-toolbar:dir(rtl) {
  border-inline-end: 0.25rem solid #6d5bd0;
}

.course-toolbar:dir(ltr) {
  border-inline-end: 0.25rem solid #26705c;
}

Do not use :lang(ar) as the switch for all RTL layout. Arabic is not the only RTL language, and language tags do not establish direction. Do not use :dir(rtl) to select Arabic typography either. Direction tells you which way the context runs, not which font or pronunciation the content needs.

This gives you a small rule worth remembering: language selectors choose language-sensitive presentation; direction selectors choose direction-sensitive presentation. Naming the rule would add paperwork without adding insight, so we shall resist.

Forms expose the difference quickly#

Forms make missing direction obvious because users interact with the base direction rather than merely looking at it.

html
<html lang="ar">
  <body>
    <label for="request">سبب الطلب</label>
    <textarea id="request" name="request"></textarea>
  </body>
</html>

The label and expected input are Arabic, but lang="ar" does not set the field RTL. With no dir, the page keeps its default LTR direction and the textarea inherits it. R023 reports text inputs rendered LTR where Arabic input is expected.

Fix the document root, not every ordinary field:

html
<html lang="ar" dir="rtl">

Now Arabic prose fields inherit RTL. Known technical values remain exceptions:

html
<label for="resource-url">رابط المادة</label>
<input
  id="resource-url"
  name="resourceUrl"
  type="url"
  dir="ltr"
  autocomplete="url"
>

R070 reports tel, email or url inputs rendered RTL. The page direction is a default, not a vow that every value will march the same way.

Language metadata still matters on the labels, help and errors. Direction gets the editing flow right; language gives assistive technology the information it needs to pronounce the surrounding instructions. Once again, the attributes decline to cover each other's shift.

lang does not localize browser messages#

Declaring Arabic is necessary, but it does not force every piece of browser UI into Arabic. Built-in validation messages are a useful edge case because they come from the browser, not from your translation files.

html
<html lang="ar" dir="rtl">
  <form>
    <label for="course-code">رمز المقرر</label>
    <input id="course-code" name="courseCode" required>
    <button>متابعة</button>
  </form>
</html>

In Chromium 151 running with an English browser interface, submitting the empty required field still produces the browser's English validation message. The Arabic lang value does not change that UI language. Firefox and Safari were not measured for the brief, so test them rather than inviting them into the claim by assumption.

If your product requires fully controlled Arabic validation copy, render application-owned error messages and connect them to the field accessibly. Keep native validation behavior in mind while testing. lang="ar" describes your content; it is not a remote control for the browser installation.

Neither attribute formats dates or numbers#

Language and direction provide context, but they do not turn raw data into locale-formatted output. Setting both attributes will not select a calendar, numbering system, currency pattern or plural category.

Formatting belongs in an internationalization API with an explicit locale and product requirements:

js
// locale comes from the active content locale.
const dateFormat = new Intl.DateTimeFormat(locale, {
  dateStyle: "long",
  calendar: "gregory",
  numberingSystem: "latn"
});

The exact options are a product decision, not a universal Arabic preset. Locale-data defaults can vary by locale and browser version. Declare the output conventions that matter instead of assuming the root attributes will wander into JavaScript and arrange things.

After formatting, the resulting string still appears inside a language and direction context. These layers cooperate; they do not merge. A date formatter cannot replace lang, and dir has no opinions about the Gregorian calendar. It has enough to do.

Document language is not the viewer's preference#

The root attributes describe the document being shown, not the person looking at it. An Arabic page remains lang="ar" dir="rtl" when opened by someone whose browser interface is English. An English page does not become Arabic because the signed-in account prefers Arabic notifications.

This distinction matters during partial localization. If a route is still predominantly English, do not label it Arabic merely because it lives under an Arabic URL or inside an Arabic account. That would give language-aware software a false description of the content. Likewise, do not set dir="rtl" on an untranslated English document just to make the navigation resemble the Arabic product.

Choose the root from the page's actual primary content and intended localized version. Then mark genuine language changes below it. If the shell is Arabic but a large document viewer shows an English handbook, the shell can remain the Arabic document context while the viewer gets lang="en" dir="ltr" at its own boundary.

Avoid this shortcut:

js
document.documentElement.lang = navigator.language;

The browser preference describes the user's preferred language list, not the language of the HTML your server delivered. It can help choose a locale before rendering. Once content exists, the markup must describe that content rather than repeating a preference signal like a particularly obedient parrot.

The same principle applies to account settings, geolocation and market. They can influence locale selection, but they do not replace the final lang and dir values on the rendered document.

Keep locale switches atomic in single-page apps#

A full page load can render the correct root attributes from the server. A single-page app must update them when the active document language changes:

js
// nextLocale is validated application locale data.
const root = document.documentElement;

root.lang = nextLocale.language;
root.dir = nextLocale.direction;
document.title = nextLocale.title;

Validate language as an allowed BCP 47 tag and direction as ltr or rtl. Do not derive one from the other in the component. Locale configuration should provide both explicitly.

Update the root attributes, translated content, title and direction-dependent UI as one state transition. If lang changes first and dir arrives after another render, users can see the old layout with the new text. If only dir changes, the page may look finished while accessibility metadata remains stale. The interface has completed half a locale switch and is now waiting for management approval.

Server rendering and hydration must agree too. Render both attributes in the initial HTML so the browser establishes the correct language and direction before the application loads. A client-side repair after hydration may fix the final state, but it cannot prevent the initial wrong layout or missing document metadata.

When content changes language without the application shell changing, update the content boundary instead of the root. A user-generated English note inside an Arabic route does not make the document English. The root describes the page as a whole; nested attributes describe exceptions.

Make component APIs accept both values#

A reusable content component should not infer direction from its language prop unless that inference is an explicit, documented product rule. Give the component both pieces of metadata or pass a locale object that contains both.

jsx
// LocalizedNotice maps language and direction props to HTML attributes.
// content is trusted structured data from the course catalog API.
<LocalizedNotice
  language={content.language}
  direction={content.direction}
>
  {content.text}
</LocalizedNotice>

The component can validate language as an allowed language tag and direction as ltr, rtl or auto, then render them as lang and dir. If direction is unknown for user content, auto is honest. If it is known, an explicit value avoids asking a first-strong heuristic to interpret the opening product name.

Do not design an API with arabic={true} and make that boolean select font, language, direction, spacing and translated copy. It begins as a convenience and ends as a small constitutional crisis when Persian, Hebrew or an English quotation enters the component.

Portals deserve attention because they render outside the apparent component parent. A dialog or tooltip mounted under body may inherit the document defaults rather than the language and direction of its trigger. Pass both values to the portalled surface when its content differs from the root.

Custom elements and shadow trees can inherit language and direction from their host, but internal styles and third-party code may impose their own values. Inspect the rendered element, its computed direction and the accessibility tree. A prop named locale is promising; it is not evidence.

When sanitizing rich text, preserve valid lang and dir values on intentional language boundaries while removing unsafe markup independently. Stripping both attributes can turn a carefully authored multilingual document back into one undifferentiated block, which is a very clean DOM and a less useful page.

Do not confuse lang with neighboring metadata#

A multilingual page can contain several language-related declarations. They are not substitutes for html[lang]:

  • hreflang="ar" describes the language of a linked resource, not the current page;
  • Content-Language response metadata can describe an intended audience or resource language, but W3C still recommends lang on the HTML root;
  • character encoding such as UTF-8 tells the browser how bytes map to characters, not which language those characters form;
  • dir="rtl" establishes direction, not language;
  • an Arabic URL path proves only that somebody typed Arabic into a URL path.

Use each declaration for its own job. Inferring the page language from the encoding is especially optimistic: UTF-8 can represent nearly every language, which is rather the point.

For HTML served as text/html, use lang. xml:lang belongs to XML contexts and has compatibility rules when both appear. Unless you are deliberately producing XML or a polyglot document, adding both to ordinary HTML mostly creates a second value that can disagree with the first. One source of truth is enough excitement.

Test semantics and rendering separately#

A page can pass a visual review and still lack language metadata. It can also have perfect language metadata while flowing in the wrong direction. Your tests need to inspect both contracts.

js
// page and expect are supplied by the browser test runner.
const root = page.locator("html");

await expect(root).toHaveAttribute("lang", "ar");
await expect(root).toHaveAttribute("dir", "rtl");
await expect(root).toHaveCSS("direction", "rtl");

Attribute assertions prove the declarations exist. The computed-style assertion catches CSS that contradicts the structural direction. Then test behavior the attributes are meant to support:

  • read representative Arabic content with supported screen-reader and browser combinations;
  • inspect nested English content for lang="en" dir="ltr" where both change;
  • type into an Arabic prose field and an LTR URL field;
  • verify table, grid and toolbar flow in both locales;
  • inspect punctuation around mixed-direction values;
  • switch locale without a full reload and re-check the root;
  • load the server-rendered page with JavaScript delayed and inspect the initial state.

A compact matrix keeps the two contracts visible during review:

ScenarioExpected langExpected dir
Arabic documentarrtl
English documentenltr
English policy inside Arabic pageenltr
Arabic course card inside English pagearrtl
LTR technical field on Arabic pageinherited Arabic for its labelltr on the field

The last row is deliberately asymmetric. A URL input can be LTR while its Arabic label remains Arabic. “Make both attributes match” is not the rule. “Make each attribute describe its own content” is the rule, which is slightly longer and considerably less wrong.

A screen reader test cannot prove visual direction. A screenshot cannot prove language pronunciation. Asking either one to cover both is like asking a smoke alarm to review your typography. Use the right test for the contract.

For a quick manual diagnosis, inspect document.documentElement.lang, document.documentElement.dir and getComputedStyle(document.documentElement).direction. If the first two are correct but the computed direction disagrees, look for CSS overriding the attribute. If one attribute is missing, fix the markup before investigating downstream symptoms.

Give each attribute its one job#

The reliable implementation is not complicated:

html
<html lang="ar" dir="rtl">

lang="ar" identifies Arabic content. dir="rtl" establishes its RTL base direction. Nested content changes either value only when its own language or direction changes.

That is the whole principle, stated without decoration: preserve both language and direction as separate metadata because users and software need both. We may now return to the normal web-development tradition of making simple things complicated somewhere else.

A Ritla scan can surface an Arabic page whose root language or direction is missing or contradictory. Use the guide to dir="rtl" for document-direction implementation, and the bidirectional text guide when nested inline content needs more than a root declaration.

Checks in this guide

See what your Arabic pages are hiding

Paste a URL. Ritla renders the page on desktop and mobile, runs every check, and shows the top issues with screenshot evidence.