Validators.email()
Quick Start (30 seconds)
const form = Forms.create(
{
email: '',
workEmail: ''
},
{
email: Forms.v.email('Please enter a valid email address'),
workEmail: Forms.v.email('Work email is invalid')
}
);
// Invalid email
form.setValue('email', 'notanemail');
form.validateField('email');
console.log(form.getError('email')); // "Please enter a valid email address"
// Valid email
form.setValue('email', 'user@example.com');
form.validateField('email');
console.log(form.getError('email')); // "" (no error)What just happened? Validators.email() created a validator that checks if the value is a valid email format!
What is Validators.email()?
Validators.email() is a built-in validator that checks if a value matches valid email format.
Simply put, it ensures the field contains a properly formatted email address with @ and domain.
Key characteristics:
- ✅ Validates email format (user@domain.com)
- ✅ Custom error message
- ✅ Uses standard email regex pattern
- ✅ Allows empty values (combine with
required()if needed) - ✅ Case-insensitive validation
- ✅ Handles common email formats
Syntax
// Create email validator with custom message
const validator = Forms.v.email('Invalid email address');
// Use in form
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Please enter a valid email') }
);
// With default message
const validator = Forms.v.email(); // "Please enter a valid email address"Parameters:
message(string, optional) - Custom error message. Default:'Please enter a valid email address'
Returns: function(value) => string - Validator function
Validation Pattern:
- Must contain
@symbol - Must have characters before and after
@ - Must have domain with
.(e.g.,example.com) - Common formats:
user@domain.com,user.name@sub.domain.co.uk
Why Does This Exist?
The Challenge: Manual Email Validation
const form = Forms.create(
{ email: '' },
{
// ❌ Manual email validation - error-prone
email: (value) => {
if (!value) return '';
// Easy to get regex wrong
const regex = /^[a-z0-9]+@[a-z]+\.[a-z]+$/;
if (!regex.test(value)) {
return 'Invalid email';
}
return '';
}
}
);
// This regex is too strict! Rejects valid emails like:
// - user.name@example.com (dots in name)
// - user+tag@example.com (plus signs)
// - user@sub.domain.com (subdomains)Problems: ❌ Complex regex - Hard to get right ❌ Inconsistent - Different validation in different places ❌ Incomplete - Misses valid email formats ❌ Too strict - Rejects legitimate emails
The Solution with Validators.email()
const form = Forms.create(
{ email: '' },
{
// ✅ Battle-tested email validation
email: Forms.v.email('Please enter a valid email')
}
);
// Handles all common formats correctly!Mental Model
Think of Validators.email() as an email format checker - it verifies the structure looks like a real email address.
Validation Flow
Value entered
↓
Check if empty → YES → Valid (allows empty)
↓
NO
↓
Has @ symbol? → NO → ❌ Invalid
↓
YES
↓
Has local part (before @)? → NO → ❌ Invalid
↓
YES
↓
Has domain (after @)? → NO → ❌ Invalid
↓
YES
↓
Domain has .? → NO → ❌ Invalid
↓
YES
↓
✅ Valid email formatHow Does It Work?
Internal Process
function email(message = 'Please enter a valid email address') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return function(value) {
// Allow empty (combine with required() if needed)
if (!value) return '';
// Test against regex
if (!emailRegex.test(value)) {
return message;
}
return '';
};
}Basic Usage
Example 1: Simple Email Validation
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Invalid email') }
);
// Invalid formats
form.setValue('email', 'notemail');
console.log(form.hasError('email')); // true
form.setValue('email', '@example.com');
console.log(form.hasError('email')); // true
form.setValue('email', 'user@');
console.log(form.hasError('email')); // true
// Valid format
form.setValue('email', 'user@example.com');
console.log(form.hasError('email')); // falseExample 2: Email with Required
const form = Forms.create(
{ email: '' },
{
email: Forms.v.combine(
Forms.v.required('Email is required'),
Forms.v.email('Invalid email format')
)
}
);
// Empty - required error
form.validate();
console.log(form.getError('email')); // "Email is required"
// Invalid format
form.setValue('email', 'notvalid');
form.validateField('email');
console.log(form.getError('email')); // "Invalid email format"
// Valid
form.setValue('email', 'user@example.com');
form.validateField('email');
console.log(form.hasError('email')); // falseExample 3: Multiple Email Fields
const form = Forms.create(
{
personalEmail: '',
workEmail: '',
recoveryEmail: ''
},
{
personalEmail: Forms.v.email('Invalid personal email'),
workEmail: Forms.v.email('Invalid work email'),
recoveryEmail: Forms.v.email('Invalid recovery email')
}
);Example 4: Email with Visual Feedback
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Invalid email') }
);
emailInput.addEventListener('input', form.handleChange);
emailInput.addEventListener('blur', form.handleBlur);
effect(() => {
if (form.shouldShowError('email')) {
emailInput.classList.add('error');
errorSpan.textContent = form.getError('email');
} else if (form.values.email) {
emailInput.classList.add('success');
errorSpan.textContent = '';
} else {
emailInput.classList.remove('error', 'success');
errorSpan.textContent = '';
}
});Example 5: Email Case Normalization
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Invalid email') }
);
emailInput.addEventListener('blur', (e) => {
// Normalize to lowercase
const normalized = e.target.value.toLowerCase();
form.setValue('email', normalized);
e.target.value = normalized;
});Advanced Patterns
Pattern 1: Domain Whitelist
function emailWithDomain(allowedDomains, message) {
return (value) => {
// First check basic email format
const emailError = Forms.v.email('Invalid email format')(value);
if (emailError) return emailError;
if (!value) return '';
// Check domain
const domain = value.split('@')[1]?.toLowerCase();
if (!allowedDomains.includes(domain)) {
return message || `Email must be from: ${allowedDomains.join(', ')}`;
}
return '';
};
}
const form = Forms.create(
{ workEmail: '' },
{
workEmail: emailWithDomain(
['company.com', 'company.co.uk'],
'Please use your company email'
)
}
);Pattern 2: Email Existence Check
const form = Forms.create(
{ email: '' },
{
email: async (value) => {
// Basic format check
const formatError = Forms.v.email('Invalid email')(value);
if (formatError) return formatError;
if (!value) return '';
// Check if email exists (debounced)
const response = await fetch(`/api/check-email?email=${value}`);
const { exists } = await response.json();
if (exists) {
return 'This email is already registered';
}
return '';
}
}
);Pattern 3: Email with Auto-Complete Suggestions
const commonDomains = ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com'];
emailInput.addEventListener('input', (e) => {
form.handleChange(e);
const value = e.target.value;
const atIndex = value.indexOf('@');
if (atIndex > 0) {
const domain = value.substring(atIndex + 1);
if (domain && domain.length >= 1) {
const suggestions = commonDomains
.filter(d => d.startsWith(domain.toLowerCase()))
.map(d => value.substring(0, atIndex + 1) + d);
showSuggestions(suggestions);
}
}
});Pattern 4: Email with Typo Detection
function suggestEmailCorrection(email) {
const typos = {
'gmial.com': 'gmail.com',
'gmai.com': 'gmail.com',
'yahooo.com': 'yahoo.com',
'outlok.com': 'outlook.com'
};
const [local, domain] = email.split('@');
if (typos[domain]) {
return `${local}@${typos[domain]}`;
}
return null;
}
emailInput.addEventListener('blur', (e) => {
const value = form.values.email;
const suggestion = suggestEmailCorrection(value);
if (suggestion) {
const shouldFix = confirm(`Did you mean: ${suggestion}?`);
if (shouldFix) {
form.setValue('email', suggestion);
e.target.value = suggestion;
}
}
});Pattern 5: Disposable Email Blocker
const disposableDomains = [
'10minutemail.com',
'guerrillamail.com',
'mailinator.com',
'tempmail.com'
];
function blockDisposableEmail(message) {
return (value) => {
const emailError = Forms.v.email('Invalid email')(value);
if (emailError) return emailError;
if (!value) return '';
const domain = value.split('@')[1]?.toLowerCase();
if (disposableDomains.includes(domain)) {
return message || 'Disposable email addresses are not allowed';
}
return '';
};
}
const form = Forms.create(
{ email: '' },
{ email: blockDisposableEmail('Please use a permanent email address') }
);Pattern 6: Email Confirmation Match
const form = Forms.create(
{
email: '',
confirmEmail: ''
},
{
email: Forms.v.email('Invalid email'),
confirmEmail: (value, allValues) => {
const emailError = Forms.v.email('Invalid email')(value);
if (emailError) return emailError;
if (value !== allValues.email) {
return 'Emails must match';
}
return '';
}
}
);Pattern 7: Email with Real-Time Format Hints
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Invalid email') }
);
emailInput.addEventListener('input', (e) => {
const value = e.target.value;
if (!value) {
hintDiv.textContent = 'Example: user@example.com';
} else if (!value.includes('@')) {
hintDiv.textContent = 'Email must contain @';
} else if (!value.split('@')[1]?.includes('.')) {
hintDiv.textContent = 'Email must include domain (e.g., .com)';
} else {
hintDiv.textContent = '';
}
});Pattern 8: Email Validation with Loading State
const form = Forms.create(
{ email: '' },
{
email: async (value) => {
const formatError = Forms.v.email('Invalid email')(value);
if (formatError) return formatError;
if (!value) return '';
emailLoadingIndicator.style.display = 'block';
try {
const response = await fetch(`/api/validate-email?email=${value}`);
const { valid, message } = await response.json();
return valid ? '' : message;
} finally {
emailLoadingIndicator.style.display = 'none';
}
}
}
);Pattern 9: Internationalized Email Support
function internationalEmail(message) {
// More permissive regex for international emails
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return (value) => {
if (!value) return '';
if (!regex.test(value)) {
return message || 'Invalid email format';
}
// Additional checks for international characters
const [local, domain] = value.split('@');
if (local.length > 64 || domain.length > 255) {
return 'Email address is too long';
}
return '';
};
}Pattern 10: Email Normalization Pipeline
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Invalid email') }
);
function normalizeEmail(email) {
let normalized = email.toLowerCase().trim();
// Remove dots from Gmail addresses (they're ignored)
const [local, domain] = normalized.split('@');
if (domain === 'gmail.com') {
const cleanLocal = local.split('+')[0].replace(/\./g, '');
normalized = `${cleanLocal}@${domain}`;
}
return normalized;
}
emailInput.addEventListener('blur', (e) => {
const normalized = normalizeEmail(e.target.value);
form.setValue('email', normalized);
e.target.value = normalized;
});Common Pitfalls
Pitfall 1: Not Combining with Required
// ❌ Email validator allows empty values
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Invalid') }
);
form.validate();
console.log(form.isValid); // true (empty is valid!)
// ✅ Combine with required
const form = Forms.create(
{ email: '' },
{
email: Forms.v.combine(
Forms.v.required('Email required'),
Forms.v.email('Invalid email')
)
}
);Pitfall 2: Over-Strict Validation
// ❌ Custom regex that's too strict
const strictEmail = (value) => {
if (!/^[a-z]+@[a-z]+\.com$/.test(value)) {
return 'Invalid';
}
return '';
};
// Rejects valid emails:
// - user.name@example.com
// - user@example.co.uk
// - User@Example.COM (uppercase)
// ✅ Use the built-in validator
Forms.v.email('Invalid email')Pitfall 3: Not Normalizing Case
// ❌ Case-sensitive storage
form.setValue('email', 'User@Example.COM');
// Stored as-is, may cause duplicate account issues
// ✅ Normalize to lowercase
emailInput.addEventListener('blur', (e) => {
const normalized = e.target.value.toLowerCase();
form.setValue('email', normalized);
});Pitfall 4: Validating on Every Keystroke
// ❌ Shows error while user is still typing
emailInput.addEventListener('input', (e) => {
form.handleChange(e);
form.validateField('email'); // Annoying!
});
// ✅ Validate only on blur
emailInput.addEventListener('input', form.handleChange);
emailInput.addEventListener('blur', (e) => {
form.handleBlur(e);
// Validation happens automatically
});Pitfall 5: Trusting Client-Side Validation Only
// ❌ Only client-side validation
const form = Forms.create(
{ email: '' },
{ email: Forms.v.email('Invalid') }
);
// User can bypass this!
// ✅ Always validate server-side too
async function register(values) {
const response = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(values)
});
if (!response.ok) {
const { errors } = await response.json();
form.setErrors(errors); // Server validation errors
}
}Summary
Key Takeaways
- Validates email format - checks for
user@domain.compattern - Allows empty values - combine with
required()if field is mandatory - Uses standard regex - handles common email formats
- Custom error messages - provide user-friendly feedback
- Case-insensitive - validates regardless of letter case
When to Use Validators.email()
✅ Use for:
- Email input fields
- Contact forms
- Registration/login forms
- Newsletter signups
❌ Don't use when:
- Field accepts non-email text
- Need custom email format rules
- Validating phone numbers or other formats
Valid Email Examples
| Valid? | |
|---|---|
user@example.com | ✅ Yes |
user.name@example.com | ✅ Yes |
user+tag@example.com | ✅ Yes |
user@sub.domain.com | ✅ Yes |
USER@EXAMPLE.COM | ✅ Yes |
notanemail | ❌ No |
@example.com | ❌ No |
user@ | ❌ No |
user@domain | ❌ No (no TLD) |
Related Validators
Validators.required()- Ensure email is providedValidators.pattern()- Custom email regexValidators.combine()- Combine multiple validatorsValidators.custom()- Advanced email validation
One-Line Rule
Validators.email(message)creates a validator that checks if a value matches standard email format (user@domain.com), allowing empty values unless combined withrequired().
What's Next?
- Combine with
Validators.required()for mandatory emails - Add server-side email existence validation
- Implement email normalization on submit