Modal Dialog
النافذة الحواريةrole="dialog" aria-modal="true"
also calledmodal، dialog box، modal window، overlay dialog، confirmation dialog، lightbox، مودال، مربّع حوار، صندوق حوار، نافذة منبثقة حاجبة، نافذة تأكيد، ديالوج
A modal dialog is a layer that opens over the page and takes interaction hostage: everything underneath goes out of service until it closes. Modality itself is what defines it: aria-modal="true" together with a real focus trap and a scrim that swallows clicks. Shape and a sudden entrance do not make a dialog modal. It is therefore not a non-modal popover, which dismisses the moment you click away and leaves the rest of the page usable and keyboard-reachable. A drawer and a bottom sheet are a separate case, defined by where they live: they slide from one edge and stay welded to it, and they may or may not be modal, whereas a dialog floats in the middle of the viewport belonging to no edge. It is not role="alertdialog" either, the narrow type for an urgent message needing an immediate answer: an alertdialog interrupts a screen reader mid-sentence, so it is the wrong choice for an ordinary edit form. The practical test: if the user could reasonably ignore what is inside and carry on working, it should never have been modal.
If you called it…
Live specimen
Interact with the demo. Every part is real and numbered.
نشر التغييرات؟
سيصبح الإصدار الجديد مرئياً لكل المستخدمين فور النشر، ويمكنك التراجع خلال ربع ساعة.
Anatomy: every part, named
Hover a row to locate it in the demo above.
Build prompt
How it behaves right-to-left
This section is ours alone.
Footer button order
mirrorsThe footer is a flex row: keep DOM order fixed (secondary first, primary last) and set justify-content: flex-end. flex-end follows direction by itself, so the primary settles at the logical end of the line, far right in English and far left in Arabic. That is the correct order, because the primary action always stands at the end of the reading direction. The common mistake is "fixing" the flip with flex-direction: row-reverse under [dir="rtl"]: the reversal happens twice, the primary lands back at the start, and visual order splits from Tab order. Likewise, replace margin-left: auto with margin-inline-start: auto.
The close button corner
mirrorsThe × sits in the header corner at the logical end: top right in English, top left in Arabic. Pin it with position: absolute; right: 12px and it stays stranded on the wrong side after the flip. Use inset-inline-end: 12px instead, or better, make it the last child of a flex header with justify-content: space-between and drop absolute positioning entirely. The × glyph itself is symmetric about its vertical axis, so never run scaleX(-1) over it.
The scrim does not mirror
never mirrorsThe scrim covers the entire viewport, so there is nothing in it to flip: inset: 0 and ::backdrop are completely direction-neutral and need no special rule under [dir="rtl"]. The only mistake available here is painting it with a directional gradient such as linear-gradient(to right, …) or giving it a horizontally offset shadow, which turns a neutral layer into a directional one for no reason. Gradient functions accept no logical keywords at all: there is no to inline-end in CSS, only physical sides and angles. So either keep the scrim a flat colour with alpha, or drive it from a --scrim-angle custom property set to 90deg and flipped to 270deg under [dir="rtl"], passed in as linear-gradient(var(--scrim-angle), …).
The portal escapes the direction
mirrorsThe dialog is rendered through a portal hung off <body>, which lifts it out of the very DOM subtree you set dir="rtl" on. When direction lives on an inner wrapper, say a <div dir="rtl"> around the app, the dialog is born outside it and inherits ltr from the root: text aligns left, the close button jumps to the opposite corner, the footer order reverses, and every logical property you wrote is still correct, because the fault is the missing direction context and not the CSS. Set dir on <html> itself (<html lang="ar" dir="rtl">) so anything injected into body inherits it, or pass dir explicitly to the portal container or the dialog root. To check, open the dialog and read getComputedStyle(el).direction. If it comes back ltr, the bug is here and no other rule you change will help.
Scroll lock and scrollbar compensation
mirrorsLocking page scroll behind the dialog hides the vertical scrollbar, and the content jumps sideways by its width. The usual compensation, padding-right: 15px, is only correct in English. The vertical scrollbar sits at the logical inline-end edge, which is the left side of a dir="rtl" page, so the sound fix is padding-inline-end, or simply scrollbar-gutter: stable on the root element. Note the bug never reproduces on macOS with overlay scrollbars, so test it on Windows before declaring it fine.
In code
Each row is one framework's word for the same thing. Take the row your project speaks.
See also
Updated · 2026-08-19