ปุ่มตัวเลือก HTML ที่แปลง: คู่มือฉบับสมบูรณ์

แก้ไขล่าสุดเมื่อ 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

คำถามที่พบบ่อย (สั้นและปฏิบัติได้จริง)

1) มีตัวเลือกกี่ตัวที่มากเกินไปสำหรับวิทยุ?

มุ่งหวัง 2 6-หากคุณมี 7–10 ให้จัดกลุ่มหรือใช้ตารางสองคอลัมน์ หากมากกว่า ~10 ให้เปลี่ยนเป็น เลือก (พร้อมค้นหา) หรือออกแบบใหม่เพื่อจำกัดตัวเลือกก่อน

2) ฉันควรเลือกหนึ่งตัวเลือกล่วงหน้าหรือไม่?

ถ้ามีก็เพียงแต่มี ผิดนัดชำระหนี้เสียงข้างมากอย่างชัดเจน และจะไม่ทำให้ข้อมูลมีอคติ การเลือกล่วงหน้าจะช่วยเร่งกระบวนการให้เสร็จสมบูรณ์ แต่อาจช่วยลดการเลือกโดยเจตนา หากไม่แน่ใจ อย่าเลือกล่วงหน้า.

3) วิทยุ เทียบกับ เลือก เทียบกับ กล่องกาเครื่องหมาย ฉันควรเลือกอะไร?

วิทยุ: ตัวเลือกเดียว ควรมีตัวเลือก มองเห็นได้ เคียงบ่าเคียงไหล่.
เลือก: ทางเลือกเดียวเมื่อพื้นที่จำกัดหรือมีตัวเลือกมากมาย
ช่องทำเครื่องหมาย: 0–หลาย ทางเลือก (ไม่แยกจากกัน)

4) ฉันสามารถจัดรูปแบบวิทยุเป็น "การ์ด" โดยไม่กระทบต่อการเข้าถึงได้หรือไม่

ใช่ครับ เก็บไว้ อินพุตดั้งเดิมที่สามารถโฟกัสได้ (อย่าใช้ display:none). ใส่ข้อมูลอินพุต ภายใน <label>ตั้งค่าเป็น opacity:0; position:absolute; inset:0;และกำหนดรูปแบบฉลาก ใช้ :has(input:focus-visible) และ :has(input:checked) เพื่อขับเคลื่อนโฟกัส/สถานะที่เลือก

5) วิธีที่ดีที่สุดในการตรวจสอบกลุ่มวิทยุคืออะไร

ใช้ HTML ก่อน: ใส่ required บนวิทยุครั้งแรก และโทร form.reportValidity() (หรือให้เบราว์เซอร์จัดการส่ง) สำหรับ UI ที่สวยงามขึ้น ให้แสดงข้อความสั้นๆ ข้อผิดพลาดแบบอินไลน์ ใกล้กลุ่ม หลังจาก ความพยายามครั้งแรกและ ชัดเจนเกี่ยวกับการเปลี่ยนแปลง.

6) ฉันต้องมีบทบาท ARIA เช่น role="radiogroup"?

ไม่เหมาะสำหรับวิทยุท้องถิ่น เหมาะสม fieldset + legend ให้บริบทที่ถูกต้องแก่โปรแกรมอ่านหน้าจอแล้ว เพิ่ม ARIA เฉพาะในกรณีที่คุณกำลังสร้าง กำหนดเองได้อย่างเต็มที่ วิดเจ็ต (หลีกเลี่ยงถ้าเป็นไปได้)

7) ฉันจะจัดการกับฟิลด์ติดตามแบบมีเงื่อนไขได้อย่างไร (แสดงอินพุตที่แตกต่างกันตามการเลือก)

แสดง/ซ่อนการติดตามผล หลังจาก การเลือก เมื่อซ่อนไว้ก็เช่นกัน ปิดการใช้งาน ตัวควบคุมเหล่านั้นจึงอยู่นอกลำดับแท็บและไม่ถูกส่ง เมื่อเปิดเผยแล้ว ให้ย้ายโฟกัสอย่างสมเหตุสมผลและตรวจสอบให้แน่ใจว่าป้ายกำกับยังคงชัดเจน

8) ฉันควรทดสอบอะไรก่อนจัดส่ง?

การไหลของแป้นพิมพ์ โฟกัสที่มองเห็นได้ การประกาศของโปรแกรมอ่านหน้าจอ (อ่านคำอธิบาย สถานะที่เลือก) พื้นที่การกดในอุปกรณ์เคลื่อนที่ เวลาการตรวจสอบ (ไม่มีการรบกวนก่อนกำหนด) และค่าที่เลือก สู้ ข้อผิดพลาดด้านเซิร์ฟเวอร์

เข้าร่วมจดหมายข่าวของเราเพื่อรับการอัปเดตล่าสุดโดยตรง

แสดงความคิดเห็น

ที่อยู่อีเมลของคุณจะไม่ถูกเผยแพร่ ช่องที่ต้องการถูกทำเครื่องหมาย *