- แม้ว่า ปุ่มตัวเลือก ที่มีมาในเว็บเบราว์เซอร์โดยพื้นฐานจะเป็นองค์ประกอบ HTML ที่เรียบง่าย แต่ในไลบรารี UI ของ Shadcn กลับถูกประกอบขึ้นใหม่เป็นคอมโพเนนต์ React หลายชั้น
<RadioGroup> และ <RadioGroupItem> ของ Shadcn ครอบคอมโพเนนต์จาก Radix UI อีกชั้นหนึ่ง และใช้ ไอคอนจาก lucide-react พร้อม คลาส Tailwind หลายสิบรายการ
- Radix ใช้ แอตทริบิวต์ ARIA เพื่อการเข้าถึงและการปรับแต่ง แต่ในทางปฏิบัติกลับนำ element แบบปุ่มมาใช้ซ้ำแทน
<input type="radio"> พื้นฐาน
- ทั้งที่สามารถทำสไตล์แบบเดียวกันได้ด้วย CSS แบบเรียบง่าย โครงสร้างนี้กลับเพิ่ม โค้ดหลายร้อยบรรทัดและ dependency หลายตัว จนก่อให้เกิดความซับซ้อนที่ไม่จำเป็น
- การไม่ใช้ element HTML พื้นฐานซ้ำทำให้ ประสิทธิภาพลดลงและภาระในการบำรุงรักษาเพิ่มขึ้น พร้อมทั้งบั่นทอนความเรียบง่ายของการพัฒนาเว็บ
วิเคราะห์โครงสร้างปุ่มตัวเลือกของ Shadcn
- Shadcn ใช้คอมโพเนนต์สองตัวคือ
<RadioGroup> และ <RadioGroupItem> เพื่อสร้างปุ่มตัวเลือก
- แต่ละคอมโพเนนต์ครอบ primitive ที่นำเข้าจาก
@radix-ui/react-radio-group และใช้ CircleIcon จาก lucide-react
- มีโค้ดมากกว่า 45 บรรทัดและมี external import 3 รายการ พร้อมการกำหนดสไตล์ด้วย คลาส Tailwind มากกว่า 30 ตัว
- มีโครงสร้างที่เรียกใช้ ไลบรารีไอคอน SVG เพียงเพื่อแสดงวงกลมแบบง่าย ๆ
- ซึ่งเป็นสิ่งที่แทนได้ด้วย
border-radius ของ CSS หรือ element <circle>
บทบาทของ Radix UI
- Radix ที่ Shadcn ใช้งานคือ ไลบรารี UI คอมโพเนนต์ระดับล่างที่เน้นการเข้าถึงและการปรับแต่ง
- การทำงานของ radio group ใน Radix ใช้ โค้ด React ราว 215 บรรทัด และ import ไฟล์ 7 ไฟล์
- Radix เพิ่ม แอตทริบิวต์ ARIA ลงใน element
<button> เพื่อให้ทำงานเหมือนปุ่มตัวเลือก
- อย่างไรก็ตาม หลักการข้อแรกของการใช้ ARIA ของ W3C ระบุว่า “หากเป็นไปได้ให้ใช้องค์ประกอบ HTML พื้นฐาน”
- แต่ Radix ไม่ได้ทำตามหลักนี้ และเลือกนำปุ่มมาใช้ซ้ำแทน
<input>
- ยังมีโครงสร้างที่เพิ่ม
<input type="radio"> แบบซ่อนไว้เฉพาะภายใน <form> เท่านั้น ทำให้ขาดความสม่ำเสมอ
ทางเลือกที่เรียบง่ายด้วย CSS
- ปุ่มตัวเลือก HTML พื้นฐานสามารถตกแต่งได้ง่ายด้วย
appearance: none, ::before, :checked, border-radius เป็นต้น
- ในโค้ดตัวอย่างสามารถปรับแต่งได้ครบถ้วนโดย ไม่ต้องพึ่ง dependency, JavaScript หรือแอตทริบิวต์ ARIA
- เอฟเฟกต์แบบเดียวกันสามารถทำได้ด้วย Tailwind เช่นกัน
- มุมมองที่ว่า “การตกแต่งปุ่มตัวเลือกเป็นเรื่องยาก” เป็นปัญหาจากอดีต และปัจจุบัน CSS ล้วนก็ให้การควบคุมได้เพียงพอ
ปัญหาของความซับซ้อนที่สะสม
- เมื่อใช้ Shadcn ร่วมกับ Radix จำเป็นต้องทำความเข้าใจ ไลบรารีสองตัวและโค้ดหลายร้อยบรรทัด
- เพียงเพื่อปุ่มตัวเลือกตัวเดียว กลับต้องโหลด JavaScript หลาย KB เพิ่มเติม
- ผู้ใช้ต้องรอการ parse และรัน JS เพื่อให้การสลับปุ่มทำงาน
- โครงสร้างเช่นนี้นำไปสู่ ภาระด้านการรับรู้ที่สูงขึ้น, โอกาสเกิดบั๊กที่มากขึ้น และ ประสิทธิภาพเว็บที่แย่ลง
การกลับไปหาความเรียบง่าย
- เบราว์เซอร์มีปุ่มตัวเลือกให้ใช้อยู่แล้วโดยพื้นฐาน และเพียง
<input type="radio" name="beverage" value="coffee" /> บรรทัดเดียวก็เพียงพอ
- การทำ abstraction ที่ไม่จำเป็นและการใช้ไลบรารีซ้อนกันหลายชั้น เป็นการทำลาย ความเรียบง่ายและประสิทธิภาพดั้งเดิมของการพัฒนาเว็บ
- แม้จะเป็นองค์ประกอบ UI ขนาดเล็ก การออกแบบที่ นำความสามารถพื้นฐานกลับมาใช้ซ้ำ ก็เป็นผลดีทั้งต่อการบำรุงรักษาและประสิทธิภาพ
4 ความคิดเห็น
น่าเบื่อและแสร้งทำเป็นลึกซึ้ง:
เสร็จเร็วแต่จำได้นาน:
ถ้าดูคอมโพเนนต์ปุ่มของ react aria คงเป็นลมไปเลย 555
ผมอยู่สาย frontend เลยเจอปัญหานี้มานานมากแล้ว จะว่าไงดี มันก็เป็นปัญหาที่แก้ยากจริง ๆ ครับ แม้วิธี implement จะเปลี่ยนไปเรื่อย ๆ ตามยุคสมัย แต่สิ่งที่ไม่แก้ด้วย input type ก็ยังเหมือนเดิมทุกยุค... การพยายามเลียนแบบการทำงานของปุ่ม radio และ checkbox ในเว็บเบราว์เซอร์ แล้วไป implement สเปกด้าน accessibility แยกต่างหากนี่ สรุปคือจะทำไปเพื่ออะไรกันแน่... ไม่เข้าใจจริง ๆ... อย่างที่ในบทความบอก ตอนนี้ก็มีทางเลือกด้วย CSS แล้ว แต่พอเห็นว่ายังจะดันทุรังทำเป็นคอมโพเนนต์ให้ได้ ก็แอบขำเหมือนกันครับ
ความเห็นจาก Hacker News
ผมไม่ได้ทำฟรอนต์เอนด์บ่อยนัก แต่ตั้งแต่ช่วงที่ React กลายเป็นกระแสหลัก ก็เริ่มเห็นเค้าลางว่าความซับซ้อนจะเพิ่มขึ้น
ชั้นของ abstraction อื่น ๆ มักมีแนวโน้มจะทำให้เรียบง่ายลง แต่ React กลับสร้าง abstraction ที่ซับซ้อนกว่า เทคโนโลยีพื้นฐานของมันมาก
รู้สึกว่านักพัฒนาที่รู้แค่ React ค่อย ๆ สุมต่อขึ้นไปบนเลเยอร์ที่สูงกว่า จนได้ผลลัพธ์ที่ออกแบบเกินความจำเป็น
ตัวอย่างเช่น Shadcn หรือ Radix เป็นไลบรารี UI สำหรับ React โดยเฉพาะ แต่ถ้าดูแค่ข้อความการตลาดจะเหมือนเป็นไลบรารี UI ทั่วไป
พอขนาดงานใหญ่ขึ้น สุดท้ายก็มักต้องทำ framework ของตัวเองหรือไม่ก็ไม่พอใจกับ framework ที่มีอยู่ แต่ React ก็ช่วยแก้ปัญหานั้นได้ระดับหนึ่ง
ผมมองว่าปัญหาไม่ใช่ React เอง แต่เป็น ความซับซ้อนส่วนเกินของ ecosystem ถ้าใช้ React เป็นก็ยังใช้งานได้อย่างเพลิดเพลิน
เอาแต่พยายามเลี่ยง CSS ด้วยเครื่องมืออย่าง Tailwind ผมใช้ React จัดการ state ก็จริง แต่เรื่อง styling ผมยังชอบเขียน CSS เองมากกว่า
สิ่งที่ยากที่สุดคือการโน้มน้าวให้เพื่อนร่วมทีมยอมเรียน CSS
ผมจึงหลีกเลี่ยง framework ที่ “ทันสมัย” แบบนี้ และชอบ เทคโนโลยีพื้นฐาน ให้มากที่สุด
React แค่ให้ “กล่อง” มา แล้วจะใส่อะไรไว้ข้างในเป็นสิ่งที่นักพัฒนาตัดสินใจเอง นั่นคือพลังที่แท้จริงของ React
ตัวอย่าง radio button นี้ทั้งน่าขำและน่าประทับใจ
ผลลัพธ์แทบแยกไม่ออกจาก radio button CSS พื้นฐาน เลยสงสัยว่าทำไมต้องทำให้ซับซ้อนขนาดนี้
อยากรู้ว่ามีเว็บขนาดใหญ่ที่สร้างขึ้นโดย ไม่มีความซับซ้อนเกินจำเป็น แบบนี้บ้างไหม
โค้ดเยอะกว่าตอนนี้ก็จริง แต่ตัวอินเทอร์เฟซมี ความไวในการตอบสนองทันที
ดูได้จาก โค้ดของโปรเจกต์ Takeoff
มีคำพูดว่า “ไม่มีใครโดนไล่ออกเพราะเลือก React” มันเลยกลายเป็นตัวเลือกที่ปลอดภัย
นักพัฒนาควรจำไว้ว่าสามารถ โต้แย้งข้อกำหนดด้านดีไซน์ได้เสมอ
ตอนมีนักพัฒนาคนหนึ่งเสียเวลาไป 4 ชั่วโมงกับปัญหา layout ง่าย ๆ ใน React Native ผมบอกให้เขาลองถามว่า “เปลี่ยนดีไซน์นิดหน่อยได้ไหม” สุดท้ายแก้เสร็จใน 10 นาที
ถ้าใช้ CSS ให้ดีก็ได้ผลลัพธ์ที่ดีพอแล้ว อยากรู้ว่ามีใครใช้แนวทางนี้แล้วแนะนำอะไรบ้าง
ความผิดพลาดที่ใหญ่ที่สุดในปี 2025 คือการเลือก Shadcn
ตอนเห็นว่าต้อง import Radix อยู่เรื่อย ๆ ก็เป็นสัญญาณเตือนครั้งแรก พอเห็นคอมโพเนนต์ radio ก็เป็นสัญญาณเตือนครั้งที่สอง
แต่โปรเจกต์เดินไปแล้วเลยต้องยอมใช้ต่อและแก้ด้วย Copilot ซึ่งสุดท้ายก็ไม่ชอบ การพึ่ง AI แบบนั้นอยู่ดี
POC ก่อนหน้านี้ทั้งง่ายและมีประสิทธิภาพกว่ามาก สักวันหนึ่งอยาก ทำใหม่ทั้งหมดด้วย vanilla HTML
อย่างน้อย Remix หรือ React Router 7 ก็ยังพยายามรักษาความใกล้กับมาตรฐานเว็บไว้
ผมเริ่มรู้สึกว่า Tailwind “ไม่ใช่แล้ว” และถ้าเพื่อน ๆ ยังบอกว่ามันดีหลังรีแฟกเตอร์ค่อยกลับมาดูอีกที
React มี การ styling แบบอิง props อยู่แล้ว เลยไม่มีเหตุผลมากนักที่จะไปใช้ก้อน CSS class ใหญ่ ๆ
ถ้าเน้นเรื่อง accessibility ใช้แค่ Radix UI ก็น่าจะพอ
ปัญหาคือองค์ประกอบ
<input>ของเบราว์เซอร์ โดยเฉพาะ radio และ select ปรับแต่งได้ยากradio button แบบพื้นฐานก็ ใช้งานบนมือถือไม่ค่อยดี
อยากรู้ให้ละเอียดกว่านี้ว่าบนมือถือมีปัญหาอะไร
<label>แล้วใส่ padding ก็ใช้งานบนมือถือได้ดีพอแล้วโปรเจกต์ส่วนใหญ่มักเริ่มต้นด้วยเจตนาดี แต่สุดท้ายก็เต็มไปด้วย โค้ด radio button ยาว 200 บรรทัด กับ import 7 ตัว
นั่นแหละคือจุดเริ่มของ code rot
ไม่นานมานี้ผมลองใช้ daisyUI แล้วค่อนข้างชอบ
มันอิง CSS ล้วน เลยใช้ ฟีเจอร์ใหม่ของเบราว์เซอร์ (เช่น dialog) ได้ดี
ตัวอย่างเช่น Drawer กักโฟกัสไว้ไม่ได้ และ Accordion ก็ใช้ radio button แทน JS แบบเกินเลย
ด้วยเหตุนี้ไลบรารีอย่าง Radix จึงหลีกเลี่ยงไม่ได้ที่จะซับซ้อน
ผมเห็นด้วยกับประเด็นหลักของบทความ แต่ก็สงสัยว่าถ้าต้อง ทำสไตล์จาก Figma ให้ตรงเป๊ะเหมือนกันทุกเบราว์เซอร์ จะทำได้ด้วย vanilla CSS จริงหรือ
จะทำซ้ำของอย่าง เดโมของ Radix ได้ครบจริงไหม?
ดู ตัวอย่าง CodePen ได้
แค่ดึง CSS ออกมาแล้วผูกกับคอมโพเนนต์ React แบบง่าย ๆ ก็น่าจะพอ
แค่เพื่อหลีกเลี่ยงความไม่ตรงกันทางภาพเล็กน้อย จะคุ้มไหมถ้าต้องเพิ่ม โค้ดหลายสิบ KB และภาระการดูแลรักษา
ก็ทำให้นึกถึงคำพูดของ Nam June Paik ว่า “ถ้าสมบูรณ์แบบเกินไป พระเจ้าจะโกรธ”
ต้นทุนที่แท้จริงไม่ใช่โค้ด แต่คือ เวลา onboarding
ถ้านักพัฒนาคนใหม่จะต้องทำความเข้าใจ radio button 47 บรรทัดที่สร้างบน Radix อาจใช้เวลาหลายสัปดาห์
แต่แบบ vanilla ใช้เวลาสร้างวันเดียว และอธิบายได้ใน 20 นาที
แน่นอนว่าถ้าเป็นผลิตภัณฑ์อย่าง Figma หรือ Linear ที่ accessibility และ keyboard navigation สำคัญมาก ความซับซ้อนนั้นก็อาจสมเหตุสมผล
หลายคอมเมนต์วิจารณ์ Shadcn แต่ผมกลับมองว่ามันส่งเสริม โครงสร้างคอมโพเนนต์และการนำกลับมาใช้ซ้ำ ได้ดี
แก่นของ Shadcn คือปรัชญา “ให้คุณเป็นเจ้าของคอมโพเนนต์และแก้ไขมันได้เอง”
นี่เป็น แนวทางที่ต่างจาก UI library แบบเดิมอย่างพื้นฐาน