טבלאות רספונסיביות

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

העיקרון המרכזי של טבלה רספונסיבית הוא לשמור על הקשר בין תאי הנתונים לבין הכותרות שלהם.

  1. בסיס סמנטי: משתמשים ברכיבי <table> רגילים עם רכיבי <thead>,‏ <tbody> ו-<th>.
  2. הקשר קבוע: החלת position: sticky גם על כותרות של עמודות וגם על כותרות של שורות. כך, לא משנה כמה המשתמשים גוללים לכל כיוון, הם תמיד יכולים לראות את ההקשר של הנתונים.
  3. טרנספורמציות דינמיות: משתמשים בשאילתות @container במקום בשאילתות @media. כך הטבלה יכולה להתאים את עצמה לרוחב שלה (למשל, כשהיא ממוקמת בסרגל צדדי או בווידג'ט צר במרכז הבקרה) במקום לכל אזור התצוגה.
  4. הזרקת תוויות: בפריסה המדורגת, הרכיב <thead> מוסתר, וכותרות נגישות מוזרקות לכל תא באמצעות רכיבי פסאודו ::before ומשתני CSS.

שלבי ההטמעה

1. תגי עיצוב ופריסה

מבנה הטבלה צריך לכלול כותרות סמנטיות רגילות. משתמשים ב-.table-wrapper כדי לטפל בגלישה מהמסך ולספק קונטיינר לשאילתות. כדי לוודא שאנשים שמשתמשים רק במקלדת יכולים לגלול בטבלה באופן עקבי, צריך להוסיף מאפיין tabindex="0" למאגר.

כדי להבהיר את המטרה של ה-wrapper, צריך לתת לו מאפיין role="region". צריך לספק לו תווית נגישות באמצעות aria-labelledby (הפניה למאפיין id של רכיב קיים שמוסיף כיתוב לטבלה או נותן לה שם) או מאפיין aria-label.

<div tabindex="0" role="region" aria-labelledby="employee-table-caption" class="table-wrapper">
  <table>
    <caption id="employee-table-caption">Current employee directory</caption>
    <thead>
      <tr>
        <th>Employee</th>
        <th>Role</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <!-- Row header remains sticky horizontally on desktop, 
             and becomes a sticky card header on mobile -->
        <th>Alex Rivera</th>
        <td>Engineer</td>
        <td>Active</td>
      </tr>
    </tbody>
  </table>
</div>

2. הגדרת כותרות במיקום קבוע

הפעלת גלילה והצמדת כותרות. כדי לנהל את סדר הערימה, השתמשו במאפיינים לוגיים ובערכי z-index מפורשים.

.table-wrapper {
  overflow: auto;
  max-inline-size: 100%;
  max-block-size: min(500px, 80vh);
  container-type: inline-size;
}

table {
  border-collapse: separate;
  border-spacing: 0;
}

/* Sticky column headers (desktop) */
thead {
  position: sticky;
  inset-block-start: 0;
  z-index: 1; /* Above sticky row headers */
}

/* Sticky row headers (desktop) */
tbody th {
  position: sticky;
  inset-inline-start: 0;
  background: white; /* Required to cover background during scroll */
}

3. פריסה רספונסיבית עם רכיבים מוערמים

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

@container (width < 600px) {
  table, thead, tbody, tr, th, td {
    display: block;
  }

  thead {
    /* MANDATORY: Hide column header from layout and screen readers. */
    display: none
  }

  tr {
    margin-block-end: 1.5rem;
    border: 1px solid #ccc;
  }

  /* Sticky row headers at the top of each block/card */
  tbody th {
    position: sticky;
    inset-block-start: 0;
    z-index: 2;
    background: #eee;
  }

  /* Define column labels as CSS variables (often set via JS) */
  table {
    --label-1: "Employee";
    --label-2: "Role";
    --label-3: "Status";
  }

  /* Inject labels */
  td:nth-child(2)::before {
    /* MANDATORY: If using non-text content, provide the accessible name (after the /) which ensures screen readers hear the text alternative. */
    content: var(--label-2) ": ";
  }
  /* MANDATORY: Map the label for each column */
  td:nth-child(3)::before {
    content: var(--label-3) ": ";
  }
}