เปิด dialog และ popover ไม่ต้องเขียน JS: command และ commandfor ใน HTML
ปุ่ม HTML สั่งเปิดปิด dialog และ popover ได้เองด้วย command กับ commandfor ใช้ได้ทุกเบราว์เซอร์หลักตั้งแต่ธันวาคม 2025 ค่าที่มีให้ คำสั่งของเราเอง และทางสำรอง
- ปุ่มที่มี commandfor="id" และ command="show-modal" เปิด dialog แบบ modal ได้โดยไม่มี JavaScript สักบรรทัด
- มีค่าในตัว 6 ค่า: show-modal, close, request-close, show-popover, hide-popover, toggle-popover
- คำสั่งที่ขึ้นต้นด้วย -- เป็นของเราเอง เบราว์เซอร์ยิง event ชื่อ command ไปที่ element ปลายทาง
- ณ ตุลาคม 2026 caniuse ให้ราว 86% ของผู้ใช้ทั่วโลก ส่วนที่เหลือต้องมีทางสำรอง
ปุ่ม “เปิดหน้าต่าง” หนึ่งปุ่มเคยต้องมี JavaScript อย่างน้อยสามบรรทัด หา element, ผูก click, เรียก showModal() ตอนนี้ HTML ทำเองได้ด้วย attribute สองตัว commandfor บอกว่าคุมใคร command บอกว่าให้ทำอะไร เรียกรวมกันว่า Invoker Commands และใช้ได้ทุกเบราว์เซอร์หลักแล้ว
ข้อมูลตรวจกับ MDN, caniuse, บล็อก Chrome และ web.dev ณ ตุลาคม 2026: Chrome และ Edge 135, Firefox 144, Safari 26.2
สองบรรทัดแทนสคริปต์
แบบเดิม
<button id="open">ตั้งค่า</button>
<dialog id="settings">...</dialog>
<script>
document.getElementById('open')
.addEventListener('click', () => document.getElementById('settings').showModal());
</script>
แบบใหม่
<button type="button" commandfor="settings" command="show-modal">ตั้งค่า</button>
<dialog id="settings">
<p>ตั้งค่าบัญชี</p>
<button type="button" commandfor="settings" command="close">ปิด</button>
</dialog>
commandfor รับ id ของ element ที่จะคุม (ไม่ใช่ CSS selector) ไม่มี script เลย เบราว์เซอร์จัดการ focus, ปุ่ม Esc และ backdrop ของ modal ให้เหมือนเรียก showModal() เอง
ค่าในตัวทั้ง 6 ค่า
| ค่า | ใช้กับ | เทียบเท่า |
|---|---|---|
show-modal | <dialog> | dialog.showModal() |
close | <dialog> | dialog.close(value) |
request-close | <dialog> | ยิง cancel ก่อน ถ้าไม่ถูกห้ามจึงปิด |
show-popover | element ที่มี popover | popovertargetaction="show" |
hide-popover | element ที่มี popover | popovertargetaction="hide" |
toggle-popover | element ที่มี popover | popovertargetaction="toggle" |
ถ้าสั่งซ้ำกับสถานะเดิม เช่น show-modal กับ dialog ที่เปิดอยู่แล้ว เบราว์เซอร์ไม่ทำอะไร ไม่ error
close กับ value ค่าของปุ่มจะกลายเป็น returnValue ของ dialog ใช้ทำ dialog ยืนยันได้โดยไม่ต้องมี form
<dialog id="confirm-delete">
<p>ลบไฟล์นี้?</p>
<button type="button" commandfor="confirm-delete" command="close" value="cancel">ยกเลิก</button>
<button type="button" commandfor="confirm-delete" command="close" value="delete">ลบ</button>
</dialog>
<script>
const d = document.getElementById('confirm-delete');
d.addEventListener('close', () => {
if (d.returnValue === 'delete') deleteFile();
});
</script>
JS ยังมีอยู่ แต่เหลือแค่ส่วนที่เป็นตรรกะของเราจริง ๆ ไม่ต้องมีโค้ดเปิดปิดหน้าต่าง
request-close ต่างจาก close ตรงที่มันยิง event cancel ก่อน เราเรียก preventDefault() ใน cancel เพื่อห้ามปิดได้ เช่น ฟอร์มที่ยังไม่บันทึก
dialog.addEventListener('cancel', (e) => {
if (formIsDirty()) e.preventDefault(); // ยังไม่ให้ปิด
});
popover: เมนูแบบไม่มี JS
<button type="button" commandfor="menu" command="toggle-popover">เมนู</button>
<div id="menu" popover>
<a href="/blog/">บทความ</a>
<a href="/lab/">บันทึกทดลอง</a>
</div>
popover ได้ light dismiss (คลิกข้างนอกหรือกด Esc แล้วปิด) และลอยอยู่ชั้นบนสุดเอง ทีม Chrome เขียนว่าคำสั่งในตัวดูแลความสัมพันธ์ทาง accessibility ให้ด้วย (เทียบเท่า aria-expanded และ aria-details) ไม่ต้องคอยสลับค่า aria-expanded ด้วยมือ
ใช้ค่าในตัวก่อนเสมอ เบราว์เซอร์จัดการ focus, Esc และ accessibility ให้ เขียนเองครบยากกว่าที่คิด
คำสั่งของเราเอง: ขึ้นต้นด้วย —
ค่าที่ขึ้นต้นด้วยขีดสองตัวเป็น custom command เบราว์เซอร์ไม่ทำอะไรเพิ่ม แค่ยิง event ชื่อ command ไปที่ element ปลายทาง ใน event มี command (ค่าของปุ่ม) และ source (ปุ่มที่กด)
<button type="button" commandfor="photo" command="--rotate-left">หมุนซ้าย</button>
<button type="button" commandfor="photo" command="--rotate-right">หมุนขวา</button>
<img id="photo" src="/img/cat.jpg" alt="แมวนอนบนแป้นพิมพ์">
<script>
const img = document.getElementById('photo');
let deg = 0;
img.addEventListener('command', (e) => {
if (e.command === '--rotate-left') deg -= 90;
if (e.command === '--rotate-right') deg += 90;
img.style.rotate = `${deg}deg`;
});
</script>
ข้อดีคือ listener อยู่ที่ตัวที่ถูกสั่ง ตัวเดียว จะมีปุ่มกี่ปุ่ม อยู่ตรงไหนของหน้า ไม่ต้องผูก click ทีละปุ่ม เพิ่มปุ่มใหม่ก็แค่เขียน HTML
ที่ต้องมี -- เพราะค่าที่ไม่มีขีดสงวนไว้ให้คำสั่งในตัวที่จะเพิ่มในอนาคต ตั้งชื่อเองโดยไม่มีขีดเสี่ยงชนกับของจริงวันหลัง
รองรับแค่ไหน: คิดเป็นจำนวนคน
| เบราว์เซอร์ | เวอร์ชันแรก |
|---|---|
| Chrome / Edge | 135 |
| Firefox | 144 |
| Safari (macOS / iOS) | 26.2 |
Safari 26.2 ออกธันวาคม 2025 ทำให้ฟีเจอร์นี้เป็น Baseline Newly available ตั้งแต่เดือนนั้น ณ ตุลาคม 2026 caniuse ให้ตัวเลขผู้ใช้ทั่วโลกที่รองรับ 86.32%
ตัวอย่างคำนวณ เว็บมีผู้เข้า 10,000 คนต่อวัน สัดส่วนผู้ใช้เหมือนค่าเฉลี่ยโลก
- ไม่รองรับ = 100% − 86.32% = 13.68%
- 10,000 × 0.1368 = 1,368 คนต่อวัน ที่กดปุ่มแล้วไม่มีอะไรเกิดขึ้น
ตัวเลขนี้มากพอที่จะไม่ปล่อยไว้ ถ้าปุ่มนั้นเป็นทางเดียวไปถึงฟังก์ชันสำคัญ ผู้ชมของแต่ละเว็บต่างกัน ดู analytics ของตัวเองประกอบ โดยเฉพาะสัดส่วน Safari ต่ำกว่า 26.2 เพราะ Safari บน iPhone อัปเดตตาม iOS
ทางสำรองสำหรับเบราว์เซอร์เก่า
ตรวจว่าเบราว์เซอร์มี property commandForElement ไหม ถ้าไม่มี ผูก click เองสำหรับค่าในตัวที่ใช้จริง
if (!('commandForElement' in HTMLButtonElement.prototype)) {
document.addEventListener('click', (e) => {
const btn = e.target.closest('button[commandfor]');
if (!btn) return;
const el = document.getElementById(btn.getAttribute('commandfor'));
const cmd = btn.getAttribute('command');
if (!el) return;
if (cmd === 'show-modal') el.showModal();
else if (cmd === 'close') el.close(btn.value);
else if (cmd === 'toggle-popover') el.togglePopover();
else if (cmd === 'show-popover') el.showPopover();
else if (cmd === 'hide-popover') el.hidePopover();
});
}
สคริปต์นี้ทำงานเฉพาะเบราว์เซอร์ที่ไม่รองรับ เบราว์เซอร์ใหม่ข้ามไปทั้งก้อน วันที่ตัวเลขใน analytics ต่ำพอ ลบทิ้งได้โดยไม่ต้องแตะ HTML เลย ถ้าใช้ custom command ด้วย ต้องสร้าง CommandEvent เองซึ่งยุ่งกว่า ใช้ polyfill ที่ดูแลอยู่จะง่ายกว่า หรือผูก click ตรง ๆ สำหรับปุ่มนั้น
ข้อผิดพลาดที่เจอบ่อย
- ใส่ selector ใน commandfor เช่น
commandfor="#settings"ไม่ทำงาน ต้องเป็น id เปล่า ๆsettings - ลืม type=“button” ในฟอร์ม ปุ่มในฟอร์มค่าเริ่มต้นคือ submit กดแล้วฟอร์มส่งหรือหน้ารีโหลด
- ตั้งชื่อ custom command ไม่มี — เช่น
command="rotate"เบราว์เซอร์ไม่ยิง event ให้ ต้องเป็น--rotate - ผูก listener ที่ปุ่มแทนปลายทาง event
commandยิงที่ element ที่ถูกสั่ง ไม่ใช่ที่ปุ่ม - id ซ้ำในหน้า เบราว์เซอร์ใช้ตัวแรกที่เจอ เปิดผิด dialog โดยไม่มี error ให้เห็น ระวังเป็นพิเศษใน component ที่ render ซ้ำหลายรอบ
- ใช้
<div>เป็นปุ่ม commandfor ใช้ได้กับ<button>เท่านั้น ซึ่งดีอยู่แล้ว เพราะได้คีย์บอร์ดและ screen reader มาฟรี
HTML ทำได้ ไม่ต้องใช้ JS ส่วน JS ที่เหลือคือของที่เป็นตรรกะธุรกิจจริง ๆ
dialog ที่เปิดด้วยวิธีนี้ยังต้องผ่านเรื่อง layout บนมือถือเหมือนเดิม ถ้าให้ AI ช่วยดู มีเล่าไว้ในบทความตรวจ layout มือถือด้วย AI และถ้าข้อความในปุ่มหรือ dialog เป็นภาษาไทยยาว ๆ อ่านเรื่องตัดคำในภาษาไทยบนเว็บ
แหล่งอ้างอิง
คำถามที่พบบ่อย
ใช้แทน popovertarget ได้เลยไหม
ได้ command กับ commandfor ทำทุกอย่างที่ popovertarget ทำ และสั่ง dialog ได้ด้วย ทีม Chrome เขียนว่ามันมาแทน attribute ชุดเก่า แต่ popovertarget ยังใช้ได้อยู่ ไม่ต้องรีบแก้ของเดิม
ปุ่มที่อยู่ในฟอร์มจะ submit ฟอร์มไหม
ปุ่มในฟอร์มมีค่าเริ่มต้นเป็น type=submit ใส่ type="button" ให้ปุ่มสั่งงานทุกตัวที่อยู่ใน form จะได้ไม่ต้องเดาพฤติกรรม
เบราว์เซอร์ที่ไม่รองรับจะเกิดอะไร
ปุ่มกดแล้วไม่มีอะไรเกิดขึ้น ไม่ error ตรวจด้วย 'commandForElement' in HTMLButtonElement.prototype แล้วผูก click เองเฉพาะเบราว์เซอร์ที่ไม่มี
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ