01

Form structure

5 of 20 fields

02

Selected field

Field 1 of 5

Unique name. Start with a letter; use letters, digits, underscores, or hyphens.

Adjacent fields with the same legend share a fieldset. Radio choices always have their own group legend.

03

Try the fields

The preview resets when the schema changes. Use fictional values. Check fields locally runs browser validation and never sends data. Preview colors follow the browser’s light/dark preference.

5 fields ready. Review labels, instructions, and keyboard order.

HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'sha256-P4X0P2509qqOel33HmrxvKicF4taXdaFQCPWJX0v+o4='; connect-src 'none'; img-src 'none'; form-action 'none'; base-uri 'none'; object-src 'none'" />
<title>Project enquiry</title>
<style>
:root { color-scheme: light dark; font: 100%/1.6 system-ui, sans-serif; color: #172a3a; background: #f2f5f8; }
* { box-sizing: border-box; }
body { margin: 0; padding: clamp(1rem, 4vw, 3rem); }
main { max-width: 44rem; margin-inline: auto; }
h1 { font-size: clamp(1.7rem, 5vw, 2.4rem); line-height: 1.15; }
form { padding: clamp(1rem, 4vw, 2rem); border: 1px solid #bcc9d5; border-radius: .8rem; background: #fff; }
.field { margin-block: 0 1.5rem; min-width: 0; }
fieldset { min-width: 0; margin: 0 0 1.5rem; padding: 1rem; border: 1px solid #99adbe; border-radius: .4rem; }
legend, label { font-weight: 650; }
label { display: block; margin-bottom: .4rem; overflow-wrap: anywhere; }
legend { padding-inline: .35rem; overflow-wrap: anywhere; }
input:not([type=checkbox]):not([type=radio]), textarea, select { display: block; width: 100%; min-height: 44px; padding: .65rem; border: 1px solid #647c8e; border-radius: .3rem; font: inherit; color: inherit; background: #fff; }
textarea { min-height: 7rem; resize: vertical; }
.choice { display: flex; align-items: flex-start; gap: .6rem; min-height: 44px; padding-block: .5rem; }
input[type=checkbox], input[type=radio] { flex-shrink: 0; width: 1.2rem; height: 1.2rem; margin: .3rem 0 0; accent-color: #1858a1; }
.help, .notice { font-size: .9rem; color: #465f72; }
.help { margin: .45rem 0 0; }
.required { font-size: .85rem; font-weight: 400; }
button { min-height: 44px; padding: .7rem 1.1rem; border: 0; border-radius: .35rem; background: #1858a1; color: #fff; font: inherit; font-weight: 650; cursor: pointer; }
:focus-visible { outline: 3px solid #b64d00; outline-offset: 3px; }
#validation-status { min-height: 1.6em; overflow-wrap: anywhere; }
@media (prefers-color-scheme: dark) { :root { color: #edf3f8; background: #101b26; } form, input:not([type=checkbox]):not([type=radio]), textarea, select { background: #172737; } .help, .notice { color: #b8ccdd; } button { background: #96c9ff; color: #102338; } :focus-visible { outline-color: #ffb974; } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; } }
</style>
</head>
<body>
<main>
<h1>Project enquiry</h1>
<p>Tell us what you have in mind. This is a local form example.</p>
<p class="notice">Fields marked required must be completed. This example checks fields locally; it has no submission destination.</p>
<form id="generated-form" aria-label="Project enquiry">
<fieldset>
<legend>Contact details</legend>
<div class="field">
<label for="field-1">Your name <span class="required">(required)</span></label>
<input type="text" id="field-1" name="full_name" required />
</div>
<div class="field">
<label for="field-2">Email address <span class="required">(required)</span></label>
<input type="email" id="field-2" name="email" required aria-describedby="field-2-help" />
<p class="help" id="field-2-help">Use an address where you can receive a reply.</p>
</div>
</fieldset>
<div class="field">
<label for="field-3">Project type <span class="required">(required)</span></label>
<select id="field-3" name="project" required>
<option value="">Choose an option</option>
<option value="A new website">A new website</option>
<option value="An existing website">An existing website</option>
<option value="Something else">Something else</option>
</select>
</div>
<fieldset class="field">
<legend>When would you like to begin? <span class="required">(optional)</span></legend>
<label class="choice" for="field-4-0"><input type="radio" id="field-4-0" name="timing" value="Soon" /> <span>Soon</span></label>
<label class="choice" for="field-4-1"><input type="radio" id="field-4-1" name="timing" value="In a few months" /> <span>In a few months</span></label>
<label class="choice" for="field-4-2"><input type="radio" id="field-4-2" name="timing" value="Still exploring" /> <span>Still exploring</span></label>
</fieldset>
<div class="field">
<label for="field-5">A little about the project <span class="required">(optional)</span></label>
<textarea id="field-5" name="notes" aria-describedby="field-5-help" rows="4"></textarea>
<p class="help" id="field-5-help">Avoid private or sensitive information in this demonstration.</p>
</div>
<button type="button" id="check-fields" hidden>Check fields locally</button>
<p id="validation-status" role="status" aria-live="polite"></p>
<noscript><p>The fields remain usable. Local validation checks need JavaScript; nothing is submitted.</p></noscript>
</form>
</main>
<script>
(() => {
  const form = document.getElementById('generated-form');
  const status = document.getElementById('validation-status');
  const check = document.getElementById('check-fields');
  check.hidden = false;
  form.addEventListener('submit', (event) => event.preventDefault());
  form.addEventListener('input', () => { status.textContent = ''; });
  form.addEventListener('change', () => { status.textContent = ''; });
  check.addEventListener('click', () => {
    const valid = form.reportValidity();
    if (!valid) {
      const invalid = [...form.querySelectorAll('input, select, textarea')].find((field) => !field.validity.valid);
      invalid?.focus();
    }
    status.textContent = valid
      ? 'Native field checks passed. Nothing was sent. Manual accessibility testing is still needed.'
      : 'Check the highlighted field. Nothing was sent.';
  });
})();
</script>
</body>
</html>

Start with native form semantics

  1. Set a form title and introduction, then select a field from the structure list.
  2. Choose a native type, a visible label, a unique field name, and optional help. Set required only when the information is necessary.
  3. Add select or radio choices, and use a fieldset legend to group related adjacent fields. Move fields up or down to set their reading and keyboard order.
  4. Try the preview with missing and completed values. Copy or download the exact displayed document, then test and adapt it in the context of your own page.

Labels, help, and groups

Every ordinary control has a visible label linked to its stable generated ID. Help text is connected with aria-describedby. Radio choices share a name and live in a fieldset with a legend; each radio also has its own label. Selects begin with an empty prompt so a required selection is meaningful. Required and optional status is written out rather than conveyed through color alone. Related fields can share a fieldset when their identical group legends appear consecutively.

For example, a required email field uses the browser's email type and required attribute. It does not check whether an address exists or belongs to someone. A telephone field accepts plain text because phone formats differ. A required checkbox means that particular checkbox must be checked; it is not a multiple-choice group requirement. Numeric inputs keep native browser number behavior, including the default step. Custom patterns, cross-field rules, conditional fields, uploads, passwords, and multi-step flows are outside this builder.

What is included in the download?

The output is one complete HTML document with inline light/dark CSS and a small fixed script for local native validation. Labels, choices, and help are escaped as text. The script calls native constraint validation from a button, clears stale feedback when values change, and never submits. The preview uses an opaque iframe with scripts allowed but no form-submission or same-origin permission. The document's policy also blocks network resources and form delivery.

There is no action URL, contact recipient, storage, server validation, or successful-submission state. To turn the download into a real form, supply a secure submission design, review the restrictive policy, implement server validation and appropriate error handling, and replace the demonstration notice and local-check action. Browser checks alone do not secure submitted data. Do not put sensitive information in this preview.

Manual accessibility review is still required

The name describes the building blocks, not a certification. Review wording, grouping, required choices, contrast, zoom, keyboard order, errors, and screen-reader announcements in the actual page and supported browsers. Consult the W3C WAI guidance on labeling controls , grouping controls , and form instructions . A technically associated label can still be confusing or insufficient.

Limits and local state

Use 1 to 20 fields and 2 to 12 choices for each select or radio group. Labels and fieldset legends allow 120 characters, help and introductions 400, names 64, and each choice 100. Names must be unique and use the documented ASCII pattern. Invalid schemas clear both the preview and export until corrected. Schema changes stay only in the current page; copy or download before leaving. A browser may restore ordinary input values during navigation, so this is not a confidential workspace.