בינוני · 32 דק׳ קריאה · עודכן 2026-06-22

נגישות RTL בעברית — לא רק dir=rtl

מדריך נגישות RTL בעברית: WCAG 2.2 AA, תקנה 35, ומה ש-dir=rtl לא מתקן — חיצים, אנימציות, focus, סדר טאב וקוראי מסך. עם קוד מעשי.

AccessibilityRTLHebrewWCAGתקנה 35נגישות
למי זה מתאים: אתה בונה אתרים בעברית, שמת dir="rtl" על ה-<html>, וחושב שגמרת עם הנגישות. אתה טועה. המדריך הזה הוא על כל מה ש-dir="rtl" לא מתקן: חיצים שמצביעים בכיוון הלא נכון, אנימציות שנכנסות מהצד הלא נכון, focus rings שנעלמים על רקע כהה, סדר טאב שבור בגלל row-reverse, וקוראי מסך שמבטאים עברית באנגלית כי שכחת lang="he". בנוסף — מה שתקנה 35 בישראל באמת דורשת ממך, מה הסנקציות, ומה זה אומר בקוד.

מה זה בעצם נגישות (ולמה זה לא רק ״תוסף נגישות בפינה״)

נגישות באינטרנט היא היכולת של כל אדם — בלי קשר ליכולת הגופנית, התחושתית, או הקוגניטיבית שלו — להשתמש באתר שלך. לא לקרוא חצי ממנו עם שתי משקפיים. לא לוותר על קישור כי הוא לא מתפקד עם מקלדת. להשתמש.

באוכלוסייה הישראלית יש כ-1.6 מיליון אנשים עם מוגבלות. מתוכם כ-300 אלף עם לקות ראייה משמעותית, כ-700 אלף עם לקות שמיעה, ומאות אלפים עם לקויות מוטוריות וקוגניטיביות שמשפיעות על שימוש בעכבר ובמסך. זה לא קהל קצה. זה כ-17 אחוז מהשוק שלך — לפני שהזכרנו זקנים, אנשים על מסך קטן באוטובוס, או מישהו עם שבר ביד שמשתמש רק במקלדת השבוע.

נגישות היא גם עניין עסקי. אתר נגיש מקבל יותר תנועה מ-SEO, כי גוגל מתגמלת על HTML סמנטי, אותו בסיס בדיוק שמסביר את SEO לאתרי React ו-SPA. הוא חוטף פחות תביעות (ובישראל זה לא תיאורטי, נדבר על תקנה 35 מיד), ומקבל יותר קונברז׳ן, כי טפסים נגישים ננטשים פחות. אם אתה רוצה את הזווית המשפטית המלאה (מי חייב, מה הקנסות, ואיך נראית הצהרת נגישות תקינה) קרא את נגישות אתרים בישראל ותקן 5568. המדריך הזה מתמקד בקוד.

ועיקר חשוב: תוסף נגישות בפינה הוא לא נגישות. תוסף כזה — האייקון הכחול שמרחף בצד עם ״שנה ניגודיות, הגדל פונט״ — לא הופך אתר לא נגיש לאתר נגיש. במקרים רבים הוא אפילו פוגע בנגישות כי הוא מוסיף שכבת ARIA פגומה מעל קוד שבור. תקנה 35 דורשת תיקון הקוד עצמו, לא תוסף עליו.

תקנה 35 — החובה החוקית בישראל

בישראל, תקנה 35 לתקנות שוויון זכויות לאנשים עם מוגבלות (התאמות נגישות לשירות) משנת 2013 היא החוק המחייב. היא נכנסה לתוקף בשני שלבים: כל אתר חדש שעלה אחרי 25 באוקטובר 2015 חייב להיות נגיש מהיום הראשון. כל אתר שהיה קיים לפני התאריך הזה היה חייב להיות נגיש עד 25 באוקטובר 2017.

מי חייב. כל גוף שמספק שירות ציבורי או מידע על שירות ציבורי באמצעות אתר אינטרנט או אפליקציה. זה כולל עסקים פרטיים. זה לא רק רשויות. אם יש לך אתר עסקי שמוכר שירות או מוצר, אתה חייב. החריגים הם: עסקים זעירים (פחות מ-100 אלף שקל מחזור, או פחות מ-25 אלף ביקורים בשנה — ראה את התקנה לעדכון המספרים), אתרים פרטיים בלי שירות ציבורי, ובלוגים אישיים בלי פרסום.

מה התקנה דורשת. התקנה מפנה לתקן ישראלי 5568, שהוא אימוץ של WCAG 2.0 ברמת AA, עם התאמות מקומיות. שים לב — WCAG 2.0, לא 2.2. התקנה הישראלית הקפיאה את התקן ב-2.0. בפועל, מומלץ לעבוד לפי WCAG 2.2 AA כי הוא תואם לאחור, וכי החוק יתעדכן בקרוב.

סנקציות. הנציבות לשוויון אנשים עם מוגבלות יכולה להוציא צו נגישות. אי-ציות מוביל לקנסות של עשרות עד מאות אלפי שקלים. החוק מאפשר התראה מוקדמת — מי שמקבל הודעה על אי-נגישות מקבל בדרך כלל 60 יום לתקן. הסנקציה האמיתית, מעבר לקנס, היא תביעה אזרחית: אדם שנפגע מהיעדר הנגישות יכול לתבוע פיצוי ללא הוכחת נזק, בסכומים של 50 אלף שקלים ומעלה. ב-2023-2025 ראינו גל תביעות כאלה נגד אתרי מסחר ישראליים.

מה לא חייב. קוד מקור של הCSS שלך לא חייב להיות יפה. אתה לא חייב להוסיף תוסף נגישות צף. אתה לא חייב הצהרת נגישות ב-PDF — אבל אתה כן חייב דף הצהרת נגישות (URL נפרד, בדרך כלל /accessibility-statement) שכולל פרטי רכז נגישות וטופס דיווח על בעיות.

WCAG 2.2 AA — המסגרת הבינלאומית

WCAG (Web Content Accessibility Guidelines) הוא התקן של W3C. גרסה 2.2 התפרסמה באוקטובר 2023, ובאוקטובר 2025 הפכה לתקן ISO רשמי (ISO/IEC 40500:2025). היא תואמת אחורה ל-2.1 ול-2.0 — מי שעומד ב-2.2 עומד גם בקודמות.

ארבעת העקרונות — POUR. כל קריטריון ב-WCAG משויך לאחד מארבעת העקרונות:

  • P — Perceivable (תפיסה): המידע חייב להיות נתפס. טקסט חלופי לתמונות, כתוביות לוידאו, ניגודיות מספקת.
  • O — Operable (תפעול): ממשק שניתן להפעיל. כל פונקציה זמינה במקלדת, אין מלכודות מקלדת, מספיק זמן לקרוא.
  • U — Understandable (הבנה): טקסט קריא, התנהגות צפויה, עזרה בטופס.
  • R — Robust (חוסן): קוד שקוראי מסך וטכנולוגיות עתידיות מבינים. HTML תקין, ARIA מדויק.

רמות A, AA, AAA. A הוא מינימום (אם לא תעמוד בו, האתר ממש לא נגיש). AAA הוא רמה גבוהה (לא ריאלי לכל אתר). AA הוא הסטנדרט בעולם — וזה מה שתקנה 35 דורשת.

מה חדש ב-WCAG 2.2 שכדאי להכיר (תשעה קריטריונים חדשים מעל 2.1):

  • 2.4.11 Focus Not Obscured (AA): אלמנט עם focus חייב להישאר נראה — לא חבוי תחת sticky header, באנר עוגיות, או מודאל.
  • 2.4.13 Focus Appearance (AAA): הfocus ring חייב להיות בעובי לפחות 2px עם יחס ניגודיות 3:1 לפחות מול הצבעים הסובבים.
  • 2.5.8 Target Size Minimum (AA): כל כפתור/קישור אינטראקטיבי חייב להיות לפחות 24×24 פיקסלים. כן, כפתורי הסושיאל הקטנים שלך בפוטר.
  • 3.3.7 Redundant Entry (A): אסור לבקש את אותו מידע פעמיים בתהליך אחד.
  • 3.3.8 Accessible Authentication (AA): אל תבקש מהמשתמש לזכור קוד בלי תמיכה ב-password manager או ביומטריה.

למה dir=rtl לא מספיק — שש דוגמאות

הרבה מפתחים שמים <html dir="rtl" lang="he"> ומאמינים שהם סיימו. הם לא. dir="rtl" הופך את כיוון הטקסט הראשי וכיוון הזרימה של אלמנטים inline. הוא לא עושה את הדברים הבאים:

1. לא הופך אייקוני חצים. SVG של חץ ימינה נשאר מצביע ימינה. בעברית, ״הבא״ אמור להצביע שמאלה (כיוון הקריאה הוא מימין לשמאל, אז הבא הוא משמאל).

2. לא הופך אנימציות. transform: translateX(-100%) עדיין מזיז שמאלה, גם ב-RTL. סלייד-אין ״מהצד״ יבוא מהצד הלא נכון.

3. לא מתקן ערכי CSS פיזיים. margin-left: 16px עדיין שולח את האלמנט שמאלה, גם אם הדף הוא RTL. אתה צריך לוגיים — margin-inline-start.

4. לא משנה את סדר ה-DOM. אם השתמשת ב-flex-direction: row-reverse, הסדר הוויזואלי השתנה, אבל סדר הטאב במקלדת והקריאה לקורא מסך — נשאר אותו דבר. שבירת התאמה.

5. לא קובע את שפת הקריאה לקוראי מסך. dir="rtl" הוא על כיוון, לא על שפה. בלי lang="he", NVDA או JAWS ינסו לקרוא את העברית עם מנוע אנגלי, וזה ישמע כמו אלפבית עברית מבוטאת באנגלית. בלתי ניתנת להבנה.

6. לא משנה ספריות צד שלישי. Slick slider, Swiper, embla, react-spring — כולן זקוקות לקונפיגורציה ידנית של RTL. רק dir="rtl" על הroot לא יספיק. גם ספריות UI שלמות כמו shadcn צריכות התאמה — ראה את המדריך על Tailwind ו-shadcn ב-RTL למי שבונה על הסטאק הזה.

Logical CSS Properties — הפתרון האמיתי

זה אולי הכלי החשוב ביותר במדריך הזה. במקום ערכים פיזיים (left, right, top, bottom), השתמש בערכים לוגיים שמסתגלים אוטומטית לכיוון הדף.

טבלת המרה מהירה:

פיזי (לא להשתמש)לוגי (להשתמש)
margin-leftmargin-inline-start
margin-rightmargin-inline-end
padding-toppadding-block-start
padding-bottompadding-block-end
left: 0inset-inline-start: 0
right: 0inset-inline-end: 0
border-leftborder-inline-start
border-radius: 0 8px 8px 0border-start-end-radius: 8px; border-end-end-radius: 8px
text-align: lefttext-align: start
text-align: righttext-align: end
float: leftfloat: inline-start

דוגמה ממשית — כרטיס עם אייקון בצד:

לפני (שבור ב-RTL):

.card {
  padding: 16px 24px 16px 16px;
  border-left: 4px solid var(--accent);
  margin-left: 32px;
}

.card__icon {
  margin-right: 12px;
  float: left;
}

בעברית, ה-border עובר לצד הלא נכון, ה-margin דוחף את האלמנט בכיוון הלא נכון, והאייקון צף איפה שלא רצינו.

אחרי (עובד בשני הכיוונים):

.card {
  padding-block: 16px;
  padding-inline-start: 16px;
  padding-inline-end: 24px;
  border-inline-start: 4px solid var(--accent);
  margin-inline-start: 32px;
}

.card__icon {
  margin-inline-end: 12px;
  float: inline-start;
}

עכשיו, כשהדף יהפוך ל-RTL, כל הערכים מתהפכים אוטומטית. בלי כפילות. בלי [dir="rtl"] overrides. בלי באגים.

Shorthand גם עובד. padding: 16px עדיין תקין — הוא סימטרי. הבעיה היא רק עם ערכים אסימטריים.

margin-inline ב-shorthand:

.section {
  margin-inline: auto;   /* כמו margin-left: auto; margin-right: auto */
  padding-inline: 24px;  /* padding שווה משני הצדדים */
  padding-block: 48px;   /* padding שווה למעלה ולמטה */
}

תמיכת דפדפנים: כל הדפדפנים המודרניים תומכים מאז 2022. בטוח לשימוש בלי polyfill.

חיצים ואייקונים — הפיכה לפי כיוון

איקונים שמסמלים כיוון (חצים, chevrons, אייקוני ״הבא״/״הקודם״) חייבים להתהפך ב-RTL. חצים שלא מסמלים כיוון (חץ למטה לפתיחת dropdown, חץ לחזרה ב-back button במובייל) — לא צריכים להתהפך.

הפתרון הנקי:

/* רק חיצים שמסמלים "הבא/קודם" */
[dir="rtl"] .icon-chevron-next,
[dir="rtl"] .icon-chevron-prev,
[dir="rtl"] .icon-arrow-forward {
  transform: scaleX(-1);
}

או באמצעות הסלקטור החדש :dir() (תמיכה ב-Chrome 120+, Safari 16.4+, Firefox 121+):

.icon-chevron-next:dir(rtl) {
  transform: scaleX(-1);
}

אל תהפוך:

  • אייקון חיפוש (זכוכית מגדלת)
  • אייקון לב, כוכב, סל קניות
  • חץ של dropdown (למטה)
  • חץ ״חזרה למעלה״
  • לוגואים
  • אייקוני מדיה (play, pause — חוץ מ-play שצריך להישאר משולש לימין כי זה כיוון זמן, לא קריאה)

טיפ קטן: הוסף קלאס סמנטי לאיקונים שצריכים להתהפך, כמו .icon--directional. אז יש לך CSS אחד:

[dir="rtl"] .icon--directional {
  transform: scaleX(-1);
}

אנימציות שנכנסות ״מהצד״

ב-CSS, ״מהצד״ לרוב מתורגם ל-translateX(-100%) או translateX(100%). אבל ב-RTL, הצד הסמנטי הפוך.

שבור ב-RTL:

.toast {
  transform: translateX(-100%);
  transition: transform 300ms;
}
.toast.is-visible {
  transform: translateX(0);
}

ב-RTL, ה-toast נכנס מצד שמאל. בעברית, היה צריך להיכנס מימין.

עובד בשני הכיוונים — CSS custom property:

:root {
  --slide-from: -100%;
}

[dir="rtl"] {
  --slide-from: 100%;
}

.toast {
  transform: translateX(var(--slide-from));
  transition: transform 300ms;
}
.toast.is-visible {
  transform: translateX(0);
}

עם framer-motion / Motion.dev:

import { motion } from "framer-motion";
import { useDirection } from "@/hooks/useDirection"; // הוק משלך

function Toast({ children }) {
  const dir = useDirection(); // מחזיר 'rtl' או 'ltr'
  const from = dir === "rtl" ? 100 : -100;

  return (
    <motion.div
      initial={{ x: `${from}%` }}
      animate={{ x: 0 }}
      exit={{ x: `${from}%` }}
      transition={{ type: "spring", damping: 25 }}
    >
      {children}
    </motion.div>
  );
}

Carousels / Sliders. אם אתה משתמש ב-Embla, Swiper, או slick — וודא שאתה מעביר להם direction: 'rtl' בקונפיג. רק dir="rtl" על ה-wrapper לא יספיק. הם מחשבים אופסטים פנימית.

// Embla
useEmblaCarousel({ direction: 'rtl', loop: true });

// Swiper
<Swiper dir="rtl" ... />

Focus rings — לא לקצץ אותם בעיצוב

הfocus ring הוא איך משתמש מקלדת רואה איפה הוא נמצא בדף. לעולם אל תכתוב outline: none בלי החלפה. זה אחד הכשלים הנפוצים ביותר ב-WCAG.

הבעיה הקלאסית: CTA על משטח כהה. אם הfocus ring שלך הוא שחור, הוא בלתי נראה על כפתור שחור. אם הוא לבן, הוא בלתי נראה על רקע לבן.

הפתרון — Dual-color ring:

*:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px #0c0a09; /* טבעת כהה מסביב לבן */
}

הטריק: שתי טבעות בצבעים מנוגדים, עם יחס ניגודיות 9:1 ביניהן. אחת מהן תמיד תהיה ברורה, על כל רקע.

גרסה עם הצבעים של nisai.dev (cream / ink / stamp red):

:root {
  --focus-inner: #b91c1c;    /* stamp red */
  --focus-outer: #fef3e2;    /* cream */
}

*:focus-visible {
  outline: 2px solid var(--focus-inner);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--focus-outer);
}

על cream הטבעת האדומה בולטת. על ink (כהה) הטבעת הקרם מסביב לאדום בולטת. תמיד שתי טבעות. תמיד נראה.

:focus-visible ולא :focus. ההבדל: :focus-visible מציג focus ring רק כשהמשתמש הגיע לאלמנט במקלדת, לא בקליק עכבר. זה מה שאתה רוצה. הוא נתמך מאז 2022 בכל הדפדפנים.

אסור:

  • outline: none בלי תחליף
  • focus ring שמסתתר תחת sticky header (זה כשלון של 2.4.11)
  • focus ring דק מ-2px
  • focus ring עם ניגודיות מתחת ל-3:1 מול הרקע

Tab order ב-flex-direction: row-reverse — מלכודת RTL

זה באג שתמצא בכל פרויקט RTL שלא נבדק במקלדת. נראה לא תמים, אבל הוא שובר חוויה לחלוטין.

הסיטואציה: בנית navbar עם flexbox. בעברית, אתה רוצה שהלוגו יהיה מצד ימין, ופריטי התפריט משמאל. אז כתבת:

<nav class="navbar">
  <a href="/">לוגו</a>
  <a href="/about">אודות</a>
  <a href="/contact">צור קשר</a>
</nav>
.navbar {
  display: flex;
  flex-direction: row-reverse;
}

איך זה נראה: הלוגו מימין, ״אודות״ באמצע, ״צור קשר״ משמאל. נראה תקין.

איך זה מתנהג במקלדת: המשתמש לוחץ Tab. הfocus קופץ ל-״לוגו״ (כי זה ראשון ב-DOM), ואז ל-״אודות״, ואז ל-״צור קשר״. כלומר: ימין → מרכז → שמאל. אבל הוא קורא בעברית מימין לשמאל, אז למרות שזה נראה הגיוני, הקפיצה הוויזואלית מבלבלת: הfocus זז בכיוון שלא תואם את כיוון הקריאה.

הבעיה הגדולה יותר: קוראי מסך קוראים לפי סדר DOM. הם יקראו ״לוגו, אודות, צור קשר״. זה בסדר. אבל אם הסדר הוויזואלי שונה מהסדר ההגיוני (כי אתה משתמש ב-order: 5 או row-reverse), נוצר ניתוק שמבלבל משתמשים עם לקות ראייה חלקית שמסתמכים על שילוב של ראייה ושמיעה.

הכלל: אל תשתמש ב-row-reverse ב-RTL. במקום זה — תן ל-dir="rtl" לעשות את עבודתו. בtnav RTL, flex-direction: row תמיד תתחיל מימין, כי direction הוא RTL. הסדר הוויזואלי יתאים לסדר ה-DOM.

.navbar {
  display: flex;
  flex-direction: row; /* לא row-reverse! */
  gap: 24px;
}

ב-RTL זה אוטומטית יציב את הלוגו (הראשון ב-DOM) מצד ימין, ואת הקישורים אחריו, משמאלה. סדר הטאב מתאים לסדר הוויזואלי.

מתי בכל זאת להשתמש ב-row-reverse? רק אם אתה רוצה ניתוק מכוון בין סדר DOM לסדר ויזואלי (למשל, ״שמור״ משמאל ו-״בטל״ מימין למרות שב-DOM שמרת לפני בטל). שקול שלוש פעמים. בדרך כלל הפתרון הוא לסדר מחדש את ה-DOM.

קוראי מסך — איך לבדוק עם NVDA בעברית

NVDA הוא קורא המסך החינמי הנפוץ ביותר, ויש לו תמיכה מלאה בעברית. כל מפתח שבונה אתרים בעברית חייב להריץ NVDA לפחות פעם בשבוע ולשמוע איך האתר שלו נשמע.

התקנה: הורד מ-nvaccess.org. הגרסה הנוכחית נכון ליוני 2026 היא NVDA 2026.1.1 (תיקון אבטחה מעל 2026.1, שהיתה הגרסה הראשונה שעברה ל-64 ביט והוסיפה כלי הגדלה מובנה). בעת ההתקנה, בחר עברית כשפת ממשק. תצטרך לוודא שמותקן מנוע דיבור עברי — Windows OneCore Hebrew (חינמי, מובנה) או Vocalizer (מסחרי, איכותי יותר).

הקיצורים החשובים ביותר:

  • NVDA + Down Arrow — קרא הכל מהמיקום הנוכחי
  • NVDA + Space — מעבר בין Browse Mode ו-Focus Mode
  • H — קפיצה לכותרת הבאה
  • K — קישור הבא
  • F — שדה טופס הבא
  • R — region הבא (landmark)
  • D — מעבר בין landmarks
  • Insert + F7 — רשימת אלמנטים (קישורים, כותרות, וכו׳)

הבדיקה המינימלית:

  1. נווט עם Tab בלבד מהתחלה לסוף. וודא ש-1) הfocus תמיד נראה 2) סדר הקפיצה הגיוני 3) אין מלכודות.
  2. נווט עם H בין כותרות. וודא היררכיה הגיונית — h1 פעם אחת, h2 לסקציות ראשיות, h3 לתת-סקציות.
  3. הקשב לאיך NVDA הוגה תוכן באנגלית בתוך עברית. אם יש מילה באנגלית, היא צריכה להיות בתוך <span lang="en">React</span>.
  4. נסה להגיש טופס עם שגיאה. וודא שהודעת השגיאה מוקראת.

lang="he" קריטי. בלי <html lang="he">, NVDA ינסה לקרוא את העברית עם מנוע דיבור אנגלי (או מה שהוגדר ברירת מחדל). במקום ״שלום״ הוא ינסה להגות את שמות האותיות העבריות במבטא אנגלי, ויצא ג׳יבריש מוחלט. זה לא קצת מציק — זה הופך את האתר לבלתי שמיש לחלוטין עבור משתמשי NVDA.

lang="en" לטקסט באנגלית. אם בתוך פסקה בעברית יש שם של ספרייה (React, Next.js), עטוף אותה:

<p>השתמשנו ב-<span lang="en">React</span> בפרויקט הזה.</p>

NVDA יזהה את השפה ויעבור זמנית למנוע דיבור אנגלי לאותה מילה, ויחזור לעברית אחריה.

aria-label בעברית. אם יש לך אייקון-כפתור בלי טקסט גלוי (X לסגירה, אייקון תפריט, אייקון חיפוש), חובה להוסיף aria-label בעברית:

<button aria-label="סגור">
  <svg>...</svg>
</button>

<button aria-label="פתח תפריט">
  <svg>...</svg>
</button>

לא באנגלית. לא "close". זה ייקרא לקורא מסך עברי כ"close" בעברית. הוא לא יבין מה זה.

טבלאות, טפסים, ניווט — דפוסים ספציפיים לעברית

טפסים

Label תמיד מקושר. השתמש ב-<label for="id"> ולא בpaceholder לבד.

<!-- שבור -->
<input type="email" placeholder="אימייל" />

<!-- תקין -->
<label for="email">אימייל</label>
<input id="email" type="email" />

<!-- גם תקין -->
<label>
  אימייל
  <input type="email" />
</label>

placeholder נעלם ברגע שמתחילים להקליד. משתמשים קוגניטיביים שצריכים תזכורת מה לכתוב — נתקעים.

הודעות שגיאה עם aria-live. הודעת שגיאה שמופיעה דינמית חייבת להיות בתוך אזור עם aria-live, אחרת קורא המסך לא ידע שמשהו השתנה.

<label for="email">אימייל</label>
<input
  id="email"
  type="email"
  aria-describedby="email-error"
  aria-invalid="true"
/>
<div id="email-error" role="alert" aria-live="polite">
  כתובת האימייל אינה תקינה
</div>

aria-live="polite" יקריא את ההודעה אחרי שהמשתמש סיים את הפעולה הנוכחית. aria-live="assertive" יפסיק מיד. ברירת המחדל לטפסים — polite. לשגיאות קריטיות (מערכת קרסה) — assertive.

aria-required="true" או required. אם השדה חובה, סמן זאת — גם ויזואלית (כוכבית), וגם סמנטית.

טבלאות

כותרות עם <th> ולא <td> בשורה הראשונה.

<table>
  <caption>תוצאות החודש</caption>
  <thead>
    <tr>
      <th scope="col">חודש</th>
      <th scope="col">הכנסות</th>
      <th scope="col">הוצאות</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">ינואר</th>
      <td>50,000</td>
      <td>30,000</td>
    </tr>
  </tbody>
</table>

<caption> נותן שם לטבלה. scope="col" ו-scope="row" עוזרים לקורא המסך להגיד ״ינואר, הכנסות, 50,000״ במקום סתם ״50,000״.

קישור ״דלג לתוכן הראשי״ שמוצג רק במקלדת:

<body>
  <a href="#main" class="skip-link">דלג לתוכן הראשי</a>
  <header>...</header>
  <main id="main">...</main>
</body>
.skip-link {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: -40px;
  background: var(--ink);
  color: var(--cream);
  padding: 8px 16px;
  z-index: 100;
}

.skip-link:focus {
  inset-block-start: 0;
}

שים לב — inset-inline-start ולא left. ב-RTL הוא יקפוץ ימין.

ניווט

עטוף בתוך <nav> עם aria-label:

<nav aria-label="ניווט ראשי">
  <ul>
    <li><a href="/">בית</a></li>
    ...
  </ul>
</nav>

<nav aria-label="פירורי לחם">
  ...
</nav>

שני <nav> באותו דף? כל אחד צריך aria-label ייחודי בעברית.

Bidi ותוכן מעורב — המספרים, הטלפונים והמחירים שנשברים

זה הבאג ש-dir="rtl" כמעט תמיד מפספס, והוא הכי נפוץ בטפסים ישראליים: טקסט שמערבב עברית, מספרים, סימני פיסוק וסימנים כמו +, , : או /. אלגוריתם ה-Bidi של הדפדפן (Unicode Bidirectional Algorithm) מנסה לנחש לאן כל קטע שייך, ולפעמים מנחש לא נכון. התוצאה: מספר טלפון שמופיע הפוך, מחיר שהשקל קופץ לצד הלא נכון, או נתיב URL שמתפרק.

הדוגמה הקלאסית — מספר טלפון. המשתמש מקליד 054-1234567 בשדה עברי. בלי טיפול, הדפדפן עלול להציג את הקידומת בצד הלא נכון, או לשבור את המקף. הקלט מאוחסן נכון, אבל מה שהמשתמש רואה שגוי — וזה פוגע באנשים עם לקות קוגניטיבית שבודקים את עצמם תוך כדי הקלדה.

הפתרון 1 — dir על השדה לפי סוג התוכן. שדות שמכילים נתונים לטיניים/מספריים מובהקים (טלפון, אימייל, URL, כרטיס אשראי) עדיף לסמן dir="ltr" עם יישור לסוף השורה:

input[type="tel"],
input[type="email"],
input[type="url"] {
  direction: ltr;
  text-align: start; /* בעברית — מתחיל מימין, אבל התוכן עצמו LTR */
}

הפתרון 2 — בידוד Bidi לתוכן דינמי. כשאתה מזריק טקסט ממקור לא ידוע (שם משתמש, כתובת, ערך מ-API) לתוך משפט עברי, עטוף אותו ב-<bdi> או הוסף unicode-bidi: isolate. זה מונע מהקטע ״לדלוף״ ולהפוך את המילים סביבו:

<p>הזמנה מספר <bdi>#A-2026/118</bdi> נקלטה בהצלחה.</p>

בלי ה-<bdi>, ה-# וה-/ יכולים לגרום למספר ההזמנה להופיע משובש בתוך המשפט העברי. עם <bdi> הקטע מבודד ומוצג כיחידה אחת.

הפתרון 3 — סימני כיוון בלתי נראים. למקרים עיקשים (מחיר עם מטבע, טווח תאריכים) יש שני תווי שליטה שימושיים: LRM (U+200E) ו-RLM (U+200F). הם תווים בלתי נראים שמכריחים כיוון נקודתי. רוב הזמן <bdi> עדיף כי הוא סמנטי וקריא, אבל בתוך attribute (למשל aria-label) שבו אי אפשר לקנן תגיות, סימן כיוון מוטמע הוא הפתרון.

טבלת החלטה מהירה:

סוג תוכןהגדרה מומלצת
שדה טלפון / אימייל / URLdir="ltr" + text-align: start
מחיר עם ₪ בתוך משפט<bdi> סביב הסכום
שם / כתובת ממשתמש (UGC)<bdi> תמיד — מקור לא ידוע
קוד / מק״ט / מספר הזמנה<bdi> או dir="ltr" על האלמנט
פסקת עברית רגילהכלום — תן ל-dir="rtl" לעבוד

למה זה גם נגישות ולא רק אסתטיקה? קורא מסך מסתמך על אותו אלגוריתם Bidi כדי להחליט באיזה סדר להקריא את הקטעים. תוכן מעורב שנשבר ויזואלית לרוב גם נקרא בסדר שגוי — קורא המסך יהגה את הספרות הפוך או יפצל מספר אחד לשני קטעים. אם אתה כותב טפסים ממירים, הדיוק הזה משתלב ישירות עם קופירייטינג ממיר בעברית: שדה שנראה שבור מאבד אמון, ואמון מאבד לידים.

prefers-reduced-motion — חובה, לא נחמד

חלק מהמשתמשים סובלים מהפרעות שיווי משקל (vestibular disorders) — לראות אנימציות מסוימות גורם להם בחילה ממש. macOS, iOS, Windows ו-Android כולם מאפשרים למשתמש להגדיר "Reduce Motion" ברמת מערכת ההפעלה. כאתר שלך, אתה חייב לכבד את זה.

CSS:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

זה גישת ״פטיש גדול״ — מבטל כל אנימציה. עדיף. אבל אם יש אנימציות פונקציונליות (אנימציית פתיחה של modal שעוזרת להבנה), אתה יכול לפנות לאלמנטים ספציפיים:

@media (prefers-reduced-motion: reduce) {
  .parallax,
  .scroll-zoom,
  .auto-carousel {
    animation: none !important;
    transform: none !important;
  }
}

framer-motion / Motion.dev:

import { MotionConfig } from "framer-motion";

function App() {
  return (
    <MotionConfig reducedMotion="user">
      {/* כל הקומפוננטים בפנים יכבדו את ההגדרה */}
    </MotionConfig>
  );
}

reducedMotion="user" משבית טרנספורמציות ואנימציות layout כשהמשתמש ביקש, אבל משאיר אנימציות של opacity וצבעים (שלא גורמות לבחילה).

או עם hook ידני:

import { useReducedMotion } from "framer-motion";

function HeroAnim() {
  const prefersReduced = useReducedMotion();

  return (
    <motion.div
      animate={prefersReduced ? {} : { x: [0, 100, 0] }}
      transition={{ duration: 2, repeat: Infinity }}
    >
      ...
    </motion.div>
  );
}

ניגודיות צבע — WCAG vs APCA

ניגודיות היא היחס בין צבע הטקסט לרקע. WCAG 2.2 דורש:

  • 4.5:1 לטקסט רגיל (פחות מ-18px, או 14px+ bold)
  • 3:1 לטקסט גדול (18px+ או 14px+ bold)
  • 3:1 לאלמנטים גרפיים ולכפתורים

איך בודקים? כלי כמו WebAIM Contrast Checker, או Stark, או axe DevTools.

הבעיה עם WCAG: הוא מחושב על בסיס "L\* relative luminance", שלא מתאים בצורה מדויקת לאיך שעין אנושית קולטת ניגודיות. בפועל, חלק מהשילובים שעוברים WCAG 4.5:1 קשים לקריאה, ושילובים אחרים שלא עוברים — נקראים מצוין.

APCA — אלגוריתם מתקדם. Advanced Perceptual Contrast Algorithm הוא התחליף שמיועד ל-WCAG 3 (שעדיין בעבודה). הוא לוקח בחשבון:

  • משקל הפונט (bold לעומת light)
  • גודל הפונט
  • צבע אבסולוטי (לא רק יחס)

מה זה אומר לעברית? הרבה פונטים עבריים עבים יותר באופן טבעי מהמקבילה הלטינית שלהם — אסיסטנט בלייט, רובוטו עברי, היבּו במשקלי 700-900. במונחי APCA, פונט עבה מותר עם ניגודיות נמוכה יותר.

אבל — אזהרה. מה ש״מותר״ לפי APCA לא תמיד ״מומלץ״. במיוחד לטקסט גוף ארוך. הרצומנדציה שלי: השתמש ב-WCAG 4.5:1 כמינימום קשיח, ובדוק שילובים אסתטיים גבוליים גם ב-APCA.

Stamp red על cream (הפלטה של nisai.dev).

  • #b91c1c על #fef3e2 → ניגודיות WCAG: 6.2:1 ✓ (עובר AA לכל גודל)
  • APCA Lc value: כ-65 → מצוין לכותרות, מספיק לטקסט גוף b400+

אבל, stamp red על ink כהה (#0c0a09) → ניגודיות WCAG: 2.7:1 ✗. לא עובר. אם יש לך CTA אדום על רקע ink, אתה חייב להוסיף outer ring/border לבן או להחליף לצבע אקסנט בהיר יותר.

כלי בדיקה — 2026 stack

אוטומטי (תופס 30-40% מהבאגים):

  • axe DevTools (חינמי, Chrome/Edge/Firefox) — הסטנדרט. עובד מתוך DevTools. כ-96 כללים. מוצא בעיות ניגודיות, missing labels, ARIA שגוי, structure issues.
  • Lighthouse (מובנה ב-Chrome) — מריץ קבוצת משנה של axe (57 audits). פחות מקיף מ-axe DevTools, אבל נוח. נותן ציון 0-100.
  • WAVE (חינמי, מ-WebAIM) — תוסף ויזואלי. שם אייקונים על האלמנטים בדף, ומסביר מה הבעיה בכל אחד. נוח לעיצובאים שלא קוראים DevTools.
  • Pa11y CLI — לבדיקה אוטומטית ב-CI. אתה מריץ pa11y https://nisai.dev ומקבל JSON של כל הבעיות. אפשר לחבר ל-GitHub Actions.

ידני (תופס את 60-70% הנותרים):

  • בדיקת מקלדת. סגור את העכבר. נסה להגיע לכל פונקציה רק עם Tab, Enter, Escape, חיצים.
  • קורא מסך. NVDA + Firefox (השילוב הסטנדרטי), או VoiceOver + Safari ב-macOS.
  • Zoom ל-200%. WCAG 1.4.4 דורש שתוכן יישאר שמיש ב-zoom של 200%. הגדל את הדף ב-Ctrl + + ובדוק.
  • הסר CSS. ב-DevTools, השבת כל ה-CSS. האם הדף עדיין הגיוני? אם לא — ה-HTML שלך לא סמנטי.
  • מצב contrast גבוה. הפעל "High contrast mode" ב-Windows. אלמנטים שמסתמכים על צבע בלבד נעלמים.

ה-checklist המינימלי לכל פרויקט לפני שעולה לprod:

  1. <html lang="he" dir="rtl"> בכל דף
  2. כל אלמנט אינטראקטיבי נגיש במקלדת
  3. focus-visible מותאם, נראה על כל רקע
  4. כל אייקון-כפתור עם aria-label בעברית
  5. כל תמונה תוכן עם alt בעברית (תמונות דקורטיביות — alt="" ריק)
  6. סדר כותרות הגיוני (h1 → h2 → h3, בלי לדלג)
  7. כל טופס עם labels מקושרים
  8. הודעות שגיאה עם aria-live
  9. ניגודיות 4.5:1 לטקסט, 3:1 לאלמנטים
  10. prefers-reduced-motion מכובד
  11. skip link
  12. הצהרת נגישות ב-/accessibility-statement
  13. axe DevTools רץ נקי
  14. בדיקת מקלדת ידנית עברה
  15. NVDA קורא את הדף הגיוני

שאלות נפוצות

האם dir="rtl" ו-lang="he" מספיקים לאתר נגיש בעברית?

לא. dir="rtl" מתקן את כיוון הזרימה של טקסט ואלמנטים inline, ו-lang="he" אומר לקורא המסך לקרוא בעברית — שניהם הכרחיים, אבל הם רק ההתחלה. הם לא הופכים חיצים, לא מתקנים אנימציות שנכנסות מהצד הלא נכון, לא מטפלים ב-focus rings ולא בסדר הטאב במקלדת. בנגישות אמיתית אתה עובד עם logical CSS properties, :focus-visible מותאם, aria-label בעברית, ובדיקת מקלדת וקורא מסך בפועל.

מה ההבדל בין WCAG 2.2 לתקן הישראלי לנגישות?

WCAG 2.2 הוא התקן הבינלאומי של W3C (ומאוקטובר 2025 גם תקן ISO/IEC 40500:2025). התקן הישראלי 5568, שאליו מפנה תקנה 35, הוא האימוץ המקומי שלו. תקנה 35 בפועל הקפיאה את הדרישה ב-WCAG 2.0 ברמת AA, אבל מכיוון ש-2.2 תואם לאחור, מי שעומד ב-WCAG 2.2 AA עומד גם בדרישת החוק הישראלי. הזווית המשפטית המלאה — מי חייב, קנסות, והצהרת נגישות — נמצאת במדריך נגישות אתרים בישראל.

למה לא להשתמש ב-flex-direction: row-reverse ב-RTL?

כי הוא משנה רק את הסדר הוויזואלי, לא את סדר ה-DOM. סדר הטאב במקלדת וסדר הקריאה של קורא המסך נשארים לפי ה-DOM, וכך נוצר ניתוק בין מה שרואים למה שמקלדת/קורא-מסך מבינים. הפתרון הנכון הוא flex-direction: row רגיל — ב-RTL הוא ממילא מתחיל מימין, כך שהסדר הוויזואלי וסדר ה-DOM נשארים מתואמים.

איך בודקים נגישות בעברית בלי לשלם על כלי יקר?

שילוב של אוטומטי וידני, הכל בחינם: axe DevTools או Lighthouse ל-30-40 אחוז מהבאגים שנתפסים אוטומטית, ואז NVDA (גרסת 2026.1.1, חינמית) עם מנוע דיבור עברי של Windows OneCore, בדיקת מקלדת מלאה בלי עכבר, ו-zoom ל-200 אחוז. הבדיקה הידנית תופסת את ה-60-70 אחוז שכלי אוטומטי לעולם לא ימצא.

האם תוסף נגישות (overlay) פותר את הבעיה?

לא, ולעיתים הוא אפילו פוגע. תוסף ה-overlay הצף מטפל בשכבה הוויזואלית (ניגודיות, גודל פונט) אבל לא בבעיות המבניות בקוד שבהן נתקל קורא מסך, ולפעמים מוסיף שכבת ARIA פגומה מעל קוד שבור. תקנה 35 דורשת תיקון הקוד עצמו. אפשר להוסיף widget בנוסף — אף פעם לא במקום.

איך גורמים למספר טלפון או מחיר להופיע נכון בתוך טקסט עברי?

עוטפים את הקטע ב-<bdi> או מסמנים את השדה dir="ltr". תוכן מעורב — ספרות, +, , / בתוך עברית — נשבר כי אלגוריתם ה-Bidi של הדפדפן מנחש את הכיוון. <bdi> מבודד את הקטע ומציג אותו כיחידה אחת, וזה מתקן גם את הסדר שבו קורא מסך מקריא אותו.

לסיכום

נגישות RTL בעברית היא לא תוסף שמוסיפים בסוף. היא ארכיטקטורה. אם בנית עם logical properties, עם focus-visible מותאם, עם labels מחוברים, ועם semantic HTML — הנגישות מובנית בקוד שלך מהיום הראשון. אם לא — אתה תכתוב את האתר פעמיים.

תקנה 35 הופכת את זה לחובה חוקית בישראל. WCAG 2.2 AA הוא הסטנדרט המקצועי. שניהם בני השגה לכל מפתח שמשקיע יום אחד בלמידה.

זה לא רק על תאימות. זה על משתמשים אמיתיים שאחרת לא יוכלו להשתמש באתר שלך. וזה הרבה משתמשים.


רוצה ביקורת נגישות לאתר שלך? דבר איתי ב-WhatsApp ואני אעבור עליו עם axe, NVDA, ומקלדת — ואחזיר לך רשימת תיקונים מסודרת. ביקורת ראשונית חינם.

מקורות