פריסות של בורר לבחירה בהתאמה אישית

תכונות לאינטרנט: תג <select> שניתן להתאים אישית

המאפיין appearance: base-select ב-CSS מאפשר לעצב רכיב <select> ואת בורר הצבעים שלו (באמצעות ::picker(select)) כמו רכיבי HTML רגילים. בשילוב עם שיטות פריסה מודרניות של CSS כמו Grid או Flexbox, אתם יכולים להשתחרר מהרשימה האנכית המסורתית וליצור חוויה חזותית עשירה בלי JS בהתאמה אישית. לדוגמה, אתם יכולים להשתמש ב-display: grid כדי לעצב את האפשרויות כרשת דו-ממדית של כרטיסים.

האפשרות הזו מתאימה במיוחד לכלי לבחירת צבעים, לכלי לבחירת אמוג'י או לווריאציות של מוצרים, שבהם תפריט ויזואלי עשיר יעיל יותר מרשימה.

איך להטמיע

1. הפעלת סגנון בסיסי

מחילים את appearance: base-select גם על הרכיב <select> וגם על הרכיב המדומה הפנימי שלו לבחירת צבע select::picker(select):

select.grid-picker {
  &, &::picker(select) {
    appearance: base-select;
  }
}

2. עיצוב של מאגר בורר התמונות

מטרגטים את select:open::picker(select) ומחילים את display: grid (או display: flex). מגדירים עמודות ומרווחים כמו בכל מאגר תגים.

select.grid-picker:open::picker(select) {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1rem;
}

חשוב: חובה להשתמש ב-:open. אם מוסיפים עיצוב ל-select::picker(select), הבורר יהיה גלוי גם כשהבחירה סגורה.

3. עיצוב האפשרויות

מטרגטים את הרכיבים של <option> כדי לעצב אותם כפריטים ברשת או ככרטיסים. אפשר להוסיף להם תמונות, קובצי SVG או פריסות מורכבות.

/* Style options as grid cards */
.grid-picker option {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* padding, border-radius, background, etc. */
}

4. עיצוב האפשרות שנבחרה

כדי לעצב את האפשרות שנבחרה, משתמשים ב-option:checked. לדוגמה:

.grid-picker option:checked {
  position: relative;
  border: 2px solid var(--primary-color);
  background-color: oklab(from var(--primary-color) l a b / 8%);
}

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

5. (אופציונלי) עיצוב סימן הווי של האפשרות שנבחרה

כברירת מחדל, הדפדפן מציג סימן וי כצאצא הראשון של כל <option>.

הוא פועל כמו ::before עם content: "✓" / "", שמוצג לפני ::before שסופק על ידי המחבר, אם קיים כזה.

אם האפשרות לא נבחרה, סימן הווי מוסתר באמצעות visibility: hidden.

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

לדוגמה, כדי למקם אותו בפינה השמאלית העליונה של האפשרות, אפשר לעשות משהו כזה:

select.grid-picker {
  > option::checkmark {
    /* Hide the checkmark for unselected options */
    display: none;
  }

  > option:checked {
    position: relative;

    &::checkmark {
      position: absolute;
      inset-block-start: -.4em;
      inset-inline-end: -.5em;
      display: flex;
      align-items: center;
      justify-content: center;
      box-sizing: border-box;
      min-inline-size: 1.6em;
      aspect-ratio: 1;
      padding-right: 0.12em;
      padding-left: 0.05em;
      border-radius: 50%;
      background: var(--primary-color);
      color: white;
      font-size: 70%;
      font-weight: bold;
    }
  }
}

טיפ: כשמציירים קונטיינר (למשל עיגול) מסביב לסימן הווי, כדאי להזיז אותו קצת למעלה (כמו שעשינו כאן עם padding-bottom ו-padding-left) כדי למרכז אותו מבחינה אופטית בהתאם לצורה שלו. אל תשתמשו ב-padding-inline-start או ב-padding-block-end למטרה הזו, כי הם ישתנו בהתאם למצב הכתיבה, אבל הצורה של סימן הווי לא תשתנה.

6. (אופציונלי) התאמה אישית של תצוגת האפשרות שנבחרה

כברירת מחדל, <select> יציג גרסת טקסט בלבד של האפשרות שנבחרה. כדי להציג תוכן עשיר לערך שנבחר, צריך להשתמש ב-<selectedcontent> בתוך <button> ו/או להציג את האפשרות שנבחרה באופן שונה (למשל, להסתיר פרטים מסוימים).

לא להשתמש ב-<selectedcontent> בלי <button> מסביב, כי <selectedcontent> יהפוך לחלק מהבורר, כמו כל רכיב תוכן אחר.

שיטות מומלצות

  • בודקים את ההתנהגות של הפריסה. ההגדרה appearance: base-select מסירה את התנהגות ברירת המחדל של הדפדפן, שבה גודל הבחירה נקבע לפי רוחב האפשרות הארוכה ביותר. יכול להיות שתצטרכו להגדיר רוחב קבוע או להשתמש באילוצים של flex/grid כדי למנוע שינויי פריסה.
  • מוודאים של-<select> יש מאפיין name ו-<label> משויך. כך אפשר לוודא שגם עם ממשק משתמש מותאם אישית, הרכיב נשאר נגיש לקוראי מסך ופועל בצורה תקינה עם שליחת טפסים רגילים.
  • כדי לוודא ששליחת הטופס פועלת בדיוק כמו קודם, צריך להשתמש במאפייני value רגילים באפשרויות.

נגישות

רכיב <select> מקורי מטפל רק בניווט ליניארי באמצעות החצים למעלה/למטה, כך שהחצים שמאלה/ימינה לא עושים כלום בבורר הפתוח, והחצים למעלה/למטה עדיין מנווטים בין הפריטים ברצף, בלי קשר לשורות ולעמודות.

כדי להטמיע ניווט מרחבי שעוקב אחרי הרשת, אפשר ליירט את keydown ולנתב את המיקוד לאפשרות הקרובה ביותר בכיוון הלחיצה (למשל באמצעות getBoundingClientRect()) כשמשתמשים במקשי החיצים. כדי לקבוע מה הפריט הבא או הקודם, צריך להשתמש בשיטות DOM שפועלות על גיאומטריה בפועל (למשל getBoundingClientRect()) במקום לנתח מחרוזות CSS כמו grid-template-columns או grid-column, שעלולות להיות שבירות ומועדות לשגיאות בתרחישים מורכבים של פריסות, רשתות, רשתות משנה, כתיבה מימין לשמאל ועוד. עם זאת, אל תסבכו את הקוד שלא לצורך. השתמשו במאפיינים שאתם יודעים שהם נכונים לגבי הפריסה כדי לפשט את הלוגיקה הזו.

הערה: ההגדרה הזו מתייחסת רק לניווט באמצעות המקלדת. ה-<select> וה-::picker() שלו עדיין כוללים את תפקידי ברירת המחדל שלהם (menu או listbox), ואי אפשר לשנות אותם, וגם אסור להם להכיל רכיב עם role="grid". הטמעה של wrapper עם role="grid" בתוך בורר עובדת בפועל, אבל היא לא תקינה ועלולה להפתיע משתמשים בטכנולוגיות מסייעות. במקרים מסוימים, שימוש ב-<optgroup> כדי לקבץ אפשרויות יכול ליצור חוויית משתמש טובה בקורא המסך בלי role="grid". אחרת, יכול להיות שתצטרכו לעבור לפתרון מותאם אישית באמצעות Popover API, מיקום עוגן ו-role="grid" כדי להשיג את חוויית המשתמש הרצויה למשתמשים בקורא מסך.

אסטרטגיית גיבוי

חלופות ותמיכה בדפדפנים ל-<select> שניתן להתאמה אישית

בדפדפנים שעדיין לא תומכים ב-appearance: base-select, הרכיב <select> משתנה בצורה חלקה לתפריט נפתח רגיל של מערכת ההפעלה.

  • התעלמות מתוכן שאינו טקסט: דפדפנים ישנים יותר מסירים תגי HTML (כמו <svg> או <div>) בתוך תגי <option> ומציגים רק את צומתי הטקסט. חשוב לוודא שתוכן הטקסט של ה-<option> קריא ובעל משמעות בפני עצמו.
  • טיפול במבנה HTML: מנתחי HTML רגילים עשויים להתעלם מהתגים <button> ו-<selectedcontent> בתוך <select> או להתייחס אליהם כלא תקינים. אם אתם רואים טקסט רגיל כחלופה קריאה, אין צורך ב-polyfills כבדים של JavaScript לשיפור הדרגתי.
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});