เลย์เอาต์เครื่องมือเลือกแบบกำหนดเอง

ฟีเจอร์บนเว็บ: <select> ที่ปรับแต่งได้

พร็อพเพอร์ตี้ CSS appearance: base-select ปลดล็อกความสามารถในการจัดรูปแบบองค์ประกอบ <select> และเครื่องมือเลือก (ผ่าน ::picker(select)) เช่นเดียวกับองค์ประกอบ HTML ทั่วไป เมื่อใช้ร่วมกับเทคนิคเลย์เอาต์ CSS สมัยใหม่ เช่น Grid หรือ Flexbox คุณจะหลุดพ้นจากรายการแนวตั้งแบบซ้อนกันแบบเดิมๆ และสร้างประสบการณ์การใช้งานภาพที่สมบูรณ์ได้โดยไม่ต้องใช้ JS ที่กำหนดเอง เช่น คุณสามารถใช้ display: grid เพื่อจัดสไตล์ตัวเลือกเป็นตารางกริด 2 มิติของการ์ด

ซึ่งเหมาะสำหรับตัวเลือกสี ตัวเลือกอีโมจิ หรือสินค้าย่อยที่เมนูภาพที่สมบูรณ์มีประสิทธิภาพมากกว่ารายการ

วิธีการนำไปใช้งาน

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%);
}

เช่นเดียวกับตัวควบคุม UI อื่นๆ อย่าสื่อสารสถานะด้วยสีเพียงอย่างเดียว และใช้ตัวบ่งชี้ภาพหลายรายการ (เช่น สี ความหนาของเส้นขอบ เครื่องหมายถูก) เพื่อสื่อสารว่าเลือกตัวเลือกใด

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> ที่เชื่อมโยงกัน ซึ่งจะช่วยให้มั่นใจได้ว่าแม้จะมี UI ที่กำหนดเอง คอมโพเนนต์จะยังคงเข้าถึงได้โดยโปรแกรมอ่านหน้าจอและทำงานได้อย่างถูกต้องกับการส่งแบบฟอร์มมาตรฐาน
  • ใช้แอตทริบิวต์ value มาตรฐานในตัวเลือกเพื่อให้แน่ใจว่าการส่งแบบฟอร์มทำงานเหมือนเดิมทุกประการ

การช่วยเหลือพิเศษ

<select> แบบเนทีฟจะจัดการเฉพาะการไปยังส่วนต่างๆ ด้วยลูกศรขึ้น/ลงแบบเชิงเส้น ดังนั้นลูกศรซ้าย/ขวาจึงไม่ทำงานในเครื่องมือเลือกที่เปิดอยู่ และลูกศรขึ้น/ลงยังคงไปยังส่วนต่างๆ ของรายการตามลำดับ โดยไม่คำนึงถึงแถวและคอลัมน์

หากต้องการใช้การไปยังส่วนต่างๆ เชิงพื้นที่ที่สอดคล้องกับตารางกริด คุณสามารถสกัดกั้น keydown และกำหนดเส้นทางโฟกัสไปยังตัวเลือกที่ใกล้ที่สุดในทิศทางที่กด (เช่น ใช้ getBoundingClientRect()) เมื่อใช้แป้นลูกศร หากต้องการระบุรายการถัดไป/ก่อนหน้าที่เหมาะสม ให้ใช้เมธอด DOM ที่ทำงานบนเรขาคณิตจริง (เช่น getBoundingClientRect()) แทนการแยกวิเคราะห์สตริง CSS เช่น grid-template-columns หรือ grid-column ซึ่งอาจไม่เสถียรและมีแนวโน้มที่จะเกิดข้อผิดพลาดสำหรับตารางกริดที่ซับซ้อน ตารางกริดย่อย, RTL และสถานการณ์เลย์เอาต์อื่นๆ อย่างไรก็ตาม อย่าออกแบบมากเกินไปโดยไม่จำเป็น ให้ใช้ค่าคงที่ที่คุณทราบว่าเป็นจริงเกี่ยวกับเลย์เอาต์เพื่อลดความซับซ้อนของตรรกะนี้

หมายเหตุ: วิธีนี้จะแก้ปัญหาการไปยังส่วนต่างๆ ด้วยแป้นพิมพ์เท่านั้น <select> และ ::picker() ยังคงมีบทบาทเริ่มต้น (menu หรือ listbox) และไม่สามารถลบล้างบทบาทเหล่านี้ได้ และไม่อนุญาตให้มีองค์ประกอบที่มี role="grid" การฝัง Wrapper ที่มี role="grid" ไว้ในเครื่องมือเลือกสามารถใช้งานได้จริง แต่ไม่ถูกต้อง และอาจทำให้ผู้ใช้ AT ประหลาดใจ ในบางกรณี การใช้ <optgroup> เพื่อจัดกลุ่มตัวเลือกอาจสร้าง UX ของโปรแกรมอ่านหน้าจอที่ดีได้โดยไม่ต้องใช้ role="grid" ไม่เช่นนั้น คุณอาจต้องเปลี่ยนไปใช้โซลูชันที่กำหนดเองโดยใช้ Popover API, การวางตำแหน่ง Anchor และ role="grid" เพื่อให้ได้ UX ที่ต้องการสำหรับผู้ใช้โปรแกรมอ่านหน้าจอ

กลยุทธ์สำรอง

การสนับสนุนเบราว์เซอร์และทางเลือกสำหรับ <select> ที่ปรับแต่งได้

สำหรับเบราว์เซอร์ที่ยังไม่รองรับ appearance: base-select องค์ประกอบ <select> จะลดระดับลงเป็นเมนูแบบเลื่อนลงของระบบปฏิบัติการมาตรฐานอย่างราบรื่น

  • ละเว้นเนื้อหาที่ไม่ใช่ข้อความ: เบราว์เซอร์รุ่นเก่าจะตัดแท็ก HTML (เช่น <svg> หรือ <div>) ภายในแท็ก <option> และแสดงผลเฉพาะโหนดข้อความ ตรวจสอบว่าเนื้อหาข้อความของ <option> อ่านได้และมีความหมายในตัวเอง
  • การจัดการโครงสร้าง HTML: โปรแกรมแยกวิเคราะห์มาตรฐานอาจละเว้นแท็ก <button> และ <selectedcontent> ภายใน <select> หรือถือว่าแท็กดังกล่าวไม่ถูกต้อง ไม่จำเป็นต้องใช้ Polyfill ของ JavaScript ที่มีขนาดใหญ่สำหรับการเพิ่มประสิทธิภาพแบบต่อเนื่อง หากคุณมองว่าข้อความมาตรฐานเป็นทางเลือกสำรองที่อ่านได้
document.addEventListener("DOMContentLoaded", () => {
  // Check if browser supports base-select value
  if (!CSS.supports("appearance", "base-select")) {
    // Custom select overrides are not supported natively.
  }
});