Validators.pattern()
Quick Start (30 seconds)
const form = Forms.create(
{
phone: '',
zipCode: '',
username: ''
},
{
phone: Forms.v.pattern(/^\d{3}-\d{3}-\d{4}$/, 'Phone must be XXX-XXX-XXXX'),
zipCode: Forms.v.pattern(/^\d{5}$/, 'Zip code must be 5 digits'),
username: Forms.v.pattern(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores')
}
);
// Invalid pattern
form.setValue('phone', '1234567890');
form.validateField('phone');
console.log(form.getError('phone')); // "Phone must be XXX-XXX-XXXX"
// Valid pattern
form.setValue('phone', '123-456-7890');
form.validateField('phone');
console.log(form.hasError('phone')); // falseWhat just happened? Validators.pattern() created a validator that checks if the value matches a custom regular expression!
What is Validators.pattern()?
Validators.pattern() is a built-in validator that validates values against custom regex patterns.
Simply put, it lets you enforce any custom format using regular expressions - phone numbers, zip codes, usernames, etc.
Key characteristics:
- ✅ Validates against regex pattern
- ✅ Custom error message
- ✅ Flexible format enforcement
- ✅ Allows empty values
- ✅ Works with any string pattern
Syntax
// Create pattern validator
const validator = Forms.v.pattern(/^\d{5}$/, 'Must be 5 digits');
// Use in form
const form = Forms.create(
{ zipCode: '' },
{ zipCode: Forms.v.pattern(/^\d{5}$/, 'Invalid zip code') }
);
// With flags (case-insensitive)
const validator = Forms.v.pattern(/^[A-Z]+$/i, 'Letters only');Parameters:
regex(RegExp) - Regular expression pattern to matchmessage(string, optional) - Custom error message
Returns: function(value) => string - Validator function
Why Does This Exist?
The Challenge
// ❌ Manual regex validation - repetitive
const form = Forms.create(
{
phone: '',
ssn: '',
creditCard: ''
},
{
phone: (value) => {
if (value && !/^\d{3}-\d{3}-\d{4}$/.test(value)) {
return 'Invalid phone';
}
return '';
},
ssn: (value) => {
if (value && !/^\d{3}-\d{2}-\d{4}$/.test(value)) {
return 'Invalid SSN';
}
return '';
},
creditCard: (value) => {
if (value && !/^\d{4}-\d{4}-\d{4}-\d{4}$/.test(value)) {
return 'Invalid card';
}
return '';
}
}
);The Solution
// ✅ Clean pattern validation
const form = Forms.create(
{
phone: '',
ssn: '',
creditCard: ''
},
{
phone: Forms.v.pattern(/^\d{3}-\d{3}-\d{4}$/, 'Invalid phone'),
ssn: Forms.v.pattern(/^\d{3}-\d{2}-\d{4}$/, 'Invalid SSN'),
creditCard: Forms.v.pattern(/^\d{4}-\d{4}-\d{4}-\d{4}$/, 'Invalid card')
}
);How Does It Work?
function pattern(regex, message = 'Invalid format') {
return function(value) {
if (!value) return ''; // Allow empty
if (!regex.test(value)) {
return message;
}
return '';
};
}Basic Usage
Example 1: Phone Number Validation
const form = Forms.create(
{ phone: '' },
{
phone: Forms.v.pattern(
/^\d{3}-\d{3}-\d{4}$/,
'Phone must be in format XXX-XXX-XXXX'
)
}
);
// Valid: "123-456-7890"
// Invalid: "1234567890", "(123) 456-7890"Example 2: Username Rules
const form = Forms.create(
{ username: '' },
{
username: Forms.v.pattern(
/^[a-zA-Z0-9_]{3,15}$/,
'Username: 3-15 characters, letters/numbers/underscore only'
)
}
);
// Valid: "john_doe", "user123", "JohnDoe"
// Invalid: "ab", "user-name", "verylongusername123"Example 3: Zip Code
const form = Forms.create(
{ zipCode: '' },
{ zipCode: Forms.v.pattern(/^\d{5}(-\d{4})?$/, 'Invalid zip code') }
);
// Valid: "12345", "12345-6789"
// Invalid: "1234", "abcde"Example 4: Hexadecimal Color
const form = Forms.create(
{ color: '' },
{
color: Forms.v.pattern(
/^#[0-9A-F]{6}$/i,
'Must be valid hex color (#RRGGBB)'
)
}
);
// Valid: "#FF0000", "#00ff00", "#ABCDEF"
// Invalid: "#FFF", "red", "#GGGGGG"Example 5: Alphanumeric Only
const form = Forms.create(
{ code: '' },
{ code: Forms.v.pattern(/^[A-Z0-9]+$/i, 'Letters and numbers only') }
);
// Valid: "ABC123", "test", "123"
// Invalid: "test-123", "hello world", "test@123"Advanced Patterns
Pattern 1: Credit Card Validation
const cardPatterns = {
visa: /^4[0-9]{12}(?:[0-9]{3})?$/,
mastercard: /^5[1-5][0-9]{14}$/,
amex: /^3[47][0-9]{13}$/,
discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/
};
function creditCardValidator(message) {
return (value) => {
if (!value) return '';
const cleaned = value.replace(/[\s-]/g, '');
const isValid = Object.values(cardPatterns).some(pattern =>
pattern.test(cleaned)
);
if (!isValid) {
return message || 'Invalid credit card number';
}
return '';
};
}
const form = Forms.create(
{ cardNumber: '' },
{ cardNumber: creditCardValidator('Invalid card number') }
);Pattern 2: Password Strength Pattern
// Must contain: uppercase, lowercase, number, special char, min 8 chars
const strongPasswordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
const form = Forms.create(
{ password: '' },
{
password: Forms.v.pattern(
strongPasswordPattern,
'Password must contain uppercase, lowercase, number, and special character'
)
}
);Pattern 3: URL Validation
const urlPattern = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
const form = Forms.create(
{ website: '' },
{
website: Forms.v.pattern(
urlPattern,
'Must be a valid URL (e.g., https://example.com)'
)
}
);Pattern 4: IP Address Validation
const ipv4Pattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const form = Forms.create(
{ ipAddress: '' },
{
ipAddress: Forms.v.pattern(
ipv4Pattern,
'Must be a valid IPv4 address'
)
}
);
// Valid: "192.168.1.1", "10.0.0.1"
// Invalid: "256.1.1.1", "192.168.1"Pattern 5: Date Format Validation
// YYYY-MM-DD format
const datePattern = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])$/;
const form = Forms.create(
{ birthDate: '' },
{
birthDate: Forms.v.pattern(
datePattern,
'Date must be in YYYY-MM-DD format'
)
}
);
// Valid: "2024-01-15", "1990-12-31"
// Invalid: "2024-13-01", "24-01-15"Pattern 6: Slug Validation
const slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const form = Forms.create(
{ slug: '' },
{
slug: Forms.v.pattern(
slugPattern,
'Slug must be lowercase letters, numbers, and hyphens only'
)
}
);
// Valid: "my-blog-post", "product-123"
// Invalid: "My Blog Post", "post_name", "-invalid-"Pattern 7: License Plate Validation
const licensePlatePatterns = {
us: /^[A-Z]{3}-\d{4}$/,
uk: /^[A-Z]{2}\d{2}\s?[A-Z]{3}$/,
eu: /^[A-Z]{1,2}-[A-Z]{1,2}-\d{1,4}$/
};
function licensePlateValidator(country = 'us', message) {
const pattern = licensePlatePatterns[country];
return Forms.v.pattern(
pattern,
message || `Invalid ${country.toUpperCase()} license plate format`
);
}
const form = Forms.create(
{ plate: '' },
{ plate: licensePlateValidator('us') }
);Pattern 8: Real-Time Pattern Formatting
const form = Forms.create(
{ phone: '' },
{ phone: Forms.v.pattern(/^\d{3}-\d{3}-\d{4}$/, 'Invalid phone') }
);
phoneInput.addEventListener('input', (e) => {
let value = e.target.value.replace(/\D/g, '');
// Auto-format as user types
if (value.length >= 6) {
value = `${value.slice(0, 3)}-${value.slice(3, 6)}-${value.slice(6, 10)}`;
} else if (value.length >= 3) {
value = `${value.slice(0, 3)}-${value.slice(3)}`;
}
e.target.value = value;
form.setValue('phone', value);
});Pattern 9: Multiple Pattern Options
function multiPattern(patterns, message) {
return (value) => {
if (!value) return '';
const isValid = patterns.some(pattern => pattern.test(value));
if (!isValid) {
return message || 'Invalid format';
}
return '';
};
}
const form = Forms.create(
{ identifier: '' },
{
identifier: multiPattern(
[
/^\d{3}-\d{2}-\d{4}$/, // SSN
/^[A-Z]{2}\d{6}[A-Z]$/, // Passport
/^\d{9}$/ // Tax ID
],
'Must be valid SSN, Passport, or Tax ID'
)
}
);Pattern 10: Case-Insensitive with Normalization
const form = Forms.create(
{ hex: '' },
{
hex: Forms.v.pattern(
/^[0-9A-F]+$/i,
'Must be hexadecimal characters only'
)
}
);
hexInput.addEventListener('blur', (e) => {
// Normalize to uppercase
const normalized = e.target.value.toUpperCase();
form.setValue('hex', normalized);
e.target.value = normalized;
});Common Pitfalls
Pitfall 1: Forgetting Anchors (^ and $)
// ❌ Without anchors - matches substring
Forms.v.pattern(/\d{5}/, 'Invalid')
// "abc12345xyz" passes! (contains 5 digits)
// ✅ With anchors - matches entire string
Forms.v.pattern(/^\d{5}$/, 'Invalid')
// Only "12345" passesPitfall 2: Not Escaping Special Characters
// ❌ Unescaped dot matches any character
Forms.v.pattern(/^\d{3}.\d{3}.\d{4}$/, 'Invalid')
// Matches "123-456-7890" and "123a456b7890"
// ✅ Escaped dot matches literal dot
Forms.v.pattern(/^\d{3}\.\d{3}\.\d{4}$/, 'Invalid')
// Only matches "123.456.7890"Pitfall 3: Too Strict Patterns
// ❌ Too strict - rejects valid inputs
Forms.v.pattern(/^[a-z]+$/, 'Lowercase only')
// Rejects "John", "test123", "hello-world"
// ✅ More flexible
Forms.v.pattern(/^[a-zA-Z0-9_-]+$/, 'Letters, numbers, dash, underscore')Pitfall 4: Not Showing Expected Format
// ❌ Vague error message
Forms.v.pattern(/^\d{3}-\d{3}-\d{4}$/, 'Invalid format')
// ✅ Clear expected format
Forms.v.pattern(/^\d{3}-\d{3}-\d{4}$/, 'Phone must be XXX-XXX-XXXX')Pitfall 5: Client-Side Only Validation
// ❌ Only validating on client
Forms.v.pattern(/^[A-Z0-9]+$/, 'Alphanumeric only')
// User can bypass in browser console
// ✅ Also validate on server
// Backend must have same regex validationSummary
Key Takeaways
- Validates against regex patterns - custom format enforcement
- Flexible and powerful - supports any string pattern
- Allows empty values - combine with
required()if needed - Use anchors -
^and$for exact matches - Clear error messages - show expected format
When to Use pattern()
✅ Use for:
- Phone numbers
- Zip/postal codes
- Usernames with specific rules
- License plates
- Credit cards
- Custom format codes
❌ Don't use when:
- Email validation (use
email()) - Simple length checks (use
minLength()/maxLength()) - Number ranges (use
min()/max())
Common Patterns
| Format | Pattern | Example | | --| | | | US Phone | /^\d{3}-\d{3}-\d{4}$/ | 123-456-7890 | | US Zip | /^\d{5}(-\d{4})?$/ | 12345 or 12345-6789 | | Hex Color | /^#[0-9A-F]{6}$/i | #FF0000 | | Username | /^[a-zA-Z0-9_]{3,15}$/ | john_doe123 | | SSN | /^\d{3}-\d{2}-\d{4}$/ | 123-45-6789 | | Date (ISO) | /^\d{4}-\d{2}-\d{2}$/ | 2024-01-15 | | Slug | /^[a-z0-9-]+$/ | my-blog-post |
Related Validators
Validators.email()- Built-in email patternValidators.custom()- Complex validation logicValidators.combine()- Multiple validationsValidators.required()- Ensure value exists
One-Line Rule
Validators.pattern(regex, message)creates a validator that checks if a value matches a custom regular expression pattern, enabling flexible format enforcement for phone numbers, codes, and custom string formats.
What's Next?
- Learn regex patterns for common formats
- Combine with auto-formatting on input
- Add visual format hints to users