יצירת כרטיס

סקירה כללית

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

הנחיות

בוחרים את הסמנטיקה של התוכן ושל האינטראקציה איתו בנפרד:

  • להשתמש ב-<article> כשורש הכרטיס, בין אם הוא מקשר ליעד ובין אם לא;
  • להשתמש בקישור מובנה לניווט. כשהכרטיס כולו הוא יעד אחד, קישור יחיד יכול לעטוף את ה-<article> רק אם הכרטיס לא מכיל רכיבים אינטראקטיביים אחרים (a, button, input, select, textarea).
  • להשאיר קישורים וכפתורים לפעולות עצמאיות כפקדים נפרדים שמותאמים למכשיר.
  • להשתמש בכפתור בחירה או בתיבת סימון עם <label> משויך, או ב-option ב-<select> מקורי, כשהכרטיס הוא אפשרות בחירה.

שומרים על סדר הגיוני של המדיה, הכותרת, התוכן התומך והפעולות בקוד המקור. לא משתמשים ב-CSS order כדי לשנות את הסדר (ראו פריסות CSS ועיצוב רספונסיבי).

הטמעה

כדאי להשתמש בתגי עיצוב מינימליים ולאפשר לסלקטורים ב-CSS לזהות כל אזור, כדי שכרטיסים פשוטים יישארו פשוטים וצ'אטים אינטראקטיביים יישארו אפשריים. בכרטיס יכולים להיות עד ארבעה אזורים, בסדר המקור הזה, ואפשר להשמיט כל אחד מהם. האזורים הם צאצאים ישירים של הכרטיס, ולא מקובצים ברכיבי wrapper כמו .card__body, מה שמונע מיקום ועיצוב של אזורים לפי בורר. אזור עם רכיב אחד הוא הרכיב עצמו, בלי מעטפת: פסקה בודדת היא <p>. השתמשו ב-wrapper רק כשאזור מכיל כמה רכיבים.

  • מדיה: <img>,‏ <picture>,‏ <video> או <svg> בתור הצאצא הראשון. מוסיפים מחלקת .media רק לתוכן Hero שרירותי שהוא לא אחד מהרכיבים האלה, ואף פעם לא עוטפים <img> פשוט ברכיב .media.
  • כותרת: כותרת בפני עצמה. השתמשו ב-<hgroup> רק כשלכותרת יש כותרת משנה או תיאור קצר, וב-<header> כשהאזור כולל יותר מכותרת, כמו שורת קרדיט או תג. כשיש לכרטיס יעד ראשי, הקישור מופיע בתוך הכותרת.
  • תוכן: <p>, <ul> או <dl> בודדים עומדים בפני עצמם. כשהתוכן מורכב מכמה רכיבים, כמו כמה פסקאות או פסקה ורשימה, מקבצים אותם ב-<div> אחד כדי שהאזור יהיה פריט רשת יחיד.
  • כותרת תחתונה: <footer> שמכיל פעולות עצמאיות כקישורים וכפתורים מקוריים. קישור "קראו עוד" יכול לשתף את כתובת ה-URL של הקישור בכותרת, אבל צריך לתת לו תווית נגישות תיאורית שמתחילה בטקסט הגלוי, באמצעות טקסט שמוסתר באופן חזותי (לדוגמה, Read more<span> about poached eggs</span>), כי קישורי "קראו עוד" זהים הם לא חד-משמעיים ברשימת הקישורים של קורא המסך. אל תוסיפו קישורים אחרים לכותרת התחתונה שחוזרים על כתובת ה-URL של הכותרת.

הכרטיס הפשוט ביותר הוא <article> שמכיל כותרת ופסקה. בוחרים את הקישור ואת דפוס הפעולה שמתאימים להתנהגות של הכרטיס. מידע נוסף זמין בקטע מודלים של אינטראקציה.

אם יש תמונות או סרטונים בכרטיס שצריכים למלא אזור עם יחס גובה-רוחב קבוע, צריך להגדיר את aspect-ratio ו-object-fit: cover ב-<img> או ב-<video> עצמם, כדי שהם ימלאו את האזור בלי עיוותים (יכול להיות שהם ייחתכו). אפשר להשתמש ב-aspect-ratio של קונטיינר .media רק לתוכן Hero אחר. אחרת, צריך לשמור על היחס המקורי של המדיה. לתמונות אינפורמטיביות צריך להוסיף טקסט חלופי מתאים. לתמונות דקורטיביות צריך להשתמש ב-alt="".

התאמה למקום הפנוי

הפריסה צריכה להתבסס על השטח הפנוי בכרטיס, ולא על אזור התצוגה. כדי להתאים את הצאצאים של הכרטיס (מדיה, טקסט, פעולות), צריך להגדיר את הכרטיס כמכל הגודל (container-type: inline-size) ולכתוב כללי @container לצאצאים שלו. אל תגדירו את הגודל לפי הרכיב הראשי של הרשת או הרשימה: הרוחב בפועל של הכרטיס יכול להיות תלוי במספר העמודות, במרווחים וכו', כך שנקודות עצירה שמבוססות על הרכיב הראשי עלולות להיות רועשות.

במכל צר, האזורים מוערמים לפי סדר המקור. במאגר רחב, הציבו את המדיה בעמודה צדדית לצד האזורים האחרים, אבל רק בכרטיסים שיש בהם מדיה: הגדירו את הכללים האלה באמצעות :has() ברכיב הצאצא של המדיה (לדוגמה .card:has(> img)) כדי שהגדרות העמודה והמיקומים של grid-column לא יחולו אף פעם על כרטיס בלי מדיה. כרטיס בלי מדיה נשאר בעמודה אחת בכל רוחב, ואסור להשאיר בו עמודת מדיה ריקה.

יישור הכרטיסים ברשת

כשהכרטיסים בשורה צריכים להיות באותו גובה, משתמשים ב-subgrid: מגדירים שכל כרטיס יתפרס על פני ארבעה מסלולים משותפים בשורה (מדיה, כותרת עליונה, תוכן, כותרת תחתונה) וממקמים את האזורים לפי בורר הרכיבים, כך שכל מה שהוא לא מדיה, כותרת או <footer> נחשב לתוכן. אם בכרטיס מסוים חסר אזור, המסלול שלו נשאר ריק, כך שהאזורים שנותרו עדיין מיושרים עם הכרטיסים הסמוכים. כל מסלול מכיל פריט רשת אחד, לכן צריך לעטוף את אזור התוכן ב-<div> רק כשיש בו יותר מרכיב אחד והכרטיסים חולקים מסלולים. כרטיסים שלא מיושרים עם רכיבים אחרים באותה רמה לא צריכים wrapper או subgrid.

מודלים של אינטראקציה

כרטיסי תוכן

משתמשים ב-<article> בתור הרכיב הראשי של הכרטיס. השאירו את הקישורים והלחצנים כפקדים מקוריים נפרדים.

כרטיסים עם יעד אחד

כשהכרטיס כולו הוא יעד אחד ואין בו פעולות עצמאיות, משתמשים בקישור מותאם אחד. הקישור יכול לעטוף את <article>. אל תטמיעו קישורים, לחצנים או פקדי טפסים אחרים בתוך הקישור, והקפידו שכל טקסט של "קריאה נוספת" יהיה <p> או <span> פשוט. מכיוון שתווית הנגישות של הקישור היא כל הטקסט של הכרטיס, התוכן צריך להיות קצר. אם בכרטיס יש הרבה טקסט או פעולות אחרות, השתמשו בתבנית הקישור המורחב שבהמשך, כך שרק הכותרת תהיה הקישור:

<a href="/recipes/poached-eggs">
  <article class="card">
    <h3>Poached eggs</h3>
    <p>Two poached eggs served on toasted sourdough.</p>
  </article>
</a>

כרטיסים שאפשר ללחוץ עליהם עם פעולות עצמאיות

אם לחיצה על הכרטיס מפעילה פעולה במקום ניווט, צריך להשתמש ב-<button> במקום בקישור.

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

  • השתמשו בהבעת הסכמה אחת, בקישור הראשי עצמו: הציבו את .card-link בקישור אחד בדיוק לכל כרטיס (הקישור של הכותרת). קישורים בכותרת התחתונה וקישורים אחרים אף פעם לא מקבלים את זה, אחרת כל אחד מהם היה מרחיב את שכבת-העל שלו והאחרון היה תופס את כל הכרטיס.
  • מקשרים את הכותרת שלו ומרחיבים את אזור הלחיצה של הסמן על פני הכרטיס באמצעות ::after
.card:has(a.card-link) {
    position: relative;
}

.card a.card-link::after {
    content: "";
    position: absolute;
    inset: 0;
}
  • כדי שרכיבים אינטראקטיביים ימשיכו לפעול, צריך להגדיל את הערך של position: relative; z-index: 1 שלהם. טירגוט סוגי רכיבים (למשל a, button) במקום לדרוש הצטרפות דרך מחלקות.
  • אל תפעילו את z-index על מאגרים שלמים, כי זה יגרום להם לתעד אירועי סמן שלא מייצרים שום פעולה.
.card:has(a.card-link) {
    /* Keep other interactive elements above the overlay */
    a:not(.card-link), button, input, x-button {
        position: relative;
        z-index: 1;
    }
}
  • מעצבים את הכרטיס עצמו במצב ריחוף/מיקוד, במקום את הקישור המתוח:
.card {
    a.card-link {
        &:focus-visible {
            outline: none; /* Draw it on the card instead */
        }
    }
    
    &:has(a.card-link:focus-visible) {
        /* Focus ring */
    }

    &:has(a.card-link:hover) {
        /* Hover styles */
    }
}

כרטיסי בחירה

בכרטיסים שמייצגים אפשרויות מוגדרות מראש, צריך להשתמש ב-<label class="card"> עם תיבת סימון או כפתור בחירה כצאצא ישיר.

עיצוב הכרטיס שנבחר באמצעות :has(:checked). אף פעם אל תשנו מאפיינים שמשפיעים על הפריסה, כמו border-width,‏ padding או font-weight, כשבוחרים בכרטיס, כי הכרטיס והכרטיסים שלידו יקפצו. במקום זאת, הדגישו את הכרטיס באמצעות outline,‏ box-shadow או גבול שקוף שרק הצבע שלו משתנה:

.card {
  border: 2px solid transparent;

  &:has(> :checked) {
    border-color: var(--accent);
  }
}

תיבת הסימון או כפתור הבחירה הגלויים הם בעצמם אינדיקטור לבחירה שלא קשור לצבע, ולכן מספיק רק לשנות את הצבע. אם הפקד מוסתר מבחינה חזותית (צריך להשאיר אותו נגיש למיקוד ובעץ הנגישות, אף פעם לא display: none), מוסיפים אינדיקטור שאינו צבעוני ולא משפיע על הפריסה, כמו סמל של סימן וי או outline עבה יותר.

כשהאפשרויות שייכות לתפריט נפתח שהאפשרויות שלו מוצגות ככרטיסים, צריך להשתמש ברכיב <select> מקורי עם appearance: base-select ולעצב את הרכיבים ::picker(select) ו-<option> במקום להחליף את הרכיב select בכרטיסי <div>. פרטים נוספים וחלופות זמינים במאמר פריסות של בורר מותאם אישית.