Pattern 5: Error as Collaboration
Opening Scenario: The Accusatory Address
Sarah was trying to register her daughter for a new school district after relocating to Pittsburgh. The online registration form was going well until she reached the address field.
She typed her new address: 123 Main Street, Pittsburgh PA 15217
She clicked "Continue" and saw:
ERROR: Invalid address format
Sarah stared at the screen, confused. What's wrong with my address? That's where I live.
She tried again, this time being more careful: 123 Main St., Pittsburgh, PA 15217
ERROR: Invalid address format
Getting frustrated now. She tried variations: - 123 Main Street Pittsburgh PA 15217 (no commas) - 123 Main St Pittsburgh Pennsylvania 15217 (spelled out state) - 123 Main Street, Pittsburgh, Pennsylvania, 15217 (more commas)
Each attempt: ERROR: Invalid address format
After ten minutes and eight failed attempts, Sarah called the school office. The clerk said, "Oh, you need to put the apartment or unit number. If you don't have one, just put 'N/A' in that field."
Sarah hadn't even seen an apartment field—it was hidden below the fold. The error message never mentioned it. The form knew what was wrong but chose to say "invalid format" instead of "We need your apartment number, or enter N/A if you don't have one."
Three years later, Sarah was helping design forms for her company. She remembered her registration nightmare and insisted on better error handling. Her team built a different approach:
Address: 123 Main Street, Pittsburgh PA 15217
[System validates in real-time as user types]
✓ Street address looks good
✓ City found
✓ ZIP code valid for Pittsburgh
Apartment/Unit number: [ ]
[If you don't have a unit number, you can skip this field]
[Continue]
If the user tried to continue without filling required hidden fields, instead of "ERROR," they saw:
We're almost done! Just need one more thing:
Apartment or Unit Number
We need this to complete your mailing address. If you don't have
a unit number (single-family home), just click "No unit number" below.
[ ] [No unit number]
Sarah's daughter never saw an error message during registration. The form helped her succeed rather than catching her in mistakes.
Context
Error as Collaboration applies when:
User input requires validation: Forms need to enforce rules, formats, and constraints
Mistakes are inevitable: Complex forms generate errors no matter how well-designed
Errors have varying severity: Some must block (invalid credit card), others should warn (unusual but valid input)
Recovery paths exist: User can usually fix the error if properly guided
Domain knowledge aids correction: System knows more than "this is wrong"—it knows why and how to fix it
User intent can be inferred: System can often guess what user meant to enter
Trust matters: Adversarial error handling damages the user-system relationship
Problem Statement
Most forms treat errors as user failures rather than communication breakdowns. Common patterns:
Accusatory messaging:
ERROR: Invalid input
ERROR: Wrong format
ERROR: This field is required
These messages blame the user. They're terse, unhelpful, and frustrating.
Late error detection:
[User fills out 30 fields over 15 minutes]
[Clicks Submit]
[Page reloads showing 7 error messages]
[Some fields have lost their values]
Batch validation at the end forces users to hunt for problems and remember what they entered.
Technical jargon:
ERROR: Input does not match regex pattern ^[A-Z]{2}\d{5}$
ERROR: Constraint violation on field_name_internal
ERROR: NULL value in NON NULL column
These expose implementation details users can't understand or act upon.
No guidance for correction:
Date of Birth: [ / / ]
ERROR: Invalid date
What's invalid? Format? Value? Did I use a future date? Is the field day-first or month-first?
Binary validation:
ERROR: Email address is invalid
Even when user entered: john.smith@company.co (missing .uk or .com)
System knows it's almost valid—missing a TLD—but only reports "invalid."
No progressive assistance:
Password: [ ]
ERROR: Password must contain at least 8 characters, one uppercase letter,
one lowercase letter, one number, and one special character
User learns requirements only after failing. Requirements should be visible before attempting.
We need error handling that treats mistakes as collaborative repair opportunities—helping users understand what went wrong and how to fix it.
Forces
User Agency vs System Control
- Users need freedom to enter data their way
- Systems need data in specific formats
- Too strict = frustration
- Too loose = bad data
Immediate vs Deferred Validation
- Real-time validation catches errors early
- But can feel intrusive or jumpy
- Deferred validation allows user to complete thought
- But leaves errors hidden until later
Specificity vs Simplicity
- Specific error messages help correction
- But can overwhelm with detail
- Simple messages are easier to read
- But may not explain what's wrong
Prevention vs Recovery
- Best to prevent errors (good design)
- But some errors are inevitable
- Make recovery easy and graceful
- But don't make it so easy users don't learn
Technical Accuracy vs User Understanding
- System knows technical reason for error
- Users need actionable guidance
- Balance accuracy with comprehensibility
- Sometimes "technically wrong" explanation is more helpful
Solution
Treat errors as communication breakdowns requiring collaborative repair. Prevent when possible, detect early when not, explain clearly, guide specifically, and make correction easy.
The pattern has six key elements:
1. Progressive Prevention
Stop errors before they happen:
Field-level constraints:
<!-- Bad: No guidance -->
<input type="text" name="phone" required>
<!-- Good: Built-in prevention -->
<input type="tel" name="phone"
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
placeholder="555-123-4567"
title="Phone format: 555-123-4567">
Real-time formatting:
// As user types phone number
onInput: (value) => {
// Format automatically: "5551234567" → "(555) 123-4567"
return formatPhoneNumber(value);
}
Proactive guidance:
Password Requirements:
✓ At least 8 characters (you have 6)
✗ One uppercase letter
✓ One lowercase letter
✗ One number
✓ One special character
[Continue when all requirements met]
2. Early Detection
Catch errors as they occur, not on submit:
On-blur validation:
emailField.addEventListener('blur', async (e) => {
const email = e.target.value;
if (!email) return; // Empty is okay until submit
if (!isValidEmailFormat(email)) {
showError(emailField,
"Email should look like: name@example.com"
);
} else if (await checkEmailAvailable(email)) {
showSuccess(emailField, "Email looks good!");
} else {
showWarning(emailField,
"This email is already registered. Did you mean to log in?"
);
}
});
Incremental validation:
Date of Birth: [MM] / [DD] / [YYYY]
[User types: 13]
Gentle warning: "Months are 01-12. Did you mean 12 or 03?"
[User types: 02/32]
Immediate: "February has 28-29 days. Day must be 01-28."
3. Constructive Messaging
Explain what's wrong and how to fix it:
Bad error:
ERROR: Invalid input
Better error:
That doesn't look like a valid phone number
Best error:
That doesn't look like a US phone number.
You entered: 555123456 (9 digits)
We need: 10 digits like 555-123-4567
Need to enter an international number? [Switch to international format]
Message structure: 1. What's wrong (in user terms) 2. What you entered (if helpful) 3. What we expected (with example) 4. How to fix it (actionable guidance) 5. Alternative paths (if appropriate)
4. Intelligent Suggestions
Offer specific corrections:
Near-miss detection:
Email: john.smith@gmial.com
^^^
Did you mean: gmail.com? [Use this]
Format inference:
Date: 3/15/24
That looks like: March 15, 2024
Is this correct? [Yes] [No, I meant: 15/3/24 (day/month/year)]
Common mistakes:
SSN: 123-45-678
That's only 9 digits (need 11 with dashes)
Did you mean: 123-45-6789? [Use this]
Or: 123-45-0678? [Use this]
5. Severity-Appropriate Response
Different errors need different treatments:
Blocking errors (prevent submission):
Credit Card: [ ]
❌ Credit card number is required to complete purchase
[Can't proceed without this information]
Warning errors (unusual but valid):
Age: 127
⚠️ That age seems unusual. Most people entering this form are 18-65.
Did you mean: 27? [Use this]
Or is 127 correct? [Yes, continue]
Informational notices (not errors):
Email: personal@gmail.com
ℹ️ Tip: Use your work email if you want updates sent to your office
[This is fine, but here's a suggestion]
6. Graceful Recovery
Make fixing errors easy:
Preserve user work:
// Never lose user input on validation failure
onError: (field, error) => {
// Keep what they typed
field.value = field.value; // Don't clear
// Focus on problem field
field.focus();
// Select text for easy correction
field.select();
}
Show location clearly:
[Scroll to error]
[Highlight problem field]
[Provide inline correction guidance]
[Offer autocorrect suggestions]
Allow multiple paths:
Can't remember your password?
• [Reset by email]
• [Reset by phone]
• [Answer security questions]
• [Contact support]
Implementation Details
HTML Structure with Error States
<div class="field-group" data-field-name="email">
<label for="email">
Email Address
<span class="required">*</span>
</label>
<!-- The input field -->
<input
type="email"
id="email"
name="email"
aria-describedby="email-help email-error"
aria-invalid="false"
required
>
<!-- Help text (always visible) -->
<div id="email-help" class="field-help">
We'll use this to send your confirmation
</div>
<!-- Error message (hidden until error occurs) -->
<div id="email-error" class="field-error" role="alert" aria-live="polite">
<!-- Populated with error message when validation fails -->
</div>
<!-- Success indicator (hidden until validation passes) -->
<div class="field-success" aria-live="polite">
✓ Email looks good
</div>
<!-- Suggestion (shown for near-misses) -->
<div class="field-suggestion">
<!-- Populated with suggestions when applicable -->
</div>
</div>
JavaScript for Collaborative Errors
class CollaborativeValidation {
constructor(form) {
this.form = form;
this.validators = new Map();
this.setupValidation();
}
setupValidation() {
// Validate on blur (after user leaves field)
this.form.querySelectorAll('input, select, textarea').forEach(field => {
field.addEventListener('blur', () => this.validateField(field));
// Also validate on input for certain fields (passwords, etc.)
if (field.type === 'password' || field.dataset.validateOnInput) {
field.addEventListener('input', debounce(() => {
this.validateField(field);
}, 500));
}
});
}
async validateField(field) {
const fieldGroup = field.closest('.field-group');
const value = field.value.trim();
// Clear previous errors
this.clearError(fieldGroup);
// Skip validation if empty and not required
if (!value && !field.required) {
return true;
}
// Check required
if (field.required && !value) {
this.showError(fieldGroup, {
type: 'required',
message: `${field.labels[0].textContent} is required`,
severity: 'error'
});
return false;
}
// Field-specific validation
const fieldName = field.name;
if (fieldName === 'email') {
return await this.validateEmail(field, fieldGroup);
} else if (fieldName === 'phone') {
return this.validatePhone(field, fieldGroup);
} else if (fieldName === 'date' || field.type === 'date') {
return this.validateDate(field, fieldGroup);
}
return true;
}
async validateEmail(field, fieldGroup) {
const email = field.value.trim();
// Format check
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
// Check for common typos
const suggestion = this.suggestEmailCorrection(email);
this.showError(fieldGroup, {
type: 'format',
message: "That doesn't look like a valid email address",
detail: `Email should look like: name@example.com`,
suggestion: suggestion,
severity: 'error'
});
return false;
}
// Domain check (common typos)
const domain = email.split('@')[1];
const commonDomains = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com'];
const suggestion = this.findClosestMatch(domain, commonDomains);
if (suggestion && suggestion.distance < 2) {
this.showSuggestion(fieldGroup, {
message: `Did you mean ${email.split('@')[0]}@${suggestion.match}?`,
action: () => {
field.value = email.split('@')[0] + '@' + suggestion.match;
this.validateField(field);
}
});
}
// Check availability (if applicable)
if (field.dataset.checkAvailability) {
const available = await this.checkEmailAvailable(email);
if (!available) {
this.showError(fieldGroup, {
type: 'duplicate',
message: 'This email is already registered',
detail: 'Did you mean to log in instead?',
actions: [
{ label: 'Log in', href: '/login' },
{ label: 'Reset password', href: '/reset' }
],
severity: 'warning'
});
return false;
}
}
// Success!
this.showSuccess(fieldGroup);
return true;
}
validatePhone(field, fieldGroup) {
const phone = field.value.replace(/\D/g, ''); // Remove non-digits
if (phone.length < 10) {
this.showError(fieldGroup, {
type: 'format',
message: 'Phone number is incomplete',
detail: `You entered: ${phone} (${phone.length} digits)
We need: 10 digits like 555-123-4567`,
severity: 'error'
});
return false;
}
if (phone.length > 10 && phone.length !== 11) {
this.showError(fieldGroup, {
type: 'format',
message: 'Phone number has too many digits',
detail: `You entered: ${phone.length} digits
US numbers: 10 digits (555-123-4567)
International: Use country code format`,
actions: [
{ label: 'Switch to international format',
action: () => this.switchToInternationalPhone(field) }
],
severity: 'error'
});
return false;
}
// Auto-format
const formatted = this.formatPhone(phone);
field.value = formatted;
this.showSuccess(fieldGroup);
return true;
}
validateDate(field, fieldGroup) {
const dateStr = field.value;
const date = new Date(dateStr);
if (isNaN(date.getTime())) {
this.showError(fieldGroup, {
type: 'format',
message: 'That date doesn\'t look valid',
detail: 'Use format: MM/DD/YYYY (like 03/15/2024)',
severity: 'error'
});
return false;
}
// Check reasonable ranges
const now = new Date();
const minDate = new Date('1900-01-01');
const maxDate = new Date();
maxDate.setFullYear(maxDate.getFullYear() + 1);
if (date < minDate) {
this.showError(fieldGroup, {
type: 'range',
message: 'That date seems too far in the past',
detail: `Did you mean: ${date.getFullYear() + 100}?`,
severity: 'warning'
});
return false;
}
if (date > maxDate) {
this.showError(fieldGroup, {
type: 'range',
message: 'That date is in the future',
detail: 'Did you mean to enter a past date?',
suggestion: now.toISOString().split('T')[0],
severity: 'warning'
});
return false;
}
this.showSuccess(fieldGroup);
return true;
}
showError(fieldGroup, errorData) {
const field = fieldGroup.querySelector('input, select, textarea');
const errorDiv = fieldGroup.querySelector('.field-error');
// Build error message
let errorHTML = `
<div class="error-message ${errorData.severity}">
<strong>${errorData.message}</strong>
`;
if (errorData.detail) {
errorHTML += `<p class="error-detail">${errorData.detail}</p>`;
}
if (errorData.suggestion) {
errorHTML += `
<button type="button" class="suggestion-button"
onclick="this.closest('.field-group').querySelector('input').value='${errorData.suggestion}'; this.dispatchEvent(new Event('change', {bubbles: true}))">
Use: ${errorData.suggestion}
</button>
`;
}
if (errorData.actions) {
errorHTML += '<div class="error-actions">';
errorData.actions.forEach(action => {
if (action.href) {
errorHTML += `<a href="${action.href}">${action.label}</a>`;
} else {
errorHTML += `<button type="button" onclick="(${action.action})()">${action.label}</button>`;
}
});
errorHTML += '</div>';
}
errorHTML += '</div>';
errorDiv.innerHTML = errorHTML;
errorDiv.style.display = 'block';
// Update ARIA
field.setAttribute('aria-invalid', 'true');
fieldGroup.classList.add('has-error');
fieldGroup.classList.remove('has-success');
}
showSuggestion(fieldGroup, suggestionData) {
const suggestionDiv = fieldGroup.querySelector('.field-suggestion');
suggestionDiv.innerHTML = `
<div class="suggestion-message">
${suggestionData.message}
<button type="button" class="suggestion-button"
onclick="(${suggestionData.action})()">
Yes, use this
</button>
<button type="button" class="suggestion-dismiss"
onclick="this.closest('.field-suggestion').style.display='none'">
No, keep what I typed
</button>
</div>
`;
suggestionDiv.style.display = 'block';
}
showSuccess(fieldGroup) {
const field = fieldGroup.querySelector('input, select, textarea');
const successDiv = fieldGroup.querySelector('.field-success');
successDiv.style.display = 'block';
field.setAttribute('aria-invalid', 'false');
fieldGroup.classList.add('has-success');
fieldGroup.classList.remove('has-error');
}
clearError(fieldGroup) {
const field = fieldGroup.querySelector('input, select, textarea');
const errorDiv = fieldGroup.querySelector('.field-error');
const suggestionDiv = fieldGroup.querySelector('.field-suggestion');
const successDiv = fieldGroup.querySelector('.field-success');
errorDiv.style.display = 'none';
suggestionDiv.style.display = 'none';
successDiv.style.display = 'none';
field.setAttribute('aria-invalid', 'false');
fieldGroup.classList.remove('has-error', 'has-success');
}
// Helper functions
suggestEmailCorrection(email) {
const parts = email.split('@');
if (parts.length !== 2) return null;
const domain = parts[1];
const commonDomains = [
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
'icloud.com', 'aol.com', 'live.com'
];
return this.findClosestMatch(domain, commonDomains);
}
findClosestMatch(target, candidates) {
let closest = null;
let minDistance = Infinity;
candidates.forEach(candidate => {
const distance = this.levenshteinDistance(target, candidate);
if (distance < minDistance) {
minDistance = distance;
closest = candidate;
}
});
return minDistance <= 2 ? { match: closest, distance: minDistance } : null;
}
levenshteinDistance(a, b) {
const matrix = [];
for (let i = 0; i <= b.length; i++) {
matrix[i] = [i];
}
for (let j = 0; j <= a.length; j++) {
matrix[0][j] = j;
}
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
if (b.charAt(i - 1) === a.charAt(j - 1)) {
matrix[i][j] = matrix[i - 1][j - 1];
} else {
matrix[i][j] = Math.min(
matrix[i - 1][j - 1] + 1,
matrix[i][j - 1] + 1,
matrix[i - 1][j] + 1
);
}
}
}
return matrix[b.length][a.length];
}
formatPhone(digits) {
if (digits.length === 10) {
return `(${digits.substr(0,3)}) ${digits.substr(3,3)}-${digits.substr(6,4)}`;
} else if (digits.length === 11 && digits[0] === '1') {
return `+1 (${digits.substr(1,3)}) ${digits.substr(4,3)}-${digits.substr(7,4)}`;
}
return digits;
}
async checkEmailAvailable(email) {
// Call backend to check if email is already registered
const response = await fetch('/api/check-email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const data = await response.json();
return data.available;
}
}
// Utility: Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
const forms = document.querySelectorAll('form[data-collaborative-validation]');
forms.forEach(form => new CollaborativeValidation(form));
});
CSS for Error States
/* Field group states */
.field-group {
margin-bottom: 1.5rem;
position: relative;
}
.field-group input,
.field-group select,
.field-group textarea {
width: 100%;
padding: 0.75rem;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
.field-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
/* Error state */
.field-group.has-error input,
.field-group.has-error select,
.field-group.has-error textarea {
border-color: #dc3545;
}
.field-error {
display: none;
margin-top: 0.5rem;
padding: 0.75rem;
background: #fff5f5;
border-left: 4px solid #dc3545;
border-radius: 4px;
color: #721c24;
}
.field-error strong {
display: block;
margin-bottom: 0.25rem;
font-size: 0.95rem;
}
.error-detail {
margin: 0.5rem 0;
font-size: 0.9rem;
line-height: 1.5;
white-space: pre-line;
}
/* Success state */
.field-group.has-success input,
.field-group.has-success select,
.field-group.has-success textarea {
border-color: #28a745;
}
.field-success {
display: none;
margin-top: 0.5rem;
color: #155724;
font-size: 0.9rem;
}
.field-group.has-success .field-success {
display: block;
}
/* Suggestion state */
.field-suggestion {
display: none;
margin-top: 0.5rem;
padding: 0.75rem;
background: #fff9e6;
border-left: 4px solid #ffc107;
border-radius: 4px;
}
.suggestion-message {
font-size: 0.9rem;
color: #856404;
}
.suggestion-button {
margin-top: 0.5rem;
margin-right: 0.5rem;
padding: 0.5rem 1rem;
background: #ffc107;
color: #000;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
transition: background 0.2s ease;
}
.suggestion-button:hover {
background: #ffca28;
}
.suggestion-dismiss {
padding: 0.5rem 1rem;
background: transparent;
color: #856404;
border: 1px solid #856404;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
}
/* Error severity levels */
.error-message.warning {
background: #fff9e6;
border-color: #ffc107;
color: #856404;
}
.error-message.info {
background: #e7f3ff;
border-color: #2196F3;
color: #0c5460;
}
/* Error actions */
.error-actions {
margin-top: 0.75rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.error-actions a,
.error-actions button {
padding: 0.5rem 1rem;
font-size: 0.85rem;
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: all 0.2s ease;
}
.error-actions a {
background: #667eea;
color: white;
border: none;
}
.error-actions button {
background: transparent;
color: #667eea;
border: 1px solid #667eea;
}
.error-actions a:hover {
background: #5568d3;
}
.error-actions button:hover {
background: #f0f4ff;
}
/* Help text */
.field-help {
margin-top: 0.25rem;
font-size: 0.85rem;
color: #666;
}
/* Required indicator */
.required {
color: #dc3545;
margin-left: 0.25rem;
}
/* Responsive */
@media (max-width: 768px) {
.field-group {
margin-bottom: 1.25rem;
}
.error-actions {
flex-direction: column;
}
.error-actions a,
.error-actions button {
width: 100%;
}
}
Further Reading
Academic Foundations
Error Handling Psychology: - Norman, D. A. (2013). The Design of Everyday Things (Revised ed.). Basic Books. - Chapter 5: "Human Error? No, Bad Design"—errors are design failures, not user failures - Reason, J. (1990). Human Error. Cambridge University Press. - Classification of error types: slips, lapses, mistakes - https://doi.org/10.1017/CBO9781139062367
Error Recovery: - Shneiderman, B., et al. (2016). Designing the User Interface (6th ed.). Pearson. - Eight Golden Rules including "Support internal locus of control" - Users should feel in control when fixing errors - Cooper, A., et al. (2014). About Face: The Essentials of Interaction Design (4th ed.). Wiley. - "Make Errors Impossible" and graceful error recovery patterns
Validation Research: - Wroblewski, L. (2008). Web Form Design: Filling in the Blanks. Rosenfeld Media. - Inline validation vs. on-submit validation research - User testing results on error message effectiveness - Jarrett, C., & Gaffney, G. (2009). Forms that Work. Morgan Kaufmann. - Chapter on error prevention and recovery
Implementation Guides
Frontend Validation Libraries: - Joi (Node.js): https://joi.dev/ - Schema validation with detailed error messages - Yup (JavaScript): https://github.com/jquense/yup - Schema builder for validation with React integration - Vuelidate (Vue.js): https://vuelidate.js.org/ - Simple, lightweight validation for Vue - Angular Forms Validation: https://angular.io/guide/form-validation - Built-in reactive form validation
Error Message Design: - Material Design: Error Messages - https://material.io/components/text-fields#error-messages - Visual patterns for inline error display - Apple Human Interface Guidelines: Alerts - https://developer.apple.com/design/human-interface-guidelines/alerts - Error messaging for iOS/macOS - Microsoft Fluent Design: Error Handling - https://www.microsoft.com/design/fluent/
Inline Validation: - HTML5 Constraint Validation API - https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation - Native browser validation support - Parsley.js: https://parsleyjs.org/ - Frontend form validation with live error display - jQuery Validation: https://jqueryvalidation.org/ - Mature validation library with extensive error handling
Related Trilogy Patterns
Volume 1 Foundations: - Chapter 2: "Theoretical Foundations" - Human error and system design - Chapter 8: "Architecture of Domain-Specific Systems" - Validation architecture - Chapter 9: "User Experience Design" - Error communication and recovery
Volume 2 Patterns: - Volume 2, Pattern 4: "Interaction Outcome Classification" - Understanding error patterns - Volume 2, Pattern 9: "Early Warning Signals" - Preventing errors before they occur - Pattern 26: "Feedback Loop Implementation" - Learning from user mistakes
Volume 3 Integration: - Volume 3, Pattern 2: "Contextual Scaffolding" - Providing help before errors occur - Volume 3, Pattern 6: "Domain-Aware Validation" - Specialized validation rules - Pattern 14: "Cross-Field Validation" - Detecting related field errors
Industry Examples
Best-in-Class Error Handling: - Stripe Forms: https://stripe.com/ - Real-time card validation with helpful error messages - "Your card number is incomplete" vs. "Invalid input" - Mailchimp Signup Forms: Suggest email domain corrections - "Did you mean @gmail.com instead of @gmial.com?" - GitHub: https://github.com/ - Detailed validation errors with links to documentation
Password Validation: - Dropbox: https://www.dropbox.com/ - Visual strength meter with specific requirements - Microsoft Account: https://account.microsoft.com/ - Real-time feedback on password requirements - 1Password: https://1password.com/ - Password generator with domain-specific rules
Form Validation: - Google Forms: https://www.google.com/forms/about/ - Response validation with custom error messages - Typeform: https://www.typeform.com/ - Logic jump validation with clear error indication - Wufoo: https://www.wufoo.com/ - Field rules with instant validation feedback
Standards and Compliance
Accessibility:
- WCAG 2.1 Success Criterion 3.3.1: Error Identification
- https://www.w3.org/WAI/WCAG21/Understanding/error-identification.html
- Errors must be identified in text
- WCAG 2.1 Success Criterion 3.3.3: Error Suggestion
- https://www.w3.org/WAI/WCAG21/Understanding/error-suggestion.html
- Provide suggestions for fixing input errors
- ARIA: aria-invalid and aria-describedby
- https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA21
- Linking error messages to form fields for screen readers
Security Validation: - OWASP Input Validation Cheat Sheet - https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html - Security-focused validation patterns - OWASP XSS Prevention Cheat Sheet - https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html - Sanitizing user input
Research and Tools
Error Message Testing: - Nielsen Norman Group: Error Message Guidelines - https://www.nngroup.com/articles/error-message-guidelines/ - Research-based recommendations - Baymard Institute: Form Validation UX - https://baymard.com/blog/inline-form-validation - 60+ examples of validation patterns
Spell Checking and Suggestions: - Levenshtein Distance Algorithm - https://en.wikipedia.org/wiki/Levenshtein_distance - Computing edit distance for "Did you mean" suggestions - SymSpell: https://github.com/wolfgarbe/SymSpell - Fast spell checker for input correction - Fuse.js: https://fusejs.io/ - Fuzzy-search for suggesting corrections
Email Validation: - Email Validator (Python): https://pypi.org/project/email-validator/ - RFC-compliant email validation - Mailcheck.js: https://github.com/mailcheck/mailcheck - Suggest corrections for misspelled email domains - Abstract Email Validation API: https://www.abstractapi.com/email-verification-validation-api - Verify email deliverability