Marketplace

forms

HTML5 forms, Constraint Validation API, accessible form patterns, and modern input types

$ 安裝

git clone https://github.com/pluginagentmarketplace/custom-plugin-html /tmp/custom-plugin-html && cp -r /tmp/custom-plugin-html/skills/forms ~/.claude/skills/custom-plugin-html

// tip: Run this command in your terminal to install the skill


name: forms description: HTML5 forms, Constraint Validation API, accessible form patterns, and modern input types sasmp_version: "1.3.0" bonded_agent: html-expert bond_type: PRIMARY_BOND version: "2.0.0"

Skill Metadata

category: forms complexity: intermediate dependencies:

  • html-basics
  • accessibility

Parameter Validation

parameters: form_type: type: string required: true enum: [contact, login, registration, search, checkout, survey, multi-step] validation_mode: type: string default: "html5" enum: [html5, javascript, hybrid] accessibility_level: type: string default: "AA" enum: ["A", "AA", "AAA"]

Retry Configuration

retry: max_attempts: 3 backoff_ms: [1000, 2000, 4000] retryable_errors: [VALIDATION_ERROR, PARSE_ERROR]

Forms Skill

Production-grade HTML5 forms with Constraint Validation API and accessible patterns.

🎯 Purpose

Provide atomic, single-responsibility operations for:

  • Form structure and layout
  • Input types and validation
  • Constraint Validation API usage
  • Accessible form patterns
  • Error handling and display
  • Multi-step form logic

📥 Input Schema

interface FormsInput {
  operation: 'create' | 'validate' | 'pattern' | 'convert';
  form_type: FormType;
  fields?: FieldDefinition[];
  options?: {
    validation_mode: 'html5' | 'javascript' | 'hybrid';
    accessibility_level: 'A' | 'AA' | 'AAA';
    autocomplete: boolean;
    novalidate: boolean;
  };
}

type FormType =
  | 'contact'       // Name, email, message
  | 'login'         // Email/username, password
  | 'registration'  // Full user signup
  | 'search'        // Search input
  | 'checkout'      // Payment form
  | 'survey'        // Questions, ratings
  | 'multi-step';   // Wizard-style form

interface FieldDefinition {
  name: string;
  type: InputType;
  label: string;
  required?: boolean;
  pattern?: string;
  validation?: ValidationRule[];
}

📤 Output Schema

interface FormsOutput {
  success: boolean;
  markup: string;
  validation_script?: string;
  accessibility_score: number;
  issues: FormIssue[];
}

🛠️ Core Patterns

1. Input Types Reference

TypePurposeValidationKeyboard
textGeneric textpatterntext
emailEmail addressBuilt-in emailemail
passwordPasswordspatterntext
telPhone numberspatterntel
urlURLsBuilt-in URLurl
numberNumeric valuesmin/max/stepnumeric
dateDate pickermin/maxdate
timeTime pickermin/max/steptime
datetime-localDate + timemin/maxdatetime
searchSearch queriesNonesearch
colorColor pickerNone-
rangeSlidermin/max/step-
fileFile uploadaccept-

2. Complete Form Template

<form id="contact-form"
      action="/api/contact"
      method="POST"
      novalidate
      aria-labelledby="form-title">

  <h2 id="form-title">Contact Us</h2>

  <!-- Error Summary (initially hidden) -->
  <div id="error-summary"
       role="alert"
       aria-live="polite"
       hidden>
    <h3>Please fix the following errors:</h3>
    <ul id="error-list"></ul>
  </div>

  <!-- Name Field -->
  <div class="field">
    <label for="name">
      Full Name
      <span class="required" aria-hidden="true">*</span>
    </label>
    <input type="text"
           id="name"
           name="name"
           required
           aria-required="true"
           autocomplete="name"
           aria-describedby="name-hint">
    <p id="name-hint" class="hint">Enter your full name</p>
    <p id="name-error" class="error" aria-live="polite"></p>
  </div>

  <!-- Email Field -->
  <div class="field">
    <label for="email">
      Email
      <span class="required" aria-hidden="true">*</span>
    </label>
    <input type="email"
           id="email"
           name="email"
           required
           aria-required="true"
           autocomplete="email"
           aria-describedby="email-hint email-error">
    <p id="email-hint" class="hint">We'll never share your email</p>
    <p id="email-error" class="error" aria-live="polite"></p>
  </div>

  <!-- Message Field -->
  <div class="field">
    <label for="message">
      Message
      <span class="required" aria-hidden="true">*</span>
    </label>
    <textarea id="message"
              name="message"
              rows="5"
              required
              aria-required="true"
              minlength="10"
              maxlength="1000"
              aria-describedby="message-hint message-count"></textarea>
    <p id="message-hint" class="hint">10-1000 characters</p>
    <p id="message-count" class="hint" aria-live="polite">0/1000</p>
    <p id="message-error" class="error" aria-live="polite"></p>
  </div>

  <button type="submit">Send Message</button>
</form>

3. Constraint Validation API

const form = document.getElementById('contact-form');
const inputs = form.querySelectorAll('input, textarea, select');

// Disable browser default validation UI
form.setAttribute('novalidate', '');

// Validate on submit
form.addEventListener('submit', (e) => {
  if (!validateForm()) {
    e.preventDefault();
    showErrorSummary();
    focusFirstError();
  }
});

// Validate on blur for immediate feedback
inputs.forEach(input => {
  input.addEventListener('blur', () => validateField(input));
  input.addEventListener('input', () => {
    if (input.classList.contains('invalid')) {
      validateField(input);
    }
  });
});

function validateForm() {
  let isValid = true;
  inputs.forEach(input => {
    if (!validateField(input)) {
      isValid = false;
    }
  });
  return isValid;
}

function validateField(input) {
  const errorEl = document.getElementById(`${input.id}-error`);

  // Check validity using Constraint Validation API
  if (!input.checkValidity()) {
    const message = getErrorMessage(input);
    showError(input, errorEl, message);
    return false;
  }

  clearError(input, errorEl);
  return true;
}

function getErrorMessage(input) {
  const validity = input.validity;

  if (validity.valueMissing) {
    return `${input.labels[0].textContent} is required`;
  }
  if (validity.typeMismatch) {
    return `Please enter a valid ${input.type}`;
  }
  if (validity.patternMismatch) {
    return input.dataset.patternError || 'Please match the requested format';
  }
  if (validity.tooShort) {
    return `Must be at least ${input.minLength} characters`;
  }
  if (validity.tooLong) {
    return `Must be no more than ${input.maxLength} characters`;
  }
  if (validity.rangeUnderflow) {
    return `Must be at least ${input.min}`;
  }
  if (validity.rangeOverflow) {
    return `Must be no more than ${input.max}`;
  }

  return input.validationMessage;
}

function showError(input, errorEl, message) {
  input.classList.add('invalid');
  input.setAttribute('aria-invalid', 'true');
  input.setAttribute('aria-errormessage', errorEl.id);
  errorEl.textContent = message;
  errorEl.hidden = false;
}

function clearError(input, errorEl) {
  input.classList.remove('invalid');
  input.removeAttribute('aria-invalid');
  input.removeAttribute('aria-errormessage');
  errorEl.textContent = '';
  errorEl.hidden = true;
}

function focusFirstError() {
  const firstError = form.querySelector('.invalid');
  if (firstError) {
    firstError.focus();
  }
}

⚠️ Error Handling

Error Codes

CodeDescriptionRecovery
FORM001Missing form labelAdd aria-labelledby or aria-label
FORM002Input without labelAdd <label> element
FORM003Invalid pattern regexFix regex syntax
FORM004Missing required indicatorAdd visual + aria indicator
FORM005No error message elementAdd aria-live region
FORM006Autocomplete missingAdd autocomplete attribute
FORM007Submit without actionAdd form action
FORM008Fieldset without legendAdd legend element

ValidityState Properties

PropertyTrue When
validAll constraints satisfied
valueMissingRequired field is empty
typeMismatchValue doesn't match type (email, url)
patternMismatchValue doesn't match pattern
tooLongValue exceeds maxlength
tooShortValue below minlength
rangeUnderflowValue below min
rangeOverflowValue above max
stepMismatchValue doesn't match step
badInputBrowser can't convert input
customErrorsetCustomValidity() called

🔍 Troubleshooting

Problem: Form not validating

Debug Checklist:
□ novalidate attribute set?
□ JavaScript calling checkValidity()?
□ Required attribute on mandatory fields?
□ Pattern regex valid?
□ Type attribute correct?
□ Min/max values valid?

Problem: Errors not announced

Debug Checklist:
□ Error container has role="alert"?
□ aria-live="polite" or "assertive" set?
□ Error linked via aria-errormessage?
□ aria-invalid="true" on field?
□ Error content actually changing?

Problem: Autocomplete not working

Debug Checklist:
□ autocomplete attribute present?
□ Correct autocomplete value?
□ Input has name attribute?
□ Form has action?
□ Browser autocomplete enabled?

Autocomplete Values

ValuePurpose
nameFull name
given-nameFirst name
family-nameLast name
emailEmail address
telPhone number
street-addressStreet address
postal-codeZIP/Postal code
countryCountry
cc-numberCredit card number
cc-expCard expiration
cc-cscSecurity code
usernameUsername
current-passwordCurrent password
new-passwordNew password

📊 Form Types

Login Form

<form action="/login" method="POST" aria-labelledby="login-title">
  <h2 id="login-title">Sign In</h2>

  <div class="field">
    <label for="username">Email or Username</label>
    <input type="text"
           id="username"
           name="username"
           required
           autocomplete="username"
           autofocus>
  </div>

  <div class="field">
    <label for="password">Password</label>
    <input type="password"
           id="password"
           name="password"
           required
           autocomplete="current-password"
           minlength="8">
    <a href="/forgot-password">Forgot password?</a>
  </div>

  <label class="checkbox">
    <input type="checkbox" name="remember" value="1">
    Remember me
  </label>

  <button type="submit">Sign In</button>

  <p>Don't have an account? <a href="/register">Sign up</a></p>
</form>

Search Form

<form role="search"
      action="/search"
      method="GET"
      aria-label="Site search">
  <label for="search-input" class="visually-hidden">
    Search
  </label>
  <input type="search"
         id="search-input"
         name="q"
         placeholder="Search..."
         autocomplete="off"
         aria-describedby="search-hint">
  <button type="submit" aria-label="Submit search">
    <svg aria-hidden="true">...</svg>
  </button>
  <p id="search-hint" class="visually-hidden">
    Enter keywords to search
  </p>
</form>

Multi-Step Form

<form id="wizard" aria-labelledby="wizard-title">
  <h2 id="wizard-title">Registration</h2>

  <!-- Progress indicator -->
  <nav aria-label="Registration progress">
    <ol>
      <li aria-current="step">
        <span class="step-number">1</span>
        <span class="step-label">Account</span>
      </li>
      <li>
        <span class="step-number">2</span>
        <span class="step-label">Profile</span>
      </li>
      <li>
        <span class="step-number">3</span>
        <span class="step-label">Confirm</span>
      </li>
    </ol>
  </nav>

  <!-- Step 1: Account -->
  <fieldset id="step-1" class="step active">
    <legend>Account Information</legend>
    <div class="field">
      <label for="email">Email</label>
      <input type="email" id="email" name="email" required>
    </div>
    <div class="field">
      <label for="password">Password</label>
      <input type="password"
             id="password"
             name="password"
             required
             minlength="8"
             autocomplete="new-password">
    </div>
    <button type="button" onclick="nextStep(1)">Next</button>
  </fieldset>

  <!-- Step 2: Profile (hidden initially) -->
  <fieldset id="step-2" class="step" hidden>
    <legend>Profile Information</legend>
    <!-- fields -->
    <button type="button" onclick="prevStep(2)">Back</button>
    <button type="button" onclick="nextStep(2)">Next</button>
  </fieldset>

  <!-- Step 3: Confirm (hidden initially) -->
  <fieldset id="step-3" class="step" hidden>
    <legend>Confirm Details</legend>
    <!-- summary -->
    <button type="button" onclick="prevStep(3)">Back</button>
    <button type="submit">Create Account</button>
  </fieldset>
</form>

📋 Usage Examples

# Create contact form
skill: forms
operation: create
form_type: contact
options:
  validation_mode: hybrid
  accessibility_level: "AA"
  autocomplete: true

# Validate form markup
skill: forms
operation: validate
markup: "<form>...</form>"

# Get login form pattern
skill: forms
operation: pattern
form_type: login

🔗 References