consolelog.tools logo

Accessible Form Builder

Build WCAG-compliant forms with proper labels, validation, and error handling

Form Configuration

Form Fields

Field 1

HTML

<form
  id="contact-form"
  class="accessible-form"
  method="post"
  action="#"
  novalidate
  aria-labelledby="contact-form-title"
  
>
  <h2 id="contact-form-title">Contact Form</h2>
  

  <div class="form-group">
    <label for="name" class="form-label">
      Name
      <span class="required" aria-hidden="true">*</span><span class="sr-only">(required)</span>
    </label>
    <input
      type="text"
      id="name"
      name="name"
      class="form-input"
      
      required aria-required="true"
      
      
      
      
      
      
      
      aria-invalid="false"
    />
    
    
  </div>

  <div class="form-actions">
    <button type="submit" class="btn btn-primary">
      Submit
    </button>
    
  </div>

  <!-- Live region for form errors -->
  <div role="alert" aria-live="assertive" aria-atomic="true" class="form-alert" id="contact-form-alert"></div>
</form>

CSS

.accessible-form {
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem;
}

.form-group {
  margin-bottom: 1.5rem;
}

.form-label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 600;
  color: #1f2937;
}

.form-label .required {
  color: #dc2626;
  margin-left: 0.25rem;
}

.form-input,
.form-textarea,
.form-select {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid #d1d5db;
  border-radius: 0.375rem;
  font-size: 1rem;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.form-input:focus,
.form-textarea:focus,
.form-select:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.form-input:invalid:not(:focus),
.form-textarea:invalid:not(:focus),
.form-select:invalid:not(:focus) {
  border-color: #dc2626;
}

.form-input[aria-invalid="true"],
.form-textarea[aria-invalid="true"],
.form-select[aria-invalid="true"] {
  border-color: #dc2626;
}

.form-help-text {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  color: #6b7280;
}

.form-error {
  display: none;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  color: #dc2626;
}

.form-error.show {
  display: block;
}

.checkbox-group,
.radio-group {
  margin-bottom: 0.5rem;
}

.checkbox-label,
.radio-label {
  display: flex;
  align-items: center;
  font-weight: normal;
  cursor: pointer;
}

.checkbox-input,
.radio-input {
  width: 1.25rem;
  height: 1.25rem;
  margin-right: 0.75rem;
  cursor: pointer;
}

.checkbox-input:focus,
.radio-input:focus {
  outline: 2px solid #3b82f6;
  outline-offset: 2px;
}

.form-actions {
  display: flex;
  gap: 1rem;
  margin-top: 2rem;
}

.btn {
  padding: 0.75rem 1.5rem;
  font-size: 1rem;
  font-weight: 600;
  border: none;
  border-radius: 0.375rem;
  cursor: pointer;
  transition: background-color 0.2s, transform 0.1s;
}

.btn:focus {
  outline: 2px solid #3b82f6;
  outline-offset: 2px;
}

.btn:active {
  transform: scale(0.98);
}

.btn-primary {
  background-color: #3b82f6;
  color: white;
}

.btn-primary:hover {
  background-color: #2563eb;
}

.btn-secondary {
  background-color: #6b7280;
  color: white;
}

.btn-secondary:hover {
  background-color: #4b5563;
}

.form-alert {
  margin-top: 1rem;
  padding: 1rem;
  border-radius: 0.375rem;
  background-color: #fee2e2;
  border: 2px solid #dc2626;
  color: #991b1b;
  display: none;
}

.form-alert:not(:empty) {
  display: block;
}

/* Screen reader only text */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

JavaScript

// Form validation and accessibility enhancements
document.addEventListener('DOMContentLoaded', function() {
  const form = document.getElementById('contact-form');
  const alertBox = document.getElementById('contact-form-alert');

  form.addEventListener('submit', function(event) {
    event.preventDefault();

    // Clear previous errors
    const errors = form.querySelectorAll('.form-error');
    errors.forEach(error => error.classList.remove('show'));

    const inputs = form.querySelectorAll('[aria-invalid]');
    inputs.forEach(input => input.setAttribute('aria-invalid', 'false'));

    alertBox.textContent = '';

    // Validate form
    let isValid = true;
    let errorMessages = [];

    // Validate name
    const nameField = document.getElementById('name');
    if (nameField) {
      if (nameField.hasAttribute('required') && !nameField.value.trim()) {
        nameField.setAttribute('aria-invalid', 'true');
        const errorEl = document.getElementById('name-error');
        if (errorEl) errorEl.classList.add('show');
        errorMessages.push('Name');
        isValid = false;
      }
    }

    if (!isValid) {
      // Show errors in alert box
      alertBox.textContent = 'Please correct the errors in the form: ' + errorMessages.join(', ');

      // Focus first error
      const firstError = form.querySelector('[aria-invalid="true"]');
      if (firstError) {
        firstError.focus();
      }

      return false;
    }

    // Form is valid - submit
    alertBox.textContent = '';
    console.log('Form submitted successfully!');

    // In real implementation, submit to server
    // form.submit();
  });

  // Real-time validation on blur
  form.querySelectorAll('.form-input, .form-textarea, .form-select').forEach(input => {
    input.addEventListener('blur', function() {
      validateField(this);
    });
  });

  function validateField(field) {
    const errorElement = document.getElementById(field.id + '-error');

    if (field.hasAttribute('required') && !field.value.trim()) {
      field.setAttribute('aria-invalid', 'true');
      if (errorElement) {
        errorElement.classList.add('show');
      }
    } else if (field.validity && !field.validity.valid) {
      field.setAttribute('aria-invalid', 'true');
      if (errorElement) {
        errorElement.classList.add('show');
      }
    } else {
      field.setAttribute('aria-invalid', 'false');
      if (errorElement) {
        errorElement.classList.remove('show');
      }
    }
  }
});

WCAG Compliance

  • WCAG 2.1 Level A - 1.3.1 Info and Relationships: Proper form structure with labels
  • WCAG 2.1 Level A - 3.3.1 Error Identification: Clear error messages
  • WCAG 2.1 Level A - 3.3.2 Labels or Instructions: All fields have labels
  • WCAG 2.1 Level A - 4.1.2 Name, Role, Value: Proper ARIA attributes
  • WCAG 2.1 Level AA - 3.3.3 Error Suggestion: Helpful error messages
  • WCAG 2.1 Level AA - 3.3.4 Error Prevention: Confirmation for data submission

Accessibility Features

  • All form fields have associated labels
  • Required fields clearly marked with * and "(required)" for screen readers
  • Help text associated with fields via aria-describedby
  • Error messages announced via aria-live region
  • Invalid fields marked with aria-invalid
  • Keyboard accessible (Tab, Enter, Space)
  • Visible focus indicators on all interactive elements
  • Form title and description properly labeled

Form Accessibility Best Practices

Labels

  • Every form field must have a label
  • Use <label> element with for attribute
  • Label text should be visible and descriptive
  • Don't rely on placeholder text as labels
  • Group related fields with <fieldset> and <legend>
View example
<label for="email">Email Address</label>
<input type="email" id="email" name="email" />

Required Fields

  • Mark required fields with required attribute
  • Add aria-required="true"
  • Indicate visually with * or "required" text
  • Add "(required)" in sr-only text for screen readers
  • Explain required field format before form
View example
<label for="name">
  Name
  <span aria-hidden="true">*</span>
  <span class="sr-only">(required)</span>
</label>
<input type="text" id="name" required aria-required="true" />

Error Handling

  • Provide clear, specific error messages
  • Use aria-invalid on invalid fields
  • Link errors to fields with aria-describedby
  • Use role="alert" or aria-live for dynamic errors
  • Move focus to first error on submit
View example
<input
  type="email"
  id="email"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<span id="email-error" role="alert">
  Please enter a valid email address
</span>

Help Text

  • Provide instructions before form fields when needed
  • Link help text with aria-describedby
  • Keep help text concise
  • Use aria-describedby for multiple descriptions
  • Don't hide important info in tooltips
View example
<label for="password">Password</label>
<input
  type="password"
  id="password"
  aria-describedby="password-help"
/>
<span id="password-help">
  Must be at least 8 characters
</span>

Was this tool helpful?

Share Your Experience

Help others discover this tool!

Frequently Asked Questions - Accessible Form Builder