Form — Conditional show/hide fields
A business-account form that grows with the answers it receives. Saying you have a company reveals company-name and team-size fields, unchecking same-as-billing slides open a shipping address, and picking Other on the referral question exposes a free-text note. Revealed blocks animate in, become required only while visible, and the validation pass skips anything hidden — so users never get blocked by fields they cannot see, with an accessible error summary on submit.
MCP
Code
:root {
--brand: #5b5bf0;
--brand-d: #4646d6;
--brand-700: #3a3ab8;
--brand-50: #eef0ff;
--accent: #00b4a6;
--accent-soft: #d8f5f2;
--ink: #101322;
--ink-2: #3a4060;
--muted: #6c7393;
--bg: #f6f7fb;
--white: #ffffff;
--surface: #ffffff;
--line: rgba(16, 19, 34, 0.1);
--line-2: rgba(16, 19, 34, 0.16);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(16, 19, 34, 0.08);
--sh-2: 0 8px 24px rgba(16, 19, 34, 0.08);
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.page {
min-height: 100vh;
display: grid;
place-items: center;
padding: 40px 20px;
background:
radial-gradient(900px 420px at 12% -8%, var(--brand-50), transparent 60%),
radial-gradient(700px 360px at 108% 12%, var(--accent-soft), transparent 55%),
var(--bg);
}
/* ── Card ── */
.card {
position: relative;
width: min(540px, 100%);
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-2);
padding: 30px 30px 26px;
overflow: hidden;
}
.card__head {
margin-bottom: 22px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.01em;
color: var(--brand-700);
background: var(--brand-50);
border: 1px solid rgba(91, 91, 240, 0.18);
padding: 5px 10px;
border-radius: 999px;
}
.badge svg {
color: var(--brand);
}
.card__title {
margin-top: 14px;
font-size: 24px;
font-weight: 800;
letter-spacing: -0.02em;
}
.card__sub {
margin-top: 6px;
font-size: 14px;
color: var(--muted);
}
/* ── Error summary ── */
.summary {
display: flex;
gap: 11px;
align-items: flex-start;
margin-bottom: 20px;
padding: 13px 15px;
background: #fdf2f0;
border: 1px solid rgba(212, 80, 62, 0.32);
border-radius: var(--r-md);
color: var(--danger);
animation: fade 0.25s ease;
}
.summary[hidden] {
display: none;
}
.summary > svg {
flex: none;
margin-top: 1px;
}
.summary__title {
font-size: 13.5px;
font-weight: 700;
color: #a23829;
}
.summary__list {
list-style: none;
margin-top: 5px;
display: grid;
gap: 3px;
}
.summary__list a {
font-size: 13px;
font-weight: 600;
color: #a23829;
text-decoration: underline;
text-underline-offset: 2px;
}
.summary__list a:hover {
color: var(--danger);
}
/* ── Form ── */
.form {
display: grid;
gap: 18px;
}
.field {
display: grid;
gap: 7px;
}
.field__label {
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
}
.req {
color: var(--danger);
font-weight: 700;
}
.control {
position: relative;
display: flex;
align-items: center;
}
.input {
width: 100%;
font: inherit;
font-size: 15px;
color: var(--ink);
background: var(--white);
border: 1.5px solid var(--line-2);
border-radius: var(--r-md);
padding: 12px 42px 12px 14px;
box-shadow: var(--sh-1);
transition:
border-color 0.16s ease,
box-shadow 0.16s ease,
background 0.16s ease;
}
.input::placeholder {
color: #aab0c8;
}
.input:hover:not(:focus) {
border-color: rgba(16, 19, 34, 0.26);
}
.input:focus-visible,
.input:focus {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 4px rgba(91, 91, 240, 0.16);
}
.input:disabled {
background: #f2f3f8;
color: var(--muted);
cursor: not-allowed;
box-shadow: none;
}
/* Native select styling */
.select {
appearance: none;
-webkit-appearance: none;
padding-right: 42px;
cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%236c7393' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 20px;
}
.field.is-valid .select,
.field.is-error .select {
background-position: right 38px center;
}
/* Status icon inside control */
.control__icon {
position: absolute;
right: 14px;
width: 18px;
height: 18px;
display: grid;
place-items: center;
opacity: 0;
transform: scale(0.7);
transition:
opacity 0.18s ease,
transform 0.18s ease;
pointer-events: none;
}
.control__icon::before {
content: "";
width: 18px;
height: 18px;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
/* ── Valid state ── */
.field.is-valid .input {
border-color: var(--ok);
}
.field.is-valid .input:focus-visible,
.field.is-valid .input:focus {
box-shadow: 0 0 0 4px rgba(47, 158, 111, 0.16);
}
.field.is-valid .control__icon {
opacity: 1;
transform: scale(1);
color: var(--ok);
}
.field.is-valid .control__icon::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%232f9e6f' d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E");
}
/* ── Error state ── */
.field.is-error .input {
border-color: var(--danger);
background: #fff7f5;
}
.field.is-error .input:focus-visible,
.field.is-error .input:focus {
box-shadow: 0 0 0 4px rgba(212, 80, 62, 0.16);
}
.field.is-error .control__icon {
opacity: 1;
transform: scale(1);
color: var(--danger);
}
.field.is-error .control__icon::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23d4503e' d='M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20m1 15h-2v-2h2zm0-4h-2V7h2z'/%3E%3C/svg%3E");
}
/* ── Helper / error text ── */
.help {
font-size: 12.5px;
color: var(--muted);
min-height: 1.1em;
transition: color 0.15s ease;
}
.help.is-error {
color: var(--danger);
font-weight: 500;
}
.help.is-ok {
color: var(--ok);
font-weight: 500;
}
/* ── Grouped question (fieldset) ── */
.group {
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 16px 16px 14px;
background: #fbfbfe;
display: grid;
gap: 12px;
}
.group__legend {
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
padding: 0;
}
.group.is-error {
border-color: rgba(212, 80, 62, 0.45);
background: #fff7f5;
}
/* ── Segmented radio (Yes / No) ── */
.seg {
display: flex;
gap: 8px;
}
.seg__opt {
flex: 1;
cursor: pointer;
}
.seg__opt input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.seg__pill {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: 14px;
font-weight: 600;
color: var(--ink-2);
background: var(--white);
border: 1.5px solid var(--line-2);
border-radius: var(--r-md);
padding: 11px 12px;
box-shadow: var(--sh-1);
transition:
border-color 0.16s ease,
background 0.16s ease,
color 0.16s ease,
box-shadow 0.16s ease;
}
.seg__opt:hover .seg__pill {
border-color: rgba(16, 19, 34, 0.26);
}
.seg__opt input:checked + .seg__pill {
color: var(--brand-700);
background: var(--brand-50);
border-color: var(--brand);
box-shadow: 0 0 0 1px var(--brand) inset;
}
.seg__opt input:focus-visible + .seg__pill {
outline: none;
box-shadow: 0 0 0 4px rgba(91, 91, 240, 0.3);
}
/* ── Checkbox row ── */
.check {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 14px;
color: var(--ink-2);
user-select: none;
}
.check input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.check__box {
flex: none;
width: 20px;
height: 20px;
display: grid;
place-items: center;
color: var(--white);
background: var(--white);
border: 1.5px solid var(--line-2);
border-radius: 6px;
box-shadow: var(--sh-1);
transition:
background 0.15s ease,
border-color 0.15s ease,
transform 0.12s ease;
}
.check__box svg {
opacity: 0;
transform: scale(0.5);
transition:
opacity 0.15s ease,
transform 0.15s ease;
}
.check input:checked + .check__box {
background: var(--brand);
border-color: var(--brand);
}
.check input:checked + .check__box svg {
opacity: 1;
transform: scale(1);
}
.check input:focus-visible + .check__box {
outline: none;
box-shadow: 0 0 0 4px rgba(91, 91, 240, 0.3);
}
/* ── Conditional reveal block ── */
.reveal {
display: grid;
grid-template-rows: 0fr;
opacity: 0;
transition:
grid-template-rows 0.32s cubic-bezier(0.2, 0.9, 0.3, 1),
opacity 0.28s ease;
}
.reveal[hidden] {
display: none;
}
.reveal.is-open {
grid-template-rows: 1fr;
opacity: 1;
}
.reveal__inner {
overflow: hidden;
min-height: 0;
}
/* small top spacing once content is visible */
.reveal.is-open .reveal__inner {
padding-top: 13px;
border-top: 1px dashed var(--line-2);
margin-top: 1px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
/* ── Submit ── */
.submit {
position: relative;
margin-top: 4px;
width: 100%;
font: inherit;
font-size: 15px;
font-weight: 700;
color: var(--white);
background: linear-gradient(180deg, var(--brand), var(--brand-d));
border: none;
border-radius: var(--r-md);
padding: 14px 16px;
cursor: pointer;
box-shadow: 0 8px 18px rgba(70, 70, 214, 0.28);
transition:
transform 0.12s ease,
box-shadow 0.16s ease,
background 0.16s ease,
opacity 0.16s ease;
}
.submit:hover:not(:disabled) {
background: linear-gradient(180deg, var(--brand-d), var(--brand-700));
}
.submit:active:not(:disabled) {
transform: translateY(1px);
}
.submit:focus-visible {
outline: none;
box-shadow:
0 0 0 4px rgba(91, 91, 240, 0.3),
0 8px 18px rgba(70, 70, 214, 0.28);
}
.submit:disabled {
cursor: not-allowed;
opacity: 0.55;
background: var(--ink-2);
box-shadow: none;
}
.submit__spin {
display: none;
width: 16px;
height: 16px;
margin-left: 9px;
vertical-align: -3px;
border: 2px solid rgba(255, 255, 255, 0.4);
border-top-color: var(--white);
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
.submit.is-loading .submit__spin {
display: inline-block;
}
.submit.is-loading .submit__label {
opacity: 0.85;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.form__foot {
font-size: 12px;
color: var(--muted);
text-align: center;
}
.link {
color: var(--brand-d);
font-weight: 600;
text-decoration: none;
}
.link:hover {
text-decoration: underline;
}
/* ── Success overlay ── */
.done {
position: absolute;
inset: 0;
background: var(--surface);
display: grid;
place-content: center;
justify-items: center;
text-align: center;
gap: 8px;
padding: 32px;
animation: fade 0.3s ease;
}
.done[hidden] {
display: none;
}
@keyframes fade {
from {
opacity: 0;
transform: translateY(8px);
}
}
.done__seal {
width: 64px;
height: 64px;
display: grid;
place-items: center;
color: var(--white);
background: linear-gradient(180deg, var(--ok), #257a57);
border-radius: 50%;
box-shadow: 0 8px 20px rgba(47, 158, 111, 0.35);
margin-bottom: 6px;
animation: pop 0.4s cubic-bezier(0.18, 1.4, 0.4, 1);
}
@keyframes pop {
from {
transform: scale(0.4);
opacity: 0;
}
}
.done__title {
font-size: 22px;
font-weight: 800;
letter-spacing: -0.01em;
}
.done__text {
font-size: 14px;
color: var(--muted);
max-width: 34ch;
}
.done__text strong {
color: var(--ink-2);
}
.ghost {
margin-top: 10px;
font: inherit;
font-size: 14px;
font-weight: 600;
color: var(--brand-d);
background: var(--brand-50);
border: 1px solid rgba(91, 91, 240, 0.2);
border-radius: var(--r-md);
padding: 10px 18px;
cursor: pointer;
transition: background 0.15s ease;
}
.ghost:hover {
background: #e3e6ff;
}
.ghost:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(91, 91, 240, 0.3);
}
/* ── Toast ── */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 140%);
background: var(--ink);
color: var(--white);
font-size: 13.5px;
font-weight: 500;
padding: 11px 18px;
border-radius: 999px;
box-shadow: var(--sh-2);
opacity: 0;
transition:
transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1),
opacity 0.3s ease;
pointer-events: none;
z-index: 50;
}
.toast.is-show {
transform: translate(-50%, 0);
opacity: 1;
}
.toast.is-error {
background: var(--danger);
}
.toast.is-ok {
background: var(--ok);
}
/* ── Responsive ── */
@media (max-width: 520px) {
.page {
padding: 18px 14px;
align-items: start;
}
.card {
padding: 24px 20px 22px;
border-radius: var(--r-md);
}
.card__title {
font-size: 21px;
}
.grid-2 {
grid-template-columns: 1fr;
}
.seg {
flex-direction: column;
}
.toast {
left: 14px;
right: 14px;
bottom: 14px;
transform: translateY(140%);
text-align: center;
}
.toast.is-show {
transform: translateY(0);
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
.reveal {
transition: none;
}
}(function () {
"use strict";
var form = document.getElementById("account");
var submitBtn = document.getElementById("submit");
var summary = document.getElementById("summary");
var summaryList = document.getElementById("summary-list");
var summaryTitle = document.getElementById("summary-title");
var done = document.getElementById("done");
var toastEl = document.getElementById("toast");
var toastTimer = null;
// ── Toast helper ──────────────────────────────────────────────
function toast(msg, kind) {
toastEl.textContent = msg;
toastEl.classList.remove("is-error", "is-ok");
if (kind) toastEl.classList.add("is-" + kind);
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2600);
}
// ── Field helpers ─────────────────────────────────────────────
// A "field" is the .field wrapper holding one control + help text.
function fieldOf(input) {
return input.closest(".field");
}
function helpOf(input) {
var f = fieldOf(input);
return f ? f.querySelector(".help") : null;
}
function setError(input, msg) {
var f = fieldOf(input);
if (f) f.classList.remove("is-valid");
if (f) f.classList.add("is-error");
input.setAttribute("aria-invalid", "true");
var help = helpOf(input);
if (help) {
if (!help.dataset.base) help.dataset.base = help.textContent;
help.textContent = msg;
help.classList.add("is-error");
help.classList.remove("is-ok");
}
}
function setValid(input) {
var f = fieldOf(input);
if (f) f.classList.remove("is-error");
if (f) f.classList.add("is-valid");
input.setAttribute("aria-invalid", "false");
var help = helpOf(input);
if (help) {
if (help.dataset.base) help.textContent = help.dataset.base;
help.classList.remove("is-error");
}
}
function clearState(input) {
var f = fieldOf(input);
if (f) f.classList.remove("is-error", "is-valid");
input.removeAttribute("aria-invalid");
var help = helpOf(input);
if (help) {
if (help.dataset.base) help.textContent = help.dataset.base;
help.classList.remove("is-error", "is-ok");
}
}
// ── Conditional reveal blocks ─────────────────────────────────
// Each reveal block is gated by an answer. When closed, its inner
// controls become non-required, disabled, and are skipped entirely
// by validation.
var reveals = {
company: document.querySelector('[data-reveal="company"]'),
shipping: document.querySelector('[data-reveal="shipping"]'),
referral: document.querySelector('[data-reveal="referral"]'),
};
// Controls that only count when their parent block is open.
function conditionalInputs(key) {
return Array.prototype.slice.call(
document.querySelectorAll('[data-conditional="' + key + '"]')
);
}
function openReveal(key) {
var block = reveals[key];
if (!block || block.classList.contains("is-open")) return;
block.hidden = false;
// force reflow so the grid-rows transition runs from 0fr
void block.offsetHeight;
block.classList.add("is-open");
conditionalInputs(key).forEach(function (input) {
input.disabled = false;
input.required = true;
input.setAttribute("aria-required", "true");
});
}
function closeReveal(key) {
var block = reveals[key];
if (!block || !block.classList.contains("is-open")) {
// Still make sure controls are inert if block was never opened.
conditionalInputs(key).forEach(function (input) {
input.disabled = true;
input.required = false;
input.removeAttribute("aria-required");
clearState(input);
});
if (block) block.hidden = true;
return;
}
block.classList.remove("is-open");
conditionalInputs(key).forEach(function (input) {
input.disabled = true;
input.required = false;
input.removeAttribute("aria-required");
clearState(input);
});
// hide after the collapse transition completes
var hide = function () {
if (!block.classList.contains("is-open")) block.hidden = true;
block.removeEventListener("transitionend", hide);
};
block.addEventListener("transitionend", hide);
// fallback for reduced-motion (no transitionend fires)
setTimeout(function () {
if (!block.classList.contains("is-open")) block.hidden = true;
}, 400);
}
// Initialise: company hidden, shipping hidden (same-as-billing checked),
// referral-other hidden. Disable their controls up front.
["company", "shipping", "referral"].forEach(function (key) {
conditionalInputs(key).forEach(function (input) {
input.disabled = true;
input.required = false;
});
});
// ── Validators ────────────────────────────────────────────────
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
// Returns "" when valid, otherwise an error message.
function validateInput(input) {
var name = input.name;
var value = (input.value || "").trim();
// Hidden / disabled conditional controls never fail.
if (input.disabled) return "";
if (input.required && value === "") {
return requiredMessage(name);
}
switch (name) {
case "name":
if (value.length < 2) return "Enter at least 2 characters.";
break;
case "email":
if (!EMAIL_RE.test(value))
return "Enter a valid email, like [email protected].";
break;
case "companyName":
if (value.length < 2) return "Enter your company's legal name.";
break;
case "billing":
if (value.length < 8) return "Enter a full street address.";
break;
case "shipping":
if (value.length < 8) return "Enter a full shipping address.";
break;
case "referralOther":
if (value.length < 2) return "A couple of words is enough.";
break;
default:
break;
}
return "";
}
function requiredMessage(name) {
var msgs = {
name: "Your name is required.",
email: "An email address is required.",
companyName: "Company name is required.",
companySize: "Pick a team size.",
billing: "A billing address is required.",
shipping: "A shipping address is required.",
referral: "Please choose an option.",
referralOther: "Please tell us where.",
};
return msgs[name] || "This field is required.";
}
function fieldLabel(name) {
var labels = {
name: "Full name",
email: "Work email",
hasCompany: "Company question",
companyName: "Company name",
companySize: "Team size",
billing: "Billing address",
shipping: "Shipping address",
referral: "How you heard about us",
referralOther: "Referral detail",
};
return labels[name] || name;
}
// Live validation for a single text/select input.
function checkField(input) {
var msg = validateInput(input);
if (msg) {
setError(input, msg);
return false;
}
if (!input.disabled && (input.value || "").trim() !== "") {
setValid(input);
} else {
clearState(input);
}
return true;
}
// ── Radio group: "Do you have a company?" ─────────────────────
form.querySelectorAll('input[name="hasCompany"]').forEach(function (radio) {
radio.addEventListener("change", function () {
var group = radio.closest(".group");
if (group) group.classList.remove("is-error");
if (radio.value === "yes") {
openReveal("company");
toast("Company details added", "ok");
} else {
closeReveal("company");
}
});
});
// ── Checkbox: "Shipping same as billing" ──────────────────────
var sameAddr = document.getElementById("sameAddr");
sameAddr.addEventListener("change", function () {
if (sameAddr.checked) {
closeReveal("shipping");
} else {
openReveal("shipping");
toast("Add a separate shipping address", "ok");
}
});
// ── Select: "How did you hear about us?" ──────────────────────
var referralSel = document.getElementById("referral");
referralSel.addEventListener("change", function () {
checkField(referralSel);
if (referralSel.value === "other") {
openReveal("referral");
} else {
closeReveal("referral");
}
});
// ── Live + blur validation wiring ─────────────────────────────
var watched = ["name", "email", "billing"];
watched.forEach(function (id) {
var input = document.getElementById(id);
input.addEventListener("blur", function () {
input.dataset.touched = "1";
checkField(input);
});
input.addEventListener("input", function () {
if (input.dataset.touched) checkField(input);
});
});
// Conditional text inputs validate live once touched.
["companyName", "shipping", "referralOther"].forEach(function (id) {
var input = document.getElementById(id);
input.addEventListener("blur", function () {
input.dataset.touched = "1";
checkField(input);
});
input.addEventListener("input", function () {
if (input.dataset.touched) checkField(input);
});
});
// Selects validate on change.
var companySize = document.getElementById("companySize");
companySize.addEventListener("change", function () {
checkField(companySize);
});
// ── Full validation pass on submit ────────────────────────────
function collectActiveControls() {
// Every named control that is currently enabled (i.e. visible
// or always-on). Disabled = inside a closed reveal = skipped.
var list = [];
form
.querySelectorAll("input[name], select[name]")
.forEach(function (el) {
if (el.type === "radio" || el.type === "checkbox") return;
if (el.disabled) return;
list.push(el);
});
return list;
}
function runFullValidation() {
var errors = [];
// Radio group is required (company question must be answered).
var hasCompany = form.querySelector('input[name="hasCompany"]:checked');
if (!hasCompany) {
var grp = form.querySelector('[data-group="company"]');
if (grp) grp.classList.add("is-error");
errors.push({
id: form.querySelector('input[name="hasCompany"]').id,
name: "hasCompany",
msg: "Tell us if this is for a company.",
});
}
collectActiveControls().forEach(function (input) {
var msg = validateInput(input);
if (msg) {
setError(input, msg);
errors.push({ id: input.id, name: input.name, msg: msg });
} else {
setValid(input);
}
});
return errors;
}
function showSummary(errors) {
summaryList.innerHTML = "";
summaryTitle.textContent =
errors.length === 1
? "Please fix 1 field"
: "Please fix " + errors.length + " fields";
errors.forEach(function (err) {
var li = document.createElement("li");
var a = document.createElement("a");
a.href = "#" + err.id;
a.textContent = fieldLabel(err.name) + " — " + err.msg;
a.addEventListener("click", function (e) {
e.preventDefault();
var target = document.getElementById(err.id);
if (target) {
target.focus();
target.scrollIntoView({ behavior: "smooth", block: "center" });
}
});
li.appendChild(a);
summaryList.appendChild(li);
});
summary.hidden = false;
summary.focus();
}
form.addEventListener("submit", function (e) {
e.preventDefault();
var errors = runFullValidation();
if (errors.length) {
showSummary(errors);
toast(
errors.length === 1
? "1 field needs attention"
: errors.length + " fields need attention",
"error"
);
return;
}
summary.hidden = true;
// Simulated async submit.
submitBtn.classList.add("is-loading");
submitBtn.disabled = true;
submitBtn.querySelector(".submit__label").textContent = "Creating…";
setTimeout(function () {
finish();
}, 950);
});
// ── Success state ─────────────────────────────────────────────
function finish() {
var name = (document.getElementById("name").value || "").trim();
var email = (document.getElementById("email").value || "").trim();
// Describe which conditional sections were actually filled in.
var extras = [];
if (form.querySelector('input[name="hasCompany"]:checked') &&
form.querySelector('input[name="hasCompany"]:checked').value === "yes") {
var cn = (document.getElementById("companyName").value || "").trim();
extras.push(cn ? cn : "your company");
}
if (!sameAddr.checked) extras.push("a separate shipping address");
if (referralSel.value === "other") extras.push("how you found us");
document.getElementById("done-name").textContent = name || "there";
document.getElementById("done-email").textContent = email || "you";
document.getElementById("done-fields").textContent = extras.length
? extras.join(", ")
: "your account details";
form.style.display = "none";
summary.hidden = true;
done.hidden = false;
done.querySelector(".done__title").focus();
toast("Account requested", "ok");
}
// ── Reset / start over ────────────────────────────────────────
document.getElementById("reset").addEventListener("click", function () {
form.reset();
form.style.display = "";
done.hidden = true;
summary.hidden = true;
// Reset visual field states + touched flags.
form.querySelectorAll(".field").forEach(function (f) {
f.classList.remove("is-error", "is-valid");
});
form.querySelectorAll(".group").forEach(function (g) {
g.classList.remove("is-error");
});
form.querySelectorAll("input, select").forEach(function (el) {
delete el.dataset.touched;
el.removeAttribute("aria-invalid");
});
form.querySelectorAll(".help").forEach(function (h) {
if (h.dataset.base) h.textContent = h.dataset.base;
h.classList.remove("is-error", "is-ok");
});
// Collapse all reveals (sameAddr returns to checked → shipping hidden).
closeReveal("company");
closeReveal("referral");
sameAddr.checked = true;
closeReveal("shipping");
// Restore submit button.
submitBtn.classList.remove("is-loading");
submitBtn.disabled = false;
submitBtn.querySelector(".submit__label").textContent = "Create account";
document.getElementById("name").focus();
toast("Form cleared", "ok");
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Conditional show/hide fields</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="page">
<section class="card" aria-labelledby="form-title">
<header class="card__head">
<span class="badge">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path
fill="currentColor"
d="M3 6h18v2H3zm3 5h12v2H6zm3 5h6v2H9z"
/>
</svg>
Adaptive form
</span>
<h1 id="form-title" class="card__title">Open a business account</h1>
<p class="card__sub">
The form grows with your answers — extra fields appear only when they apply,
and hidden fields are never validated.
</p>
</header>
<!-- Error summary (shown only on a failed submit) -->
<div
id="summary"
class="summary"
role="alert"
tabindex="-1"
hidden
>
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
<path
fill="currentColor"
d="M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20m1 15h-2v-2h2zm0-4h-2V7h2z"
/>
</svg>
<div>
<p class="summary__title" id="summary-title">Please fix these fields</p>
<ul class="summary__list" id="summary-list"></ul>
</div>
</div>
<form id="account" class="form" novalidate>
<!-- Full name -->
<div class="field" data-field="name">
<label class="field__label" for="name">
Full name <span class="req" aria-hidden="true">*</span>
</label>
<div class="control">
<input
id="name"
name="name"
type="text"
class="input"
autocomplete="name"
placeholder="Priya Ramanathan"
required
aria-required="true"
aria-describedby="name-help"
/>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="name-help" class="help">The primary contact for the account.</p>
</div>
<!-- Email -->
<div class="field" data-field="email">
<label class="field__label" for="email">
Work email <span class="req" aria-hidden="true">*</span>
</label>
<div class="control">
<input
id="email"
name="email"
type="email"
class="input"
autocomplete="email"
inputmode="email"
placeholder="[email protected]"
required
aria-required="true"
aria-describedby="email-help"
/>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="email-help" class="help">We'll send the welcome packet here.</p>
</div>
<!-- ── Company toggle ── -->
<fieldset class="group" data-group="company">
<legend class="group__legend">
Are you opening this for a company?
<span class="req" aria-hidden="true">*</span>
</legend>
<div class="seg" role="radiogroup" aria-label="Are you opening this for a company?">
<label class="seg__opt">
<input type="radio" name="hasCompany" value="yes" />
<span class="seg__pill">Yes, a company</span>
</label>
<label class="seg__opt">
<input type="radio" name="hasCompany" value="no" />
<span class="seg__pill">No, just me</span>
</label>
</div>
<!-- Reveal: company details -->
<div class="reveal" data-reveal="company" hidden>
<div class="reveal__inner">
<div class="grid-2">
<div class="field" data-field="companyName">
<label class="field__label" for="companyName">
Company name <span class="req" aria-hidden="true">*</span>
</label>
<div class="control">
<input
id="companyName"
name="companyName"
type="text"
class="input"
autocomplete="organization"
placeholder="Meridian Labs, Inc."
data-conditional="company"
aria-describedby="companyName-help"
/>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="companyName-help" class="help">As registered legally.</p>
</div>
<div class="field" data-field="companySize">
<label class="field__label" for="companySize">
Team size <span class="req" aria-hidden="true">*</span>
</label>
<div class="control">
<select
id="companySize"
name="companySize"
class="input select"
data-conditional="company"
aria-describedby="companySize-help"
>
<option value="" disabled selected>Choose a range</option>
<option value="1-9">1–9 people</option>
<option value="10-49">10–49 people</option>
<option value="50-249">50–249 people</option>
<option value="250+">250+ people</option>
</select>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="companySize-help" class="help">Helps us set sensible limits.</p>
</div>
</div>
</div>
</div>
</fieldset>
<!-- Billing address (always shown) -->
<div class="field" data-field="billing">
<label class="field__label" for="billing">
Billing address <span class="req" aria-hidden="true">*</span>
</label>
<div class="control">
<input
id="billing"
name="billing"
type="text"
class="input"
autocomplete="billing street-address"
placeholder="48 Halcyon Row, Portland, OR 97204"
required
aria-required="true"
aria-describedby="billing-help"
/>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="billing-help" class="help">Street, city, state, ZIP.</p>
</div>
<!-- ── Shipping toggle ── -->
<fieldset class="group" data-group="shipping">
<legend class="group__legend">Where should we ship hardware?</legend>
<label class="check">
<input type="checkbox" id="sameAddr" name="sameAddr" checked />
<span class="check__box" aria-hidden="true">
<svg viewBox="0 0 24 24" width="13" height="13">
<path
fill="currentColor"
d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
/>
</svg>
</span>
<span class="check__text">Shipping address is the same as billing</span>
</label>
<!-- Reveal: shipping address -->
<div class="reveal" data-reveal="shipping" hidden>
<div class="reveal__inner">
<div class="field" data-field="shipping">
<label class="field__label" for="shipping">
Shipping address <span class="req" aria-hidden="true">*</span>
</label>
<div class="control">
<input
id="shipping"
name="shipping"
type="text"
class="input"
autocomplete="shipping street-address"
placeholder="Suite 200, 19 Cedar Wharf, Seattle, WA 98101"
data-conditional="shipping"
aria-describedby="shipping-help"
/>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="shipping-help" class="help">Where deliveries should go instead.</p>
</div>
</div>
</div>
</fieldset>
<!-- ── Referral source ── -->
<fieldset class="group" data-group="referral">
<legend class="group__legend">
How did you hear about us? <span class="req" aria-hidden="true">*</span>
</legend>
<div class="field" data-field="referral">
<div class="control">
<select
id="referral"
name="referral"
class="input select"
required
aria-required="true"
aria-describedby="referral-help"
>
<option value="" disabled selected>Select a source</option>
<option value="search">Web search</option>
<option value="friend">A friend or colleague</option>
<option value="podcast">A podcast</option>
<option value="conference">A conference</option>
<option value="other">Other</option>
</select>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="referral-help" class="help">Just so we know what's working.</p>
</div>
<!-- Reveal: other source -->
<div class="reveal" data-reveal="referral" hidden>
<div class="reveal__inner">
<div class="field" data-field="referralOther">
<label class="field__label" for="referralOther">
Tell us where <span class="req" aria-hidden="true">*</span>
</label>
<div class="control">
<input
id="referralOther"
name="referralOther"
type="text"
class="input"
placeholder="e.g. a newsletter, a YouTube review…"
data-conditional="referral"
aria-describedby="referralOther-help"
/>
<span class="control__icon" aria-hidden="true"></span>
</div>
<p id="referralOther-help" class="help">A short note is plenty.</p>
</div>
</div>
</div>
</fieldset>
<button id="submit" type="submit" class="submit">
<span class="submit__label">Create account</span>
<span class="submit__spin" aria-hidden="true"></span>
</button>
<p class="form__foot">
We only ask for what your answers require.
<a href="#" class="link">Privacy notice</a>.
</p>
</form>
<!-- Success overlay -->
<div id="done" class="done" hidden>
<div class="done__seal" aria-hidden="true">
<svg viewBox="0 0 24 24" width="30" height="30">
<path
fill="currentColor"
d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
/>
</svg>
</div>
<h2 class="done__title" tabindex="-1">Account requested</h2>
<p class="done__text">
Thanks, <strong id="done-name">there</strong> — we captured
<strong id="done-fields">your details</strong> and will email
<strong id="done-email">you</strong> shortly.
</p>
<button type="button" class="ghost" id="reset">Start a new request</button>
</div>
</section>
</main>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Conditional show/hide fields
A business-account signup that only asks for what your answers require. Two questions and one checkbox act as gates: choosing “Yes, a company” reveals company name and team size; clearing “Shipping address is the same as billing” slides open a shipping field; and selecting “Other” on the referral dropdown exposes a short free-text note. Each gated block uses a CSS grid-template-rows 0fr → 1fr transition so content expands smoothly, with a fallback path for reduced-motion users.
The trick is that hidden fields are inert. When a block is collapsed its controls are disabled, lose their required/aria-required flags, and are cleared of any error or success styling. The submit pass only inspects controls that are currently enabled, so a user is never blocked by a field they cannot see; the moment a block opens, its fields become required again and validate live on blur and input.
A failed submit builds a role="alert" error summary listing each problem as an in-page anchor — clicking one focuses and scrolls to the field. The radio group, checkbox, and selects are fully keyboard-operable with visible focus rings, status is announced through an aria-live toast, and a successful submit shows an animated confirmation that names exactly which optional sections were filled in, plus a “Start a new request” action that resets every gate.