แก้ไขล่าสุดเมื่อ 01/11/2025
ปุ่มตัวเลือกดูเหมือนจะเรียบง่าย คลิกเดียว หนึ่งตัวเลือก อย่างไรก็ตาม ปุ่มเหล่านี้ยังเป็นหนึ่งในวิธีที่ง่ายที่สุดในการประนีประนอมกับ UX และการเข้าถึง ซ่อนอินพุตดั้งเดิม ข้าม fieldset และ legendหรือตรวจสอบอย่างเข้มงวดเกินไป และคุณจะส่งแบบฟอร์มที่ทำให้ผู้ใช้แป้นพิมพ์สับสน ทำให้โปรแกรมอ่านหน้าจอหงุดหงิด และทำให้การแปลงข้อมูลล่าช้า
คู่มือนี้จะแสดงวิธีการสร้างวิทยุให้ถูกต้องในปี 2025: เลือกได้รวดเร็ว สแกนง่าย เข้าถึงได้เต็มที่ และวัดผลได้ เราจะเริ่มต้นด้วยเบสไลน์ที่ชัดเจนและมีความหมาย จากนั้นจึงเพิ่มเลเยอร์ CSS ที่ทันสมัย เพื่อให้วิทยุดูเหมือนการ์ดหรือตัวควบคุมแบบแบ่งส่วน โดยไม่กระทบต่อการทำงานของแป้นพิมพ์หรือโปรแกรมอ่านหน้าจอ
คุณจะเห็นรูปแบบการตรวจสอบที่ใช้งานได้จริงและไม่น่ารำคาญ เลย์เอาต์ที่ตอบสนองได้ดีซึ่งช่วยให้เป้าหมายมีขนาดใหญ่บนมือถือ และตัวอย่างข้อมูลการผลิตที่คุณสามารถวางลงในสแต็กใดก็ได้ นอกจากนี้ เรายังครอบคลุมการวิเคราะห์ แนวคิด A/B และการตั้งค่า Formspree เพื่อให้คุณบันทึกผลลัพธ์ได้ทันที
เหมาะสำหรับ: นักออกแบบ นักพัฒนาฝั่ง front-end และนักการตลาดที่ใส่ใจทั้งสองสิ่งนี้ การเข้าถึง และ การแปลงไม่จำเป็นต้องมีเฟรมเวิร์กขนาดใหญ่ เพียงแค่มี HTML ที่แข็งแกร่ง, CSS ที่เน้น และการปรับปรุงแบบก้าวหน้าเล็กน้อย
เมื่อสิ้นสุดคุณจะรู้ว่า:
- เมื่อวิทยุเลือกช่องกาเครื่องหมายหรือสลับ
- วิธีการสร้างกลุ่มวิทยุที่สามารถเข้าถึงได้ (
fieldset,legend, การติดฉลาก, โฟกัส) - วิธีการกำหนดรูปแบบวิทยุ "การ์ด" ที่กำหนดเองโดยไม่ต้องซ่อนอินพุต
- วิธีการตรวจสอบด้วย HTML Constraint API (และข้อความที่ดี)
- วิธีการเชื่อมต่อวิทยุกับ Formspree และวัดการเลือก
TL; DR
- ใช้ วิทยุ อย่างแน่นอน หนึ่ง ทางเลือกภายในชุดที่กำหนดไว้อย่างชัดเจน (โดยอุดมคติ 2–6 ตัวเลือก) ที่ควรจะเป็น มองเห็นได้ในทันที.
- ควรห่อวิทยุไว้เสมอ
<fieldset>+<legend>ให้พวกเขา เดียวกันnameและทำให้แต่ละตัวเลือกเป็น คลิกได้<label>. - อย่าซ่อนอินพุตด้วย
display:none; เก็บไว้ สามารถโฟกัสได้ (เช่น,opacity:0; position:absolute; inset:0) ดังนั้น แท็บ/ลูกศร/ช่องว่าง ทำงาน - ตั้งเป้าหมายให้ใหญ่: อย่างน้อย 44×44พิกเซลด้วย วงแหวนปรับโฟกัส นั่นมองเห็นได้ชัดเจน
- ตรวจสอบด้วย API ข้อจำกัด HTML (
required,reportValidity()) แล้วปรับปรุงข้อความ ไม่ใช่วิธีอื่น - หากคุณมีตัวเลือกมากมายหรือไม่คุ้นเคย ให้เพิ่ม ข้อความช่วยเหลือ ภายใต้แต่ละฉลากและพิจารณา การค้นหาหรือการจัดกลุ่ม แทน.
- บนมือถือ สแต็คใน หนึ่งคอลัมน์; บนจอกว้าง 2–3 คอลัมน์ ผ่านทางตาราง CSS
- การวัด: การติดตาม ความประทับใจ การคัดเลือก และการส่งและการทดสอบ A/B คำสั่งตัวเลือก และ สำเนา.
- สำหรับ Formspree ให้เก็บไว้ โพสต์ดั้งเดิม เป็นทางเลือกเสริม; เพิ่ม นำมา เป็นเพียงการปรับปรุงแบบก้าวหน้าเท่านั้น
เมื่อใดควรใช้วิทยุ (ต้นไม้การตัดสินใจ)
Q1. ผู้ใช้เลือกตัวเลือกเดียวเท่านั้นหรือไม่?
- ไม่ → ใช้ ช่องทำเครื่องหมาย (0–หลาย) หรือ ข้อศอก (ไบนารี) หรือคิดงานใหม่
- ใช่ → ไปที่ Q2.
Q2. จำเป็นต้องให้ตัวเลือกต่างๆ มองเห็นได้ในทันทีหรือไม่?
- ใช่ → ใช้ วิทยุ (ดีที่สุดสำหรับการสแกนและเลือกอย่างรวดเร็ว)
- ไม่มี (พื้นที่จำกัด / มีตัวเลือกมากมาย) → พิจารณา เลือก (ดรอปดาวน์) หากตัวเลือกมีความสำคัญและได้รับประโยชน์จากการเปรียบเทียบ ให้เลือกวิทยุและออกแบบเค้าโครงใหม่
Q3. มีตัวเลือกกี่ตัวเลือก?
- 2 6- → วิทยุเป็นสิ่งที่เหมาะสมที่สุด
- 7 10- → วิทยุอาจยังทำงานกับการจัดกลุ่มหรือ สองคอลัมน์ ตาราง มิฉะนั้นให้ใช้รายการเลือกหรือรายการค้นหา
- > 10 → ใช้ เลือกด้วยการค้นหา หรือ ขั้นตอน นั่นจะทำให้ชุดแคบลงก่อน
Q4. ตัวเลือกต่างๆ มีสถานะที่แยกจากกันหรือไม่?
- ใช่ → วิทยุ
- ไม่ / การผสมผสานทำให้รู้สึก → ช่องกาเครื่องหมาย
Q5. การเลือกจะทำให้เกิดคำถามติดตามที่แตกต่างกันหรือไม่?
- ใช่ → วิทยุยังดีอยู่ แสดงการติดตามผล อย่างมีเงื่อนไขแต่เก็บช่องที่ซ่อนไว้ นอกลำดับแท็บ และ SR ไหลไปเรื่อยๆจนกว่าจะเปิดเผย
- ไม่ → กลุ่มวิทยุแบบง่าย
Q6. คุ้นเคยหรือบรรยาย?
- ฉลากที่คุ้นเคย (เช่น “เล็ก / กลาง / ใหญ่”) → ฉลากสั้นก็เพียงพอแล้ว
- ไม่คุ้นเคยหรือมีเดิมพันสูง (เช่น “SEO เชิงเทคนิค เทียบกับ SEO เชิงเนื้อหา”) → เพิ่ม ไมโครคอปี้ (คำอธิบายบรรทัดเดียว) ใต้แต่ละป้ายกำกับ
Q7. Mobile-first หรือเปล่า?
- ใช่ (เสมอ) → การใช้งาน สแต็กแบบคอลัมน์เดียว ด้วยระยะห่างที่กว้างขวางและข้อความขนาด 16–18 พิกเซล ส่งเสริม ถูกเลือกมากที่สุด ตัวเลือกไปทางด้านบน
ตัวอย่างการปฏิบัติ
- แบบฟอร์มของคุณ:“บริการใด” → วิทยุชนะการเลือก เพราะผู้ใช้ควรเปรียบเทียบตัวเลือกต่างๆ ควบคู่กัน เก็บการ์ด 3 ใบ (การสร้างลิงก์, SEO, การพัฒนาเว็บ) พร้อมข้อความย่อยสั้นๆ
- ความเร็วในการจัดส่ง:ความเร็วที่ไม่รวมกัน 3–4 → วิทยุ (พร้อมเดลต้าราคาในข้อความย่อย)
- ความถี่ในการรับจดหมายข่าว: รายวัน/รายสัปดาห์/รายเดือน → วิทยุ.
- ความสนใจ (สามารถเลือกได้หลายรายการ) → ไม่ใช่วิทยุ ให้ใช้ช่องกาเครื่องหมาย
กายวิภาคของกลุ่มวิทยุที่เหมาะสม
คัดลอกและวางบรรทัดฐานความหมาย (ยังไม่มี CSS แฟนซี)
<form id="quote-form" action="/th/submit" method="post">
<fieldset id="service-group">
<legend>Which service do you need?</legend>
<p id="service-hint">Pick one option that best matches your goal.</p>
<div class="field">
<label>
<input type="radio" name="service" id="svc-link" value="link-building" required>
<span>Link Building</span>
</label>
<label>
<input type="radio" name="service" id="svc-seo" value="seo">
<span>Search Engine Optimization</span>
</label>
<label>
<input type="radio" name="service" id="svc-web" value="web-dev">
<span>Web Design / Development</span>
</label>
</div>
<p id="service-error" class="error" role="alert" hidden>
Please choose a service to continue.
</p>
</fieldset>
<button type="submit">Continue</button>
</form>
เหตุใดแต่ละส่วนจึงมีความสำคัญ
<fieldset>+<legend>ตั้งชื่อกลุ่มสำหรับโปรแกรมอ่านหน้าจอและกำหนดบริบทสำหรับทุกคน- เดียวกัน
nameทำให้อินพุตแยกจากกัน - คลิกได้
<label>รอบ ๆ อินพุตแต่ละรายการ ช่วยให้คุณมีพื้นที่แตะ/คลิกขนาดใหญ่ (44 พิกเซลขึ้นไป) requiredบนวิทยุครั้งแรก ช่วยให้เบราว์เซอร์ตรวจสอบกลุ่มโดยตรง- ข้อความช่วยเหลือ (
service-hint) and ข้อความแสดงข้อผิดพลาด (service-error) แยกจากกันเพื่อให้คุณสามารถแสดง/ซ่อนข้อผิดพลาดได้โดยไม่ต้องย้ายสิ่งของไปมา - ไม่จำเป็นต้องมีบทบาท ARIA วิทยุพื้นเมืองได้เปิดเผยความหมายที่ถูกต้องแล้ว
CSS ขั้นต่ำเพื่อการใช้งาน (รูปลักษณ์ดั้งเดิม เป้าหมายใหญ่ โฟกัสชัดเจน)
/* Layout & spacing */
.field label {
display: flex;
align-items: center;
gap: .6rem;
padding: .65rem .8rem; /* big tap target */
border-radius: .6rem;
cursor: pointer;
}
/* Keyboard focus: highlight the whole label when the input is focused */
.field label:has(input:focus-visible) {
outline: 3px solid #3b82f6;
outline-offset: 2px;
}
/* Selected state (optional, native dot still shows) */
.field label:has(input:checked) {
background: #f1f5ff;
}
/* Error text */
.error { color: #b91c1c; margin-top: .5rem; font-size: .9rem; }
เคล็ดลับ:
:has()ช่วยให้คุณมี "วงแหวนโฟกัสบนการ์ดทั้งหมด" โดยไม่ซ่อนอินพุตจริง หากเบราว์เซอร์รุ่นเก่าไม่รองรับ ผู้ใช้จะยังคงได้รับวงแหวนโฟกัสดั้งเดิมบนวิทยุ
การตรวจสอบที่อ่อนโยนและเข้าถึงได้ (ใช้เบราว์เซอร์ก่อน)
ปล่อยให้เบราว์เซอร์จัดการส่วน "คุณต้องเลือกอันหนึ่ง" จากนั้นแสดงข้อผิดพลาดแบบอินไลน์ของคุณหากจำเป็น
const form = document.getElementById('quote-form');
const group = document.getElementById('service-group');
const error = document.getEementById('service-error');
form.addEventListener('submit', (e) => {
// Trigger native validation UI (works because the first radio has `required`)
if (!form.reportValidity()) {
// Optional: surface a friendly inline error too
error.hidden = false;
group.setAttribute('aria-invalid', 'true');
e.preventDefault();
} else {
error.hidden = true;
group.removeAttribute('aria-invalid');
}
});
พฤติกรรมที่คุณควรคาดหวัง (และทดสอบ):
- แป้นพิมพ์: กดแท็บหนึ่งครั้งเพื่อเข้ากลุ่ม ใช้ ปุ่มลูกศร การเคลื่อนย้าย; ช่องว่าง เลือก.
- โปรแกรมอ่านหน้าจอ: ตำนานจะถูกอ่านเป็นป้ายกลุ่ม โดยตัวเลือกแต่ละตัวจะประกาศด้วยสถานะ "เลือก/ไม่ได้เลือก"
- สัมผัส: การแตะที่ฉลากจะสลับวิทยุ (ด้วยการห่อฉลาก)
วิทยุแบบ "การ์ด" ที่ได้รับการออกแบบมาเป็นพิเศษ (โดยไม่ทำให้การเข้าถึงเสียหาย)
คัดลอกและวาง HTML
<form id="plan" action="/th/submit" method="post">
<fieldset>
<legend>Choose a plan</legend>
<p id="plan-hint">Pick one option. You can change anytime.</p>
<div class="card-group" role="group" aria-describedby="plan-hint">
<label class="card">
<input type="radio" name="plan" value="starter" required>
<span class="card-title">Starter</span>
<span class="card-sub">Good for small sites</span>
</label>
<label class="card">
<input type="radio" name="plan" value="growth">
<span class="card-title">Growth</span>
<span class="card-sub">Most popular</span>
</label>
<label class="card">
<input type="radio" name="plan" value="pro">
<span class="card-title">Pro</span>
<span class="card-sub">Advanced features</span>
</label>
</div>
<p id="plan-error" class="error" role="alert" hidden>Select one to continue.</p>
</fieldset>
</form>
CSS ขั้นต่ำและแข็งแกร่ง
:root{
--brand:#0b5cff; --ring: rgba(11,92,255,.35);
--border:#e5e7eb; --bg:#fff; --muted:#6b7280;
--radius:14px;
}
/* Grid */
.card-group{
display:grid; gap:16px;
grid-template-columns: repeat(3, minmax(0,1fr));
}
@media (max-width:900px){ .card-group{ grid-template-columns:1fr 1fr; } }
@media (max-width:640px){ .card-group{ grid-template-columns:1fr; } }
/* Card label */
.card{
position:relative;
display:grid; place-items:center; text-align:center;
gap:6px; padding:18px;
border:1px solid var(--border); border-radius:var(--radius);
background:var(--bg);
cursor:pointer; user-select:none;
transition: box-shadow .15s, border-color .15s, transform .02s;
}
.card:hover{ box-shadow:0 8px 20px rgba(0,0,0,.06); }
/* Keep the native input focusable: no display:none */
.card input{
position:absolute; inset:0; opacity:0;
}
/* Keyboard focus ring on the whole card */
.card:has(input:focus-visible){
box-shadow:0 0 0 3px var(--ring);
border-color: var(--brand);
}
/* Selected state */
.card:has(input:checked){
border-color: var(--brand);
box-shadow:0 0 0 3px var(--ring);
}
/* Content */
.card-title{ font-weight:700; color:#111; }
.card-sub{ font-size:14px; color:var(--muted); }
/* Error text */
.error{ color:#b91c1c; margin-top:.5rem; font-size:.9rem; }
บันทึกพฤติกรรม (เหตุใดรูปแบบนี้จึงได้ผล)
- ไม่
display:noneในการป้อนข้อมูล → ผู้ใช้สามารถกด Tab เข้าไปได้ ปุ่มลูกศร การเลือกย้าย; ช่องว่าง เลือก. - การ์ดทั้งหมดสามารถคลิกได้เนื่องจากมีอินพุตอยู่ภายใน
label. :has()ช่วยให้เรามีสไตล์ โฟกัส และ ตรวจสอบแล้ว ระบุบนบัตรผู้ปกครองโดยไม่มีการแฮ็ก ARIA- กริดตอบสนองให้ 1–3 คอลัมน์ โดยอัตโนมัติ เป้าหมายการสัมผัสจะยังคงมีขนาดใหญ่
ทางเลือก: การตรวจสอบอย่างอ่อนโยน (เจ้าของภาษาเป็นอันดับแรก)
const form = document.getElementById('plan');
const err = document.getElementById('plan-error');
form.addEventListener('submit', (e) => {
if (!form.reportValidity()) { // uses the `required` on first radio
err.hidden = false;
e.preventDefault();
} else {
err.hidden = true;
}
});กลยุทธ์การตรวจสอบที่ไม่สร้างความรำคาญ
การตรวจสอบความถูกต้องที่ยอดเยี่ยมจะมองไม่เห็นจนกว่าจะถึงเวลาจำเป็น นี่คือแนวทางเฉพาะสำหรับวิทยุที่รวดเร็ว เข้าถึงได้ และใช้งานง่ายสำหรับผู้ใช้
หลักการ
- ชาวพื้นเมืองก่อน ให้ HTML จัดการ “ต้องเลือกอันหนึ่ง”
- เลื่อนข้อผิดพลาด อย่าตะโกนใส่ผู้ใช้ก่อนที่พวกเขาจะพยายามดำเนินการต่อ
- ชี้ให้เห็นถึงปัญหา ประกาศข้อผิดพลาด ย้ายโฟกัสอย่างชาญฉลาด และเขียนข้อความให้สั้น
- ชัดเจนต่อการเปลี่ยนแปลง เมื่อทำการเลือกที่ถูกต้องแล้ว ให้ลบข้อผิดพลาดออก
พื้นฐาน (ดั้งเดิมเท่านั้น)
HTML เพียงอย่างเดียวสามารถบังคับใช้กฎได้ด้วย required ในวิทยุเครื่องหนึ่งในกลุ่ม
<fieldset id="svc" aria-describedby="svc-hint">
<legend>Which service do you need?</legend>
<p id="svc-hint">Pick one option.</p>
<label>
<input type="radio" name="service" value="link" required>
<span>Link Building</span>
</label>
<label>
<input type="radio" name="service" value="seo">
<span>Search Engine Optimization</span>
</label>
<label>
<input type="radio" name="service" value="web">
<span>Web Design / Development</span>
</label>
</fieldset>
สิ่งนี้จะทำให้คุณได้รับสิ่งต่อไปนี้: ลูกศรบนแป้นพิมพ์, พื้นที่สำหรับเลือก และการตรวจสอบระดับเบราว์เซอร์
ข้อผิดพลาดแบบอินไลน์ที่เป็นมิตร (การปรับปรุงแบบก้าวหน้า)
ปิดป๊อปอัปของเบราว์เซอร์และจัดการการส่งข้อความแบบอินไลน์เพื่อให้ตรงกับการออกแบบของคุณ
HTML (เพิ่มพื้นที่ข้อผิดพลาด)
<form id="quote" novalidate>
<!-- fieldset from above -->
<p id="svc-err" class="error" role="alert" hidden>Please choose one option.</p>
<button type="submit">Continue</button>
</form>
CSS (สไตล์เรียบง่าย)
.error { color:#b91c1c; margin-top:.5rem; font-size:.9rem; }
JS (เลื่อนข้อผิดพลาด ประกาศอย่างถูกต้อง)
const form = document.getElementById('quote');
const group = document.getElementById('svc');
const err = document.getElementById('svc-err');
const radios = form.querySelectorAll('input[name="service"]');
let attempted = false; // show errors only after user tries to continue
function hasSelection() {
return [...radios].some(r => r.checked);
}
function showError(msg='Please choose one option.') {
err.textContent = msg;
err.hidden = false;
group.setAttribute('aria-invalid', 'true');
// Append error to describedby so SRs announce it next
group.setAttribute('aria-describedby', 'svc-hint svc-err');
}
function clearError() {
err.hidden = true;
group.removeAttribute('aria-invalid');
group.setAttribute('aria-describedby', 'svc-hint');
}
form.addEventListener('submit', (e) => {
attempted = true;
if (!hasSelection()) {
e.preventDefault();
showError();
// Bring the group into view and place focus on the first radio
radios[0].focus();
group.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
clearError();
}
});
// Don’t nag early; clear once they pick something
radios.forEach(r => {
r.addEventListener('change', () => {
if (attempted) clearError();
});
});
ทำไมถึงได้ผล
- ไม่มีคำแนะนำเครื่องมือของเบราว์เซอร์ ผู้ใช้จะเห็นข้อผิดพลาด แบบอินไลน์ ข้างกลุ่ม
- โปรแกรมอ่านหน้าจอได้ยิน ตำนานแล้ว ความผิดพลาดขอบคุณ
aria-describedby. - ข้อผิดพลาดปรากฏเพียง หลังจาก ความพยายามในการส่ง (หรือเมื่อกลับมาแก้ไข)
การตรวจสอบแบบทีละขั้นตอนอย่างอ่อนโยน (แบบฟอร์มหลายขั้นตอน)
หากคุณใช้ปุ่ม "ถัดไป" ให้ตรวจสอบเพียงขั้นตอนนั้น
document.getElementById('nextBtn').addEventListener('click', () => {
if (!hasSelection()) {
showError();
} else {
clearError();
// advance to next step...
}
});
ข้อความที่กำหนดเองด้วย Constraint API (ทางเลือก)
หากคุณต้องการรักษารูปแบบความถูกต้องของเบราว์เซอร์ ให้ตั้งค่าข้อความแบบกำหนดเองบน เป็นครั้งแรก วิทยุในกลุ่ม
const firstRadio = radios[0];
form.addEventListener('submit', (e) => {
if (!hasSelection()) {
firstRadio.setCustomValidity('Please choose one option.');
// Triggers native UI; or call form.reportValidity()
e.preventDefault();
form.reportValidity();
} else {
firstRadio.setCustomValidity('');
}
});
ใช้สิ่งนี้เมื่อคุณต้องการ "ฟอง" ดั้งเดิมพร้อมข้อความของคุณเอง มิฉะนั้น ให้ยึดตามวิธีอินไลน์ด้านบนเพื่อให้ UI สอดคล้องกัน
สรุปข้อผิดพลาด (สำหรับแบบฟอร์มยาว)
หากแบบฟอร์มของคุณยาว ให้เพิ่ม บทสรุปด้านบน ที่เชื่อมโยงไปยังพื้นที่ที่มีปัญหา
<div id="error-summary" class="error" role="alert" hidden>
There’s a problem: <a href="#svc">Choose a service</a>.
</div>
function showSummary() {
const sum = document.getElementById('error-summary');
sum.hidden = false;
sum.querySelector('a').focus(); // SRs announce the alert
}
การสำรองฝั่งเซิร์ฟเวอร์และเครือข่าย
การตรวจสอบฝั่งไคลเอนต์ช่วยปรับปรุง UX แต่ เสมอ ตรวจสอบบนเซิร์ฟเวอร์ (หรือโดยบริการส่งของคุณ) ด้วย หากการส่งล้มเหลว ให้ส่งคืนหน้าด้วย:
- หน้าที่แล้ว การเลือกยังคงดำเนินต่อไปและ
- การขอ ข้อผิดพลาดอินไลน์เดียวกัน ข้างกลุ่ม
บูรณาการ Formspree (การปรับปรุงแบบก้าวหน้า)
ทำให้วิทยุทำงานได้แม้ว่า JavaScript จะล้มเหลว จากนั้นปรับปรุงเพื่อการขอบคุณที่ราบรื่นยิ่งขึ้นและการวิเคราะห์ที่ดีขึ้น
HTML ดั้งเดิม (ใช้งานได้เสมอ)
<form
id="quote"
action="https://formspree.io/f/your_form_id"
method="POST"
>
<fieldset>
<legend>Which service do you need?</legend>
<p id="svc-hint">Pick one option.</p>
<label class="card">
<input type="radio" name="service" value="link-building" required>
<span class="card-title">Link Building</span>
<span class="card-sub">Earn high-quality backlinks.</span>
</label>
<label class="card">
<input type="radio" name="service" value="seo">
<span class="card-title">Search Engine Optimization</span>
<span class="card-sub">Boost rankings & traffic.</span>
</label>
<label class="card">
<input type="radio" name="service" value="web-dev">
<span class="card-title">Web Design / Development</span>
<span class="card-sub">Fast, mobile-first sites.</span>
</label>
</fieldset>
<label>
<span>Full Name</span>
<input type="text" name="name" autocomplete="name" required>
</label>
<label>
<span>Email</span>
<input type="email" name="email" autocomplete="email" required>
</label>
<!-- Optional: subject line in your inbox -->
<input type="hidden" name="_subject" value="New lead from Contact form">
<!-- Simple honeypot -->
<input type="text" name="company" tabindex="-1" autocomplete="off" style="position:absolute; left:-9999px" aria-hidden="true">
<button type="submit">Get My Free Proposal</button>
<p id="form-error" class="error" role="alert" hidden>Something went wrong. Please try again.</p>
<p id="form-success" class="success" role="status" hidden>Thanks! We’ll be in touch shortly.</p>
</form>
สิ่งนี้จะทำให้คุณได้อะไร:
- ใช้งานได้แม้ปิด JS (ดีสำหรับ SEO, ความน่าเชื่อถือ)
- เบราว์เซอร์จัดการ “ต้องเลือกอันหนึ่ง” (
requiredในวิทยุครั้งแรก) - ฟอร์มสปรีได้รับ
service,name,emailและ_subject.
การปรับปรุง JS (UX ที่ดี, การติดตามที่ดีขึ้น)
สกัดกั้นการส่ง ตรวจสอบ ส่งด้วย fetchและแสดงคำขอบคุณแบบอินไลน์ (หรือการเปลี่ยนเส้นทาง)
<script>
(function(){
const form = document.getElementById('quote');
const okMsg = document.getElementById('form-success');
const errMsg = document.getElementById('form-error');
const submitBtn = form.querySelector('button[type="submit"]');
// Optional: capture UTM + page context
function appendMeta(fd){
const url = new URL(location.href);
['utm_source','utm_medium','utm_campaign','utm_term','utm_content'].forEach(k=>{
const v = url.searchParams.get(k);
if(v) fd.append(k, v);
});
fd.append('page_url', location.href);
if (document.referrer) fd.append('referrer', document.referrer);
}
form.addEventListener('submit', async (e) => {
// Keep native fallback if JS fails
e.preventDefault();
// Use native validity (includes radio required)
if (!form.reportValidity()) return;
const fd = new FormData(form);
// Don’t send honeypot if filled (treat as spam)
if (fd.get('company')) return; // silently drop bots
appendMeta(fd);
submitBtn.disabled = true;
const original = submitBtn.textContent;
submitBtn.textContent = 'Submitting…';
okMsg.hidden = true; errMsg.hidden = true;
try{
const res = await fetch(form.action, {
method: 'POST',
body: fd,
headers: { 'Accept': 'application/json' }
});
if(res.ok){
form.reset();
okMsg.hidden = false;
// Optional: fire analytics
if (window.gtag) gtag('event','generate_lead',{method:'formspree', service: fd.get('service')});
// Optional: redirect after success
// setTimeout(()=> location.href = '/thank-you/', 1200);
} else {
errMsg.hidden = false;
}
} catch {
errMsg.hidden = false;
} finally {
submitBtn.disabled = false;
submitBtn.textContent = original;
}
});
})();
</script>
หมายเหตุ :
- ชุด
Accept: application/jsonดังนั้น Formspree จึงส่งคืน JSON และคุณสามารถตัดสินความสำเร็จ/ความล้มเหลวได้อย่างชัดเจน - ใช้
form.reportValidity()เพื่อให้เบราว์เซอร์บังคับการเลือกวิทยุและรูปแบบอีเมลก่อนที่คุณจะส่ง - รักษาความเป็นพื้นเมือง
action/methodดังนั้นแบบฟอร์มยังคงทำงานได้โดยไม่ต้องใช้ JS
การควบคุมสแปมโดยไม่กระทบต่อการแปลง
- honeypot (รวมอยู่ด้านบน) จับบอทได้หลายตัวโดยไม่มีการเสียดสี
- การจับเวลา: เพิ่มซ่อนไว้
started_atเมื่อโหลดหน้า ให้ละเว้นการส่งข้อมูลที่เร็วกว่า เช่น 2 วินาที - สัญญาณเนื้อหา:หากคุณเห็นค่าขยะที่เกิดขึ้นซ้ำๆ ให้กรองค่าเหล่านั้นในกฎ Formspree ของคุณหรือหลังการประมวลผล
<input type="hidden" name="started_at" id="started_at">
<script>
document.getElementById('started_at').value = Date.now();
</script>
// in submit handler before sending:
const started = Number(fd.get('started_at'));
if (Date.now() - started < 2000) return; // looks like a bot
บรรทัดหัวเรื่องที่สะท้อนถึงตัวเลือกวิทยุ (กล่องจดหมายที่สะอาด)
คุณสามารถปรับแต่งได้ _subject จากวิทยุที่เลือก:
const service = fd.get('service'); // e.g., "seo"
fd.set('_subject', `New ${service} lead from Contact form`);
ข้อผิดพลาดทั่วไปที่ควรหลีกเลี่ยง
- การซ่อนวิทยุด้วย
display:none. ใช้opacity:0; position:absolute; inset:0ด้านในฉลากเพื่อให้แป้นพิมพ์ยังคงทำงานได้ - ลืม
fieldset/legend. โปรแกรมอ่านหน้าจอต้องอาศัยสิ่งเหล่านี้ในการดูบริบท - การตรวจสอบเกินจริงตั้งแต่เนิ่นๆ แสดงข้อผิดพลาดหลังจากส่ง/ถัดไป จากนั้นล้างทันทีที่ผู้ใช้แก้ไขกลุ่ม
- ไม่ได้จับบริบท เพิ่ม
page_url,referrerและฟิลด์ UTM คุณจะขอบคุณตัวเองในภายหลัง
ความเป็นสากลและการรวมกลุ่ม
ภาษา ฉลาก และน้ำเสียง
- ใช้หน้าเพจ
lang(และสลับหน้าแต่ละตำแหน่ง):<html lang="en"> … </html> - ให้ป้ายสั้นและ พรรณนาหลีกเลี่ยงศัพท์แสงเฉพาะด้านวัฒนธรรม
- ชอบ เป็นกลาง การใช้ถ้อยคำ (“เลือกแผน”) มากกว่าการตั้งสมมติฐาน (“งบประมาณของคุณคือเท่าไร”)
- ทรานสเลท ข้อผิดพลาด และ ข้อความช่วยเหลือ ด้วยพจนานุกรมง่ายๆ ที่มีคีย์
document.documentElement.lang.
const i18n = {
en: { choose: 'Please choose one option.' },
es: { choose: 'Elige una opción, por favor.' },
ar: { choose: 'يُرجى اختيار خيار واحد.' }
};
const t = (k) => (i18n[document.documentElement.lang] || i18n.en)[k];
errorEl.textContent = t('choose');
ฉลากยาวและตัวเลือกหลายบรรทัด
- ให้ป้ายชื่อ ห่อ; อย่าตัดคำสำคัญออก
- รักษาพื้นที่ที่โดนโจมตีให้มีขนาดใหญ่แม้ว่าข้อความจะห่อ: ใช้การเติมช่องว่างบน ฉลากไม่ใช่แค่ข้อมูลอินพุต
.card { inline-size: 100%; padding: 16px; }
.card .card-title { font-weight: 700; line-height: 1.25; }
.card .card-sub { font-size: 14px; line-height: 1.4; }
รองรับการอ่านจากขวาไปซ้าย (RTL)
ใช้ CSS คุณสมบัติเชิงตรรกะ และ :dir() คุณจึงไม่จำเป็นต้องมีสไตล์แยกกัน
/* Spacing that flips in RTL automatically */
.card { padding-inline: 16px; padding-block: 16px; }
/* Focus ring that respects direction */
.card:has(input:focus-visible) {
outline: 3px solid var(--ring);
outline-offset: 2px;
}
/* Optional: direction-specific tweaks */
:dir(rtl) .card-group { direction: rtl; }
การเข้าถึงที่มากกว่า ARIA
- ทำให้มั่นใจ ความคมชัด ≥ 4.5:1 สำหรับป้ายกำกับและสถานะโฟกัส
- เป้าหมายการสัมผัส ≥ 44 × 44 พิกเซล (การเติมมากกว่าขนาดตัวอักษร)
- อย่าสื่อสารสถานะด้วยสีเพียงอย่างเดียว ใช้ เส้นขอบ ไอคอน หรือข้อความ.
รายการตรวจสอบการทดสอบ (พิมพ์ได้)
- อรรถศาสตร์: วิทยุอยู่ข้างใน
fieldsetด้วยการมองเห็นได้legend. - แป้นพิมพ์: แท็บเข้าสู่กลุ่ม; ลูกศร เคลื่อนไหว; ช่องว่าง เลือก.
- โปรแกรมอ่านหน้าจอ:ประกาศตำนานแล้ว; แต่ละตัวเลือกจะระบุว่าเลือก/ไม่ได้เลือก
- โฟกัส: มองเห็นวงแหวนบน การ์ดทั้งหมด (และบนอินพุตดั้งเดิม)
- พื้นที่ตี:สามารถคลิกป้ายกำกับทั้งหมดได้ ใช้งานได้บนมือถือ
- การตรวจสอบ:ไม่มีข้อผิดพลาดจนกว่าจะส่ง/ถัดไป ข้อผิดพลาดจะชัดเจนเมื่อมีการเปลี่ยนแปลง
- คัดลอก: ตัวเลือกสั้น; ข้อความช่วยชี้แจงความแตกต่าง
- แบบ:1 คอลัมน์บนโทรศัพท์; 2–3 บนเดสก์ท็อป; ไม่มีการคลิปข้อมูลเกิน
- i18n: ข้อผิดพลาด/ป้ายกำกับได้รับการแปลแล้ว; ข้อความยาวถูกห่ออย่างสวยงาม; RTL โอเค
- บทวิเคราะห์: กิจกรรมเพื่อการแสดงผล เลือก ส่ง
- ประสิทธิภาพ:ไม่มีไลบรารี UI หนัก, CSS ขั้นต่ำ, ไม่มีการเปลี่ยนแปลงเค้าโครง
ประสิทธิภาพและการบำรุงรักษา
- ชอบ อินพุตดั้งเดิม + CSS เบาๆ หลีกเลี่ยงการแทนที่วิทยุด้วย div
- เก็บ CSS แบบโมดูลาร์ (คุณสมบัติแบบกำหนดเองสำหรับสี รัศมี วงแหวน)
- หลีกเลี่ยง
display:noneบนอินพุต; ใช้opacity:0; position:absolute; inset:0เพื่อให้การเข้าถึงยังคงเหมือนเดิม - เลื่อน JS ที่ไม่สำคัญออกไป และรักษาการปรับปรุงให้มีขนาดไม่เกิน 2–5 KB เมื่อเป็นไปได้
- ใช้ แบบสอบถามคอนเทนเนอร์ (เมื่อพร้อมใช้งาน) แทนจุดหยุดสื่อที่ซับซ้อนสำหรับแบบฟอร์มที่ฝังไว้
@container (min-width: 680px) {
.card-group { grid-template-columns: 1fr 1fr 1fr; }
}
การวิเคราะห์และการทดลอง
ติดตามความตั้งใจและจุดเสียดทาน:
// Fire when the radio group becomes visible
gtag?.('event','form_step_view', { step: 1, form: 'contact' });
// Fire on selection
document.querySelectorAll('input[name="service"]').forEach(r => {
r.addEventListener('change', () => {
gtag?.('event','radio_select', { group: 'service', value: r.value });
});
});
// Fire on successful submit
gtag?.('event','generate_lead', { form: 'contact', service: form.service.value });
การทดสอบ A/B:
- คำสั่งออปชั่น (ถูกเลือกมากที่สุดก่อน)
- สำเนา CTA (“รับข้อเสนอฟรีของฉัน” เทียบกับ “รับใบเสนอราคาของฉัน”)
- ข้อความช่วยเหลือ การมี
รูปแบบต่อต้าน (อย่าทำสิ่งเหล่านี้)
- การซ่อนอินพุตด้วย
display:none(ทำลายคีย์บอร์ดและ SR) - หายไป
fieldset/legend. - พื้นที่ที่ถูกโจมตีเล็กๆ (สามารถคลิกได้เฉพาะจุดเล็กๆ เท่านั้น)
- สถานะการเลือกสีเท่านั้น โฟกัสที่มองไม่เห็น
- กำลังตรวจสอบ ก่อน ผู้ใช้พยายามดำเนินการต่อ
- การบรรจุตัวเลือก 10–20 รายการลงในวิทยุ ให้ใช้ตัวเลือกที่สามารถค้นหาได้แทน
รายการตรวจสอบขั้นสุดท้าย
fieldset+legendนำเสนอ- เดียวกัน
nameไม่เหมือนใครvalues - ป้ายกำกับห่ออินพุต; คลิกการ์ดทั้งหมดได้
- วงแหวนโฟกัสมองเห็นได้ชัดเจน
requiredในวิทยุครั้งแรก ข้อผิดพลาดอินไลน์ที่อ่อนโยน- ตอบสนองกริด 1–3 คอลัมน์ เป้าหมายสัมผัสขนาดใหญ่
- สตริงที่แปลเป็นภาษาท้องถิ่น; RTL ปลอดภัยผ่านคุณสมบัติเชิงตรรกะ
- การวิเคราะห์ในการดู/เลือก/ส่ง
- กฎไคลเอนต์มิเรอร์การตรวจสอบฝั่งเซิร์ฟเวอร์
แหล่งข้อมูล
ข้อมูลจำเพาะและรูปแบบการเขียน
- มาตรฐาน HTML –
<input type="radio">: ความหมายดั้งเดิม การจัดกลุ่มตามname, พฤติกรรมแบบฟอร์ม - แนวปฏิบัติการเขียน WAI-ARIA 1.2 – กลุ่มวิทยุ: พฤติกรรมแป้นพิมพ์ที่คาดหวัง (กด Tab เข้ากลุ่ม ลูกศร การเคลื่อนย้าย ช่องว่าง เพื่อเลือกรูปแบบการติดฉลาก
- ดับเบิลยูซีจี 2.2 สิ่งจำเป็นสำหรับวิทยุ:
- 2.1.1 แป้นพิมพ์ (ทุกอย่างสั่งงานได้ผ่านคีย์บอร์ด)
- 2.4.7 โฟกัสที่มองเห็นได้ (โฟกัสที่มองเห็นได้ชัดเจน)
- 2.5.5 ขนาดของเป้าหมาย (แนะนำ ≥44×44px)
- 1.4.3 คอนทราสต์ (ขั้นต่ำ)
คู่มืออ้างอิง
- MDN: อินพุตวิทยุ,
label,fieldset/legend, การตรวจสอบแบบฟอร์ม (Constraint Validation API) - ระบบการออกแบบ GOV.UK – วิทยุ:คำแนะนำที่ยอดเยี่ยมและผ่านการทดสอบการต่อสู้ในเรื่องการใช้คำและข้อผิดพลาด
- ส่วนประกอบที่ครอบคลุม (เฮย์ดอน พิคเคอริง):รูปแบบและคำวิจารณ์เชิงปฏิบัติ A11y
ลองอ่านบล็อกล่าสุดของเราได้ที่ วิธีค้นหาโอกาสในการโพสต์แขกบน Ahrefs (คู่มือทีละขั้นตอน)
การทดสอบและเครื่องมือ
- ขวาน DevTools (ส่วนขยายเบราว์เซอร์) – การตรวจสอบ a11y อัตโนมัติ
- การเข้าถึงข้อมูลเชิงลึก – แบบทดสอบแนะนำอย่างรวดเร็ว
- ประภาคาร – สัญญาณประสิทธิภาพ + การเข้าถึง
- ตัวตรวจสอบความคมชัดของ WebAIM – ตรวจสอบความคมชัดของสี
- โปรแกรมอ่านหน้าจอ เพื่อตรวจสอบพฤติกรรมที่แท้จริง:
- NVDA (Windows) JAWS (Windows) VoiceOver (macOS/iOS) TalkBack (Android)
- รหัสผ่านแป้นพิมพ์ด้วยตนเอง: กดแท็บเข้าไปในกลุ่ม → ลูกศรย้าย → เลือกช่องว่าง → วงแหวนโฟกัสจะยังคงชัดเจน
ตรวจสอบ รีวิว Notions Marketing
คำถามที่พบบ่อย (สั้นและปฏิบัติได้จริง)
มุ่งหวัง 2 6-หากคุณมี 7–10 ให้จัดกลุ่มหรือใช้ตารางสองคอลัมน์ หากมากกว่า ~10 ให้เปลี่ยนเป็น เลือก (พร้อมค้นหา) หรือออกแบบใหม่เพื่อจำกัดตัวเลือกก่อน
ถ้ามีก็เพียงแต่มี ผิดนัดชำระหนี้เสียงข้างมากอย่างชัดเจน และจะไม่ทำให้ข้อมูลมีอคติ การเลือกล่วงหน้าจะช่วยเร่งกระบวนการให้เสร็จสมบูรณ์ แต่อาจช่วยลดการเลือกโดยเจตนา หากไม่แน่ใจ อย่าเลือกล่วงหน้า.
วิทยุ: ตัวเลือกเดียว ควรมีตัวเลือก มองเห็นได้ เคียงบ่าเคียงไหล่.
เลือก: ทางเลือกเดียวเมื่อพื้นที่จำกัดหรือมีตัวเลือกมากมาย
ช่องทำเครื่องหมาย: 0–หลาย ทางเลือก (ไม่แยกจากกัน)
ใช่ครับ เก็บไว้ อินพุตดั้งเดิมที่สามารถโฟกัสได้ (อย่าใช้ display:none). ใส่ข้อมูลอินพุต ภายใน <label>ตั้งค่าเป็น opacity:0; position:absolute; inset:0;และกำหนดรูปแบบฉลาก ใช้ :has(input:focus-visible) และ :has(input:checked) เพื่อขับเคลื่อนโฟกัส/สถานะที่เลือก
ใช้ HTML ก่อน: ใส่ required บนวิทยุครั้งแรก และโทร form.reportValidity() (หรือให้เบราว์เซอร์จัดการส่ง) สำหรับ UI ที่สวยงามขึ้น ให้แสดงข้อความสั้นๆ ข้อผิดพลาดแบบอินไลน์ ใกล้กลุ่ม หลังจาก ความพยายามครั้งแรกและ ชัดเจนเกี่ยวกับการเปลี่ยนแปลง.
role="radiogroup"?ไม่เหมาะสำหรับวิทยุท้องถิ่น เหมาะสม fieldset + legend ให้บริบทที่ถูกต้องแก่โปรแกรมอ่านหน้าจอแล้ว เพิ่ม ARIA เฉพาะในกรณีที่คุณกำลังสร้าง กำหนดเองได้อย่างเต็มที่ วิดเจ็ต (หลีกเลี่ยงถ้าเป็นไปได้)
แสดง/ซ่อนการติดตามผล หลังจาก การเลือก เมื่อซ่อนไว้ก็เช่นกัน ปิดการใช้งาน ตัวควบคุมเหล่านั้นจึงอยู่นอกลำดับแท็บและไม่ถูกส่ง เมื่อเปิดเผยแล้ว ให้ย้ายโฟกัสอย่างสมเหตุสมผลและตรวจสอบให้แน่ใจว่าป้ายกำกับยังคงชัดเจน
การไหลของแป้นพิมพ์ โฟกัสที่มองเห็นได้ การประกาศของโปรแกรมอ่านหน้าจอ (อ่านคำอธิบาย สถานะที่เลือก) พื้นที่การกดในอุปกรณ์เคลื่อนที่ เวลาการตรวจสอบ (ไม่มีการรบกวนก่อนกำหนด) และค่าที่เลือก สู้ ข้อผิดพลาดด้านเซิร์ฟเวอร์