Validators.min()
Quick Start (30 seconds)
const form = Forms.create(
{
age: 0,
quantity: 0,
price: 0
},
{
age: Forms.v.min(18, 'You must be at least 18 years old'),
quantity: Forms.v.min(1, 'Quantity must be at least 1'),
price: Forms.v.min(0.01, 'Price must be at least $0.01')
}
);
// Too low
form.setValue('age', 16);
form.validateField('age');
console.log(form.getError('age')); // "You must be at least 18 years old"
// Valid
form.setValue('age', 18);
form.validateField('age');
console.log(form.hasError('age')); // falseWhat just happened? Validators.min() ensured the numeric value is at least the minimum required!
What is Validators.min()?
Validators.min() is a built-in validator that enforces minimum numeric values.
Simply put, it ensures numbers meet a minimum threshold, returning an error when too low.
Key characteristics:
- ✅ Validates minimum numeric value
- ✅ Works with numbers, not strings
- ✅ Custom error message
- ✅ Allows empty values
- ✅ Commonly used for age, quantity, price
Syntax
// Create min validator
const validator = Forms.v.min(18, 'Must be at least 18');
// Use in form
const form = Forms.create(
{ age: 0 },
{ age: Forms.v.min(18, 'You must be 18 or older') }
);
// Default message
const validator = Forms.v.min(0); // "Must be at least 0"Parameters:
min(number) - Minimum allowed value (inclusive)message(string, optional) - Custom error message
Returns: function(value) => string - Validator function
Why Does This Exist?
The Challenge
// ❌ Manual minimum validation - repetitive
const form = Forms.create(
{
age: 0,
quantity: 0,
rating: 0
},
{
age: (value) => {
if (value !== '' && value < 18) {
return 'Must be at least 18';
}
return '';
},
quantity: (value) => {
if (value !== '' && value < 1) {
return 'Must be at least 1';
}
return '';
},
rating: (value) => {
if (value !== '' && value < 1) {
return 'Must be at least 1';
}
return '';
}
}
);The Solution
// ✅ Clean and consistent
const form = Forms.create(
{
age: 0,
quantity: 0,
rating: 0
},
{
age: Forms.v.min(18, 'Must be at least 18'),
quantity: Forms.v.min(1, 'Must be at least 1'),
rating: Forms.v.min(1, 'Must be at least 1')
}
);How Does It Work?
function min(minValue, message) {
const defaultMessage = `Must be at least ${minValue}`;
return function(value) {
if (value === '' || value === null || value === undefined) {
return ''; // Allow empty
}
const numValue = Number(value);
if (numValue < minValue) {
return message || defaultMessage;
}
return '';
};
}Basic Usage
Example 1: Age Validation
const form = Forms.create(
{ age: 0 },
{ age: Forms.v.min(18, 'You must be 18 or older') }
);
form.setValue('age', 16);
console.log(form.hasError('age')); // true
form.setValue('age', 18);
console.log(form.hasError('age')); // falseExample 2: Quantity Validation
const form = Forms.create(
{ quantity: 0 },
{ quantity: Forms.v.min(1, 'Quantity must be at least 1') }
);
effect(() => {
const qty = form.values.quantity;
totalPrice.textContent = `$${(qty * unitPrice).toFixed(2)}`;
});Example 3: Price Validation
const form = Forms.create(
{ price: 0 },
{ price: Forms.v.min(0.01, 'Price must be at least $0.01') }
);Example 4: Rating System
const form = Forms.create(
{ rating: 0 },
{ rating: Forms.v.min(1, 'Rating must be at least 1 star') }
);
// Star rating UI
stars.forEach((star, index) => {
star.addEventListener('click', () => {
form.setValue('rating', index + 1);
});
});Example 5: Percentage Validation
const form = Forms.create(
{ discount: 0 },
{
discount: Forms.v.combine(
Forms.v.min(0, 'Discount cannot be negative'),
Forms.v.max(100, 'Discount cannot exceed 100%')
)
}
);Advanced Patterns
Pattern 1: Dynamic Minimum Based on Other Field
const form = Forms.create(
{
minPrice: 0,
maxPrice: 0
},
{
minPrice: Forms.v.min(0, 'Minimum price must be positive'),
maxPrice: (value, allValues) => {
if (value === '' || value === null) return '';
const minError = Forms.v.min(0, 'Maximum price must be positive')(value);
if (minError) return minError;
if (Number(value) < Number(allValues.minPrice)) {
return 'Maximum price must be greater than minimum price';
}
return '';
}
}
);Pattern 2: Age Gate with Visual Feedback
const form = Forms.create(
{ age: 0 },
{ age: Forms.v.min(18, 'You must be 18 or older to proceed') }
);
effect(() => {
const age = form.values.age;
if (age >= 18) {
ageGate.classList.add('valid');
continueButton.disabled = false;
} else if (age > 0) {
ageGate.classList.add('invalid');
continueButton.disabled = true;
} else {
ageGate.classList.remove('valid', 'invalid');
continueButton.disabled = true;
}
});Pattern 3: Stock Quantity Validation
const availableStock = 50;
const form = Forms.create(
{ quantity: 0 },
{
quantity: (value) => {
if (!value) return '';
const minError = Forms.v.min(1, 'Quantity must be at least 1')(value);
if (minError) return minError;
if (Number(value) > availableStock) {
return `Only ${availableStock} items available`;
}
return '';
}
}
);
effect(() => {
const qty = form.values.quantity;
stockIndicator.textContent = `${availableStock - qty} remaining`;
});Pattern 4: Sliding Scale Validator
const form = Forms.create(
{ score: 0 },
{ score: Forms.v.min(0, 'Score cannot be negative') }
);
sliderInput.addEventListener('input', (e) => {
form.setValue('score', Number(e.target.value));
const percentage = (e.target.value / e.target.max) * 100;
sliderTrack.style.background = `linear-gradient(to right,
#4CAF50 0%,
#4CAF50 ${percentage}%,
#ddd ${percentage}%,
#ddd 100%
)`;
});Pattern 5: Minimum with Currency Formatting
const form = Forms.create(
{ donation: 0 },
{ donation: Forms.v.min(5, 'Minimum donation is $5') }
);
donationInput.addEventListener('blur', (e) => {
const value = Number(e.target.value);
if (value) {
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(value);
formattedDisplay.textContent = formatted;
}
});Pattern 6: Progressive Discount Based on Minimum
const form = Forms.create(
{ orderAmount: 0 },
{ orderAmount: Forms.v.min(0, 'Amount cannot be negative') }
);
effect(() => {
const amount = form.values.orderAmount;
let discount = 0;
if (amount >= 100) {
discount = 20;
} else if (amount >= 50) {
discount = 10;
} else if (amount >= 25) {
discount = 5;
}
discountBadge.textContent = discount > 0 ? `${discount}% OFF` : 'No discount';
const total = amount - (amount * discount / 100);
totalDisplay.textContent = `$${total.toFixed(2)}`;
});Pattern 7: Bidding Minimum Increment
const currentBid = 100;
const minIncrement = 5;
const form = Forms.create(
{ bid: 0 },
{
bid: (value) => {
if (!value) return '';
const minBid = currentBid + minIncrement;
const minError = Forms.v.min(minBid, `Bid must be at least $${minBid}`)(value);
return minError;
}
}
);
effect(() => {
const bid = form.values.bid;
if (bid >= currentBid + minIncrement) {
bidButton.disabled = false;
bidButton.textContent = `Place Bid: $${bid}`;
} else {
bidButton.disabled = true;
bidButton.textContent = `Minimum Bid: $${currentBid + minIncrement}`;
}
});Pattern 8: Temperature Range Validator
const form = Forms.create(
{
temperature: 0,
unit: 'celsius'
},
{
temperature: (value, allValues) => {
if (!value && value !== 0) return '';
const limits = {
celsius: { min: -273.15, max: 1000 },
fahrenheit: { min: -459.67, max: 1832 },
kelvin: { min: 0, max: 1273.15 }
};
const { min, max } = limits[allValues.unit];
if (Number(value) < min) {
return `Temperature cannot be below ${min}°${allValues.unit[0].toUpperCase()}`;
}
if (Number(value) > max) {
return `Temperature cannot exceed ${max}°${allValues.unit[0].toUpperCase()}`;
}
return '';
}
}
);Pattern 9: Real-Time Minimum Indicator
const form = Forms.create(
{ amount: 0 },
{ amount: Forms.v.min(10, 'Minimum amount is $10') }
);
effect(() => {
const amount = form.values.amount;
const min = 10;
const percentage = Math.min(100, (amount / min) * 100);
progressBar.style.width = `${percentage}%`;
if (amount >= min) {
progressBar.classList.add('reached');
statusText.textContent = 'Minimum reached ✓';
} else if (amount > 0) {
progressBar.classList.remove('reached');
const needed = min - amount;
statusText.textContent = `$${needed.toFixed(2)} more needed`;
} else {
progressBar.classList.remove('reached');
statusText.textContent = `Minimum: $${min}`;
}
});Pattern 10: Multi-Tier Validation
function tieredMinValidator(tiers, fieldName) {
return (value) => {
if (!value && value !== 0) return '';
const numValue = Number(value);
for (const tier of tiers) {
if (numValue < tier.min) {
return tier.message || `${fieldName} must be at least ${tier.min}`;
}
if (tier.max && numValue <= tier.max) {
return tier.warning || '';
}
}
return '';
};
}
const form = Forms.create(
{ investment: 0 },
{
investment: tieredMinValidator([
{ min: 0, message: 'Investment cannot be negative' },
{ min: 100, max: 999, warning: 'Consider investing more for better returns' },
{ min: 1000 }
], 'Investment')
}
);Common Pitfalls
Pitfall 1: Validating String Length Instead of Number
// ❌ Wrong validator - min() is for numbers
const form = Forms.create(
{ username: '' },
{ username: Forms.v.min(3, 'Too short') }
);
// This won't work correctly!
// ✅ Use minLength for strings
const form = Forms.create(
{ username: '' },
{ username: Forms.v.minLength(3, 'Too short') }
);Pitfall 2: Not Handling Empty Values
// ❌ min() allows empty by default
const form = Forms.create(
{ age: '' },
{ age: Forms.v.min(18) }
);
form.validate();
console.log(form.isValid); // true! Empty is allowed
// ✅ Combine with required
const form = Forms.create(
{ age: '' },
{
age: Forms.v.combine(
Forms.v.required('Age is required'),
Forms.v.min(18, 'Must be 18+')
)
}
);Pitfall 3: Inclusive vs Exclusive Minimum
// ❌ Confusing message
Forms.v.min(18, 'Must be over 18')
// User enters 18 - is that valid? YES (min is inclusive)
// ✅ Clear message
Forms.v.min(18, 'Must be 18 or older')
// or
Forms.v.min(19, 'Must be over 18')Pitfall 4: Comparing Strings as Numbers
// ❌ Form stores strings
const form = Forms.create({ age: '16' });
form.setValue('age', '5'); // String '5'
// String comparison: '5' > '16' is false, but Number('5') < 16
// ✅ Convert to number
ageInput.addEventListener('input', (e) => {
form.setValue('age', Number(e.target.value));
});Pitfall 5: Wrong Error Message Context
// ❌ Generic message
Forms.v.min(1, 'Too low')
// ✅ Contextual message
Forms.v.min(1, 'Quantity must be at least 1')
Forms.v.min(18, 'You must be 18 or older')
Forms.v.min(0.01, 'Price must be at least $0.01')Summary
Key Takeaways
- Validates minimum numeric values - ensures numbers meet threshold
- Inclusive minimum - value can equal the minimum
- Allows empty values - combine with
required()if needed - For numbers only - use
minLength()for strings - Common for business rules - age, quantity, price constraints
When to Use min()
✅ Use for:
- Age verification
- Quantity minimums
- Price floors
- Rating systems
- Numeric thresholds
❌ Don't use when:
- Validating string length (use
minLength()) - Need exclusive minimum (value > min, not >=)
- Non-numeric values
Common Use Cases
| Field | Minimum | Reason | | -| | --| | Age | 18 | Legal requirement | | Quantity | 1 | Must order at least 1 | | Price | 0.01 | Cannot be free | | Rating | 1 | Minimum 1 star | | Discount | 0 | Cannot be negative | | Stock | 0 | Cannot have negative inventory |
Related Validators
Validators.max()- Maximum numeric valueValidators.minLength()- Minimum string lengthValidators.required()- Ensure value existsValidators.combine()- Min + Max range
One-Line Rule
Validators.min(minValue, message)creates a validator that ensures a numeric value is at least the specified minimum (inclusive), allowing empty values unless combined withrequired().
What's Next?
- Combine with
max()for numeric ranges - Add visual indicators for minimum thresholds
- Implement progressive pricing/discounts