Build-an-X workflow for verifying RTL (right-to-left) layouts - runs the test suite under Arabic / Hebrew / Persian / Urdu locales, asserts the `dir="rtl"` attribute is set, verifies layout mirrors correctly (text alignment, icon positions, scrollbar location), uses logical CSS properties (`start`/`end` over `left`/`right`) per W3C guidance, captures per-locale screenshots for visual review. Use when the app supports RTL languages.
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Per w3-rtl, dir="rtl" on the <html> tag is the standard signal for
right-to-left direction: blocks align right, bidi text flows right-to-left,
table columns progress right-to-left, and form inputs start at the right. This
skill verifies the app handles those under Arabic, Hebrew, Persian, and Urdu.
dir attribute presenceimport { test, expect } from '@playwright/test';
test.use({ extraHTTPHeaders: { 'Accept-Language': 'ar' } });
test('Arabic locale sets dir=rtl on <html>', async ({ page }) => {
await page.goto('/');
const dir = await page.locator('html').getAttribute('dir');
expect(dir).toBe('rtl');
});
test('Hebrew locale sets dir=rtl on <html>', async ({ page }) => {
await page.goto('/?lng=he');
await expect(page.locator('html')).toHaveAttribute('dir', 'rtl');
});Per w3-rtl: the html-level dir is the canonical
signal; CSS-only direction setting is incorrect:
"Do not use CSS to apply base direction in HTML pages. Direction is semantic content and should reside in markup, not styling."
test('text aligns right in RTL', async ({ page }) => {
await page.goto('/?lng=ar');
const heading = page.locator('h1').first();
const align = await heading.evaluate(el => getComputedStyle(el).textAlign);
expect(['right', 'start']).toContain(align); // 'start' resolves to right in RTL
});Per w3-rtl: prefer logical properties (start, end)
over directional (left, right) so the layout flips automatically.
Directional icons (back/forward and pagination arrows, direction chevrons) must mirror in RTL; brand logos and icons with embedded text must not.
test('back arrow mirrors in RTL', async ({ page }) => {
await page.goto('/cart?lng=ar');
const backArrow = page.getByRole('link', { name: /back/i });
const transform = await backArrow.evaluate(el => getComputedStyle(el).transform);
// RTL should apply scaleX(-1) (or use a different mirrored asset)
expect(transform).toContain('-1');
});Mixed LTR + RTL content is a common bidi issue:
"My order #ORD-12345 has shipped."In Arabic locale, the order number "ORD-12345" must remain LTR inside the RTL paragraph. Without proper bidi markers, the ordering can flip.
test('order number preserves LTR direction inside RTL paragraph', async ({ page }) => {
await page.goto('/orders/ORD-12345?lng=ar');
const paragraph = page.locator('p').filter({ hasText: 'ORD-12345' });
const orderNumber = paragraph.locator('bdi, [dir="ltr"]');
await expect(orderNumber).toBeVisible();
await expect(orderNumber).toHaveText('ORD-12345');
});The <bdi> element or dir="ltr" on a span isolates LTR text
within an RTL block - without it, browser bidi heuristics may
flip.
Per w3-rtl: in RTL, "Form inputs start at the right by default."
test('form inputs start at right in RTL', async ({ page }) => {
await page.goto('/checkout?lng=he');
const emailField = page.getByLabel(/email/i);
await emailField.click();
// Cursor / placeholder text should be at the right edge
// (assert via screenshot or computed-style direction)
await expect(emailField).toHaveCSS('text-align', /(right|start)/);
});Three checks that extend the core assertions above:
references/rtl-checks.md covers per-locale visual
regression (screenshots per RTL locale to catch un-mirrored padding, icons,
sidebars, and field order), CI integration across desktop and mobile profiles,
and dirname form-submission direction.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| CSS-only direction | Per w3-rtl: "Do not use CSS to apply base direction." | dir="rtl" on the html tag (Step 1). |
Hardcoded left / right in CSS | Doesn't mirror in RTL. | Logical properties (start / end) per w3-rtl. |
| Mirroring brand logos / icons with embedded text | Brand recognition + readability suffer. | Per-asset decision; not all icons mirror. |
Order numbers / IDs without <bdi> / dir="ltr" isolation | Bidi heuristics may flip; "ORD-12345" becomes "12345-ORD". | Wrap in <bdi> (Step 4). |
| Skipping per-locale visual regression | Regressions visible only when RTL activated. | Per-locale screenshots (Step 6). |
dir attribute, RTL effects, semantic
vs CSS direction, dirname attribute.pseudo-localization-runner - sibling: layout-level i18n testing including RTL pseudo-loc
variant.i18n-string-coverage - source-scan complement.playwright-snapshots - visual regression for catching RTL layout issues.