Tailwind CSS Validator Components
5 Components
Tailwind CSS Validator Components
<section class="p-6 bg-white rounded-lg shadow-sm">
<div class="max-w-md mx-auto space-y-8">
<h3 class="text-lg font-medium text-gray-900">Special Format Validation</h3>
<!-- 信用卡验证 -->
<div class="space-y-4">
<h4 class="text-md font-medium text-gray-800">Credit Card Information</h4>
<div class="space-y-3">
<!-- 卡号验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Card Number</label>
<div class="relative">
<input type="text" id="cc-number" required placeholder="0000 0000 0000 0000" maxlength="19"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500 tabular-nums" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z" />
<path fill-rule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clip-rule="evenodd" />
</svg>
</div>
<!-- 卡类型图标 -->
<div class="card-type absolute inset-y-0 right-0 flex items-center pr-3">
<img id="card-logo" class="h-6 w-auto hidden" src="" alt="Card type">
</div>
</div>
<p class="mt-1 text-xs text-red-500 card-error hidden">Please enter a valid card number</p>
</div>
<!-- 到期日期和CVV -->
<div class="grid grid-cols-2 gap-4">
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Expiration Date</label>
<input type="text" id="cc-expiry" required placeholder="MM/YY" maxlength="5"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500 tabular-nums" />
<p class="mt-1 text-xs text-red-500 expiry-error hidden">Enter valid date (MM/YY)</p>
</div>
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">CVV</label>
<div class="relative">
<input type="text" id="cc-cvv" required placeholder="000" maxlength="4"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500 tabular-nums" />
<div class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div class="absolute bottom-full right-0 mb-2 w-48 p-2 bg-gray-800 text-xs text-white rounded shadow-lg hidden group-hover:block">
The CVV is the 3 or 4 digit security code on your card, usually found on the back.
</div>
</div>
</div>
<p class="mt-1 text-xs text-red-500 cvv-error hidden">Enter valid CVV</p>
</div>
</div>
</div>
</div>
<!-- 日期验证 -->
<div class="space-y-4">
<h4 class="text-md font-medium text-gray-800">Date Validation</h4>
<div class="space-y-3">
<!-- 生日验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Date of Birth</label>
<div class="relative">
<input type="date" required min="1900-01-01" max="2023-12-31"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p class="mt-1 text-xs text-gray-500">Must be between 1900-01-01 and 2023-12-31</p>
</div>
<!-- 将来日期验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Schedule for Future Date</label>
<input type="date" required id="future-date"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
<p class="mt-1 text-xs text-red-500 future-date-error hidden">Date must be in the future</p>
</div>
</div>
</div>
<!-- 数字验证 -->
<div class="space-y-4">
<h4 class="text-md font-medium text-gray-800">Number Validation</h4>
<div class="space-y-3">
<!-- 范围验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Rating (1-10)</label>
<div class="relative">
<input type="number" min="1" max="10" required
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
</div>
<p class="mt-1 text-xs text-gray-500">Enter a number between 1 and 10</p>
</div>
<!-- 金额验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Amount (USD)</label>
<div class="relative">
<input type="number" min="0.01" step="0.01" required placeholder="0.00"
class="w-full px-3 py-2 pl-8 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-700">
$
</div>
</div>
<p class="mt-1 text-xs text-gray-500">Minimum amount: $0.01</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 信用卡格式化和验证
const ccNumberInput = document.getElementById('cc-number');
const ccExpiryInput = document.getElementById('cc-expiry');
const ccCvvInput = document.getElementById('cc-cvv');
const futureDateInput = document.getElementById('future-date');
const cardLogo = document.getElementById('card-logo');
// 设置将来日期的最小值为今天
const today = new Date();
const yyyy = today.getFullYear();
const mm = String(today.getMonth() + 1).padStart(2, '0');
const dd = String(today.getDate()).padStart(2, '0');
const todayFormatted = `${yyyy}-${mm}-${dd}`;
futureDateInput.setAttribute('min', todayFormatted);
// 信用卡号格式化
ccNumberInput.addEventListener('input', function(e) {
let value = e.target.value;
// 移除所有非数字字符
value = value.replace(/\D/g, '');
// 每4位添加空格
let formattedValue = '';
for (let i = 0; i < value.length; i++) {
if (i > 0 && i % 4 === 0) {
formattedValue += ' ';
}
formattedValue += value[i];
}
// 更新输入框值
e.target.value = formattedValue;
// 验证卡号并显示卡类型
validateCreditCard(value);
});
// 到期日期格式化
ccExpiryInput.addEventListener('input', function(e) {
let value = e.target.value;
// 移除所有非数字字符
value = value.replace(/\D/g, '');
// 添加斜杠
if (value.length > 2) {
value = value.substring(0, 2) + '/' + value.substring(2);
}
// 更新输入框值
e.target.value = value;
// 验证到期日期
validateExpiry(value);
});
// CVV格式化
ccCvvInput.addEventListener('input', function(e) {
let value = e.target.value;
// 移除所有非数字字符
value = value.replace(/\D/g, '');
// 更新输入框值
e.target.value = value;
// 验证CVV
validateCVV(value);
});
// 将来日期验证
futureDateInput.addEventListener('input', function(e) {
validateFutureDate(e.target.value);
});
// 验证信用卡号和显示卡类型
function validateCreditCard(value) {
const cardError = document.querySelector('.card-error');
const visaPattern = /^4/;
const mastercardPattern = /^5[1-5]/;
const amexPattern = /^3[47]/;
const discoverPattern = /^6(?:011|5)/;
let isValid = false;
// 隐藏卡类型图标
cardLogo.classList.add('hidden');
cardLogo.src = '';
// 卡号长度验证
if (value.length < 13 || value.length > 19) {
cardError.textContent = "Card number must be 13-19 digits";
cardError.classList.remove('hidden');
ccNumberInput.classList.add('border-red-500');
ccNumberInput.classList.remove('border-green-500');
return;
}
// Luhn算法验证
if (!luhnCheck(value)) {
cardError.textContent = "Invalid card number";
cardError.classList.remove('hidden');
ccNumberInput.classList.add('border-red-500');
ccNumberInput.classList.remove('border-green-500');
return;
}
// 卡类型识别和验证
if (visaPattern.test(value)) {
cardLogo.src = "https://cdn.jsdelivr.net/gh/n3r4zzurr0/svg-credit-card-payment-icons/flat-rounded/visa.svg";
isValid = true;
} else if (mastercardPattern.test(value)) {
cardLogo.src = "https://cdn.jsdelivr.net/gh/n3r4zzurr0/svg-credit-card-payment-icons/flat-rounded/mastercard.svg";
isValid = true;
} else if (amexPattern.test(value)) {
cardLogo.src = "https://cdn.jsdelivr.net/gh/n3r4zzurr0/svg-credit-card-payment-icons/flat-rounded/amex.svg";
isValid = true;
} else if (discoverPattern.test(value)) {
cardLogo.src = "https://cdn.jsdelivr.net/gh/n3r4zzurr0/svg-credit-card-payment-icons/flat-rounded/discover.svg";
isValid = true;
}
if (isValid) {
cardError.classList.add('hidden');
cardLogo.classList.remove('hidden');
ccNumberInput.classList.remove('border-red-500');
ccNumberInput.classList.add('border-green-500');
} else {
cardError.textContent = "Unrecognized card type";
cardError.classList.remove('hidden');
ccNumberInput.classList.add('border-red-500');
ccNumberInput.classList.remove('border-green-500');
}
}
// Luhn算法检查
function luhnCheck(cardNumber) {
if (!cardNumber) return false;
// 移除所有非数字字符
cardNumber = cardNumber.replace(/\D/g, '');
let sum = 0;
let shouldDouble = false;
// 从右到左遍历
for (let i = cardNumber.length - 1; i >= 0; i--) {
let digit = parseInt(cardNumber.charAt(i));
if (shouldDouble) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
shouldDouble = !shouldDouble;
}
return (sum % 10) === 0;
}
// 验证到期日期
function validateExpiry(value) {
const expiryError = document.querySelector('.expiry-error');
// 检查格式
if (value.length < 5) {
expiryError.classList.remove('hidden');
ccExpiryInput.classList.add('border-red-500');
ccExpiryInput.classList.remove('border-green-500');
return;
}
const parts = value.split('/');
let month = parseInt(parts[0], 10);
let year = parseInt('20' + parts[1], 10);
// 检查月份是否有效
if (isNaN(month) || month < 1 || month > 12) {
expiryError.textContent = "Invalid month";
expiryError.classList.remove('hidden');
ccExpiryInput.classList.add('border-red-500');
ccExpiryInput.classList.remove('border-green-500');
return;
}
// 检查年份是否有效且未过期
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth() + 1; // getMonth() 返回 0-11
if (isNaN(year) || year < currentYear || (year === currentYear && month < currentMonth)) {
expiryError.textContent = "Card expired";
expiryError.classList.remove('hidden');
ccExpiryInput.classList.add('border-red-500');
ccExpiryInput.classList.remove('border-green-500');
return;
}
// 有效
expiryError.classList.add('hidden');
ccExpiryInput.classList.remove('border-red-500');
ccExpiryInput.classList.add('border-green-500');
}
// 验证CVV
function validateCVV(value) {
const cvvError = document.querySelector('.cvv-error');
// 检查长度
if (value.length < 3 || value.length > 4) {
cvvError.textContent = "CVV must be 3-4 digits";
cvvError.classList.remove('hidden');
ccCvvInput.classList.add('border-red-500');
ccCvvInput.classList.remove('border-green-500');
return;
}
// 有效
cvvError.classList.add('hidden');
ccCvvInput.classList.remove('border-red-500');
ccCvvInput.classList.add('border-green-500');
}
// 验证将来日期
function validateFutureDate(value) {
const futureDateError = document.querySelector('.future-date-error');
if (!value) {
futureDateError.classList.remove('hidden');
futureDateInput.classList.add('border-red-500');
futureDateInput.classList.remove('border-green-500');
return;
}
const selectedDate = new Date(value);
const today = new Date();
today.setHours(0, 0, 0, 0);
if (selectedDate < today) {
futureDateError.textContent = "Date must be in the future";
futureDateError.classList.remove('hidden');
futureDateInput.classList.add('border-red-500');
futureDateInput.classList.remove('border-green-500');
} else {
futureDateError.classList.add('hidden');
futureDateInput.classList.remove('border-red-500');
futureDateInput.classList.add('border-green-500');
}
}
});
</script>
</section>
<section class="p-6 bg-white rounded-lg shadow-sm">
<div class="max-w-md mx-auto space-y-6">
<h3 class="text-lg font-medium text-gray-900">Server-side Validation Form</h3>
<p class="text-sm text-gray-600">This form demonstrates server-side validation with appropriate feedback.</p>
<form id="server-validation-form" class="space-y-6">
<!-- 表单状态消息 -->
<div class="status-message hidden"></div>
<!-- 用户名输入 -->
<div class="form-control">
<label for="username" class="block text-sm font-medium text-gray-700 mb-1">Username</label>
<div class="relative">
<input type="text" id="username" name="username" required
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="server-error text-xs text-red-500 mt-1 hidden"></div>
</div>
</div>
<!-- 电子邮件输入 -->
<div class="form-control">
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
<div class="relative">
<input type="email" id="email" name="email" required
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="server-error text-xs text-red-500 mt-1 hidden"></div>
</div>
</div>
<!-- 密码输入 -->
<div class="form-control">
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<div class="relative">
<input type="password" id="password" name="password" required
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="server-error text-xs text-red-500 mt-1 hidden"></div>
</div>
</div>
<!-- 提交按钮 -->
<div>
<button type="submit" class="w-full flex justify-center items-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<span id="submit-text">Create Account</span>
<div id="submit-spinner" class="hidden ml-2">
<svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
</button>
</div>
</form>
<!-- 成功状态展示 -->
<div id="success-state" class="hidden">
<div class="text-center p-5 border border-green-200 bg-green-50 rounded-md">
<div class="inline-flex h-12 w-12 rounded-full bg-green-100 items-center justify-center mb-4">
<svg class="h-6 w-6 text-green-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
</div>
<h3 class="text-lg font-medium text-green-800">Account Created Successfully</h3>
<p class="text-sm text-green-600 mt-2">Your account has been created and you are now logged in.</p>
<button id="reset-demo" class="mt-4 px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
Reset Demo
</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('server-validation-form');
const submitButton = form.querySelector('button[type="submit"]');
const submitText = document.getElementById('submit-text');
const submitSpinner = document.getElementById('submit-spinner');
const statusMessage = document.querySelector('.status-message');
const successState = document.getElementById('success-state');
const resetButton = document.getElementById('reset-demo');
// 表单提交处理
form.addEventListener('submit', function(e) {
e.preventDefault();
// 重置所有错误状态
resetFormErrors();
// 禁用按钮并显示加载状态
submitButton.disabled = true;
submitText.textContent = 'Creating Account...';
submitSpinner.classList.remove('hidden');
// 模拟向服务器发送请求
setTimeout(() => {
// 获取表单数据
const formData = {
username: document.getElementById('username').value,
email: document.getElementById('email').value,
password: document.getElementById('password').value
};
// 模拟服务器验证
const errors = simulateServerValidation(formData);
if (Object.keys(errors).length > 0) {
// 显示错误
displayServerErrors(errors);
// 重置按钮状态
submitButton.disabled = false;
submitText.textContent = 'Create Account';
submitSpinner.classList.add('hidden');
// 显示全局错误消息
statusMessage.textContent = 'Please correct the errors and try again.';
statusMessage.classList.remove('hidden', 'bg-green-50', 'text-green-800', 'border-green-200');
statusMessage.classList.add('p-3', 'rounded-md', 'text-sm', 'bg-red-50', 'text-red-800', 'border', 'border-red-200');
} else {
// 模拟成功创建账户
setTimeout(() => {
// 隐藏表单,显示成功状态
form.classList.add('hidden');
successState.classList.remove('hidden');
// 隐藏状态消息
statusMessage.classList.add('hidden');
}, 1000);
}
}, 2000);
});
// 重置演示
resetButton.addEventListener('click', function() {
// 显示表单,隐藏成功状态
form.classList.remove('hidden');
successState.classList.add('hidden');
// 清空表单
form.reset();
// 启用按钮并重置文本
submitButton.disabled = false;
submitText.textContent = 'Create Account';
submitSpinner.classList.add('hidden');
// 重置所有错误状态
resetFormErrors();
statusMessage.classList.add('hidden');
});
// 模拟服务器验证
function simulateServerValidation(data) {
const errors = {};
// 用户名验证
if (data.username === 'admin') {
errors.username = 'Username "admin" is reserved';
} else if (data.username.length < 3) {
errors.username = 'Username must be at least 3 characters';
}
// 电子邮件验证
if (data.email === '[email protected]') {
errors.email = 'This email is already registered';
} else if (!data.email.includes('@')) {
errors.email = 'Please enter a valid email address';
}
// 密码验证
if (data.password === 'password' || data.password === '123456') {
errors.password = 'Password is too common, please choose a stronger password';
} else if (data.password.length < 8) {
errors.password = 'Password must be at least 8 characters long';
}
return errors;
}
// 显示服务器错误
function displayServerErrors(errors) {
for (const field in errors) {
if (errors.hasOwnProperty(field)) {
const input = document.getElementById(field);
const errorElement = input.parentElement.querySelector('.server-error');
// 显示错误消息
errorElement.textContent = errors[field];
errorElement.classList.remove('hidden');
// 添加错误样式
input.classList.add('border-red-500');
input.classList.remove('border-gray-300');
// 添加焦点事件以清除错误
input.addEventListener('input', function onInput() {
input.classList.remove('border-red-500');
input.classList.add('border-gray-300');
errorElement.classList.add('hidden');
// 移除事件监听器
input.removeEventListener('input', onInput);
});
}
}
}
// 重置所有表单错误
function resetFormErrors() {
// 重置所有服务器错误消息
const serverErrors = document.querySelectorAll('.server-error');
serverErrors.forEach(error => {
error.classList.add('hidden');
error.textContent = '';
});
// 重置所有输入框样式
const inputs = form.querySelectorAll('input');
inputs.forEach(input => {
input.classList.remove('border-red-500');
input.classList.add('border-gray-300');
});
}
// 设置测试数据按钮
const usernameInput = document.getElementById('username');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
// 添加测试数据注释
const usernameComment = document.createElement('p');
usernameComment.classList.add('text-xs', 'text-gray-500', 'mt-1');
usernameComment.textContent = 'Try "admin" to see an error';
usernameInput.parentElement.appendChild(usernameComment);
const emailComment = document.createElement('p');
emailComment.classList.add('text-xs', 'text-gray-500', 'mt-1');
emailComment.textContent = 'Try "[email protected]" to see an error';
emailInput.parentElement.appendChild(emailComment);
const passwordComment = document.createElement('p');
passwordComment.classList.add('text-xs', 'text-gray-500', 'mt-1');
passwordComment.textContent = 'Try "password" to see an error';
passwordInput.parentElement.appendChild(passwordComment);
});
</script>
</section>
<section class="p-6 bg-white rounded-lg shadow-sm">
<div class="max-w-md mx-auto space-y-6">
<h3 class="text-lg font-medium text-gray-900">Real-time Form Validation</h3>
<form class="space-y-6" id="validation-form" novalidate>
<!-- 电子邮件验证 -->
<div class="form-control">
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
<div class="relative">
<input id="email" type="email" required placeholder="[email protected]"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg>
</div>
<div class="hidden validation-icon absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="error-icon hidden h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="success-icon hidden h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p class="error-message mt-1 text-xs text-red-500 hidden">Please enter a valid email address</p>
</div>
<!-- 密码验证 -->
<div class="form-control">
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<div class="relative">
<input id="password" type="password" required minlength="8"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
placeholder="Enter your password"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
</svg>
</div>
<div class="hidden validation-icon absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="error-icon hidden h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="success-icon hidden h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
</div>
<div class="mt-2">
<div class="password-strength-meter">
<div class="space-y-1">
<div class="flex items-center">
<div class="flex-1 flex space-x-1">
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
</div>
<div class="ml-2 text-xs text-gray-500 w-16 password-strength-text">Too weak</div>
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-x-4 gap-y-2 mt-2">
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 8+ characters
</div>
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 1+ uppercase letter
</div>
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 1+ lowercase letter
</div>
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 1+ number
</div>
</div>
</div>
</div>
<!-- 密码确认 -->
<div class="form-control">
<label for="confirm-password" class="block text-sm font-medium text-gray-700 mb-1">Confirm Password</label>
<div class="relative">
<input id="confirm-password" type="password" required
placeholder="Confirm your password"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
<div class="hidden validation-icon absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="error-icon hidden h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="success-icon hidden h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p class="error-message mt-1 text-xs text-red-500 hidden">Passwords do not match</p>
</div>
<!-- 提交按钮 -->
<div class="pt-2">
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
disabled>
Create Account
</button>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('validation-form');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirm-password');
const submitButton = form.querySelector('button[type="submit"]');
// 验证电子邮件
function validateEmail() {
const isValid = emailInput.validity.valid;
updateValidationUI(emailInput, isValid);
return isValid;
}
// 验证密码强度
function validatePassword() {
const password = passwordInput.value;
const hasLength = password.length >= 8;
const hasLowerCase = /[a-z]/.test(password);
const hasUpperCase = /[A-Z]/.test(password);
const hasNumber = /\d/.test(password);
// 更新密码要求检查
updatePasswordRequirements(hasLength, hasUpperCase, hasLowerCase, hasNumber);
// 计算强度得分
const score = [hasLength, hasLowerCase, hasUpperCase, hasNumber].filter(Boolean).length;
updatePasswordStrengthMeter(score);
const isValid = hasLength && hasLowerCase && hasUpperCase && hasNumber;
updateValidationUI(passwordInput, isValid);
return isValid;
}
// 验证确认密码
function validateConfirmPassword() {
const isValid = confirmPasswordInput.value === passwordInput.value && confirmPasswordInput.value !== '';
updateValidationUI(confirmPasswordInput, isValid);
if (!isValid && confirmPasswordInput.value !== '') {
confirmPasswordInput.nextElementSibling.nextElementSibling.classList.remove('hidden');
}
return isValid;
}
// 更新密码强度计量表
function updatePasswordStrengthMeter(score) {
const segments = document.querySelectorAll('.strength-segment');
const strengthText = document.querySelector('.password-strength-text');
// 重置所有段
segments.forEach(segment => {
segment.classList.remove('bg-red-500', 'bg-yellow-500', 'bg-green-400', 'bg-green-500');
segment.classList.add('bg-gray-200');
});
// 根据分数设置文字
const strengthLabels = ['Too weak', 'Weak', 'Medium', 'Strong'];
const strengthColors = ['text-red-500', 'text-yellow-500', 'text-green-400', 'text-green-500'];
// 重置颜色类
strengthText.classList.remove('text-red-500', 'text-yellow-500', 'text-green-400', 'text-green-500');
// 如果有密码则更新UI
if (passwordInput.value) {
// 设置文本
strengthText.textContent = strengthLabels[score - 1] || 'Too weak';
strengthText.classList.add(strengthColors[score - 1] || 'text-red-500');
// 设置段颜色
for (let i = 0; i < score; i++) {
if (i === 0) segments[i].classList.add('bg-red-500');
else if (i === 1) segments[i].classList.add('bg-yellow-500');
else if (i === 2) segments[i].classList.add('bg-green-400');
else if (i === 3) segments[i].classList.add('bg-green-500');
}
} else {
strengthText.textContent = 'Too weak';
strengthText.classList.add('text-gray-500');
}
}
// 更新密码要求UI
function updatePasswordRequirements(hasLength, hasUpperCase, hasLowerCase, hasNumber) {
const checks = document.querySelectorAll('.validation-check');
// 更新每个要求的状态
updateRequirement(checks[0], hasLength);
updateRequirement(checks[1], hasUpperCase);
updateRequirement(checks[2], hasLowerCase);
updateRequirement(checks[3], hasNumber);
}
function updateRequirement(element, isValid) {
if (isValid) {
element.classList.remove('text-gray-300');
element.classList.add('text-green-500');
} else {
element.classList.remove('text-green-500');
element.classList.add('text-gray-300');
}
}
// 更新验证UI
function updateValidationUI(input, isValid) {
const container = input.parentElement;
const validationIcon = container.querySelector('.validation-icon');
const errorIcon = container.querySelector('.error-icon');
const successIcon = container.querySelector('.success-icon');
const errorMessage = container.nextElementSibling;
validationIcon.classList.remove('hidden');
if (isValid && input.value !== '') {
input.classList.remove('border-red-500');
input.classList.add('border-green-500');
errorIcon.classList.add('hidden');
successIcon.classList.remove('hidden');
if (errorMessage && errorMessage.classList.contains('error-message')) {
errorMessage.classList.add('hidden');
}
} else if (input.value !== '') {
input.classList.remove('border-green-500');
input.classList.add('border-red-500');
errorIcon.classList.remove('hidden');
successIcon.classList.add('hidden');
if (errorMessage && errorMessage.classList.contains('error-message')) {
errorMessage.classList.remove('hidden');
}
} else {
input.classList.remove('border-red-500', 'border-green-500');
validationIcon.classList.add('hidden');
if (errorMessage && errorMessage.classList.contains('error-message')) {
errorMessage.classList.add('hidden');
}
}
}
// 更新提交按钮状态
function updateSubmitButton() {
const isEmailValid = validateEmail();
const isPasswordValid = validatePassword();
const isConfirmPasswordValid = validateConfirmPassword();
if (isEmailValid && isPasswordValid && isConfirmPasswordValid) {
submitButton.disabled = false;
} else {
submitButton.disabled = true;
}
}
// 添加输入事件监听器
emailInput.addEventListener('input', function() {
validateEmail();
updateSubmitButton();
});
passwordInput.addEventListener('input', function() {
validatePassword();
if (confirmPasswordInput.value !== '') {
validateConfirmPassword();
}
updateSubmitButton();
});
confirmPasswordInput.addEventListener('input', function() {
validateConfirmPassword();
updateSubmitButton();
});
// 表单提交处理
form.addEventListener('submit', function(e) {
e.preventDefault();
const isValid = validateEmail() &&
validatePassword() &&
validateConfirmPassword();
if (isValid) {
alert('Form submitted successfully!');
// 这里可以发送表单数据或执行其他操作
}
});
});
</script>
</section>
<section class="p-6 bg-white rounded-lg shadow-sm">
<div class="max-w-md mx-auto space-y-8">
<h3 class="text-lg font-medium text-gray-900">Checkbox and Radio Validation</h3>
<form id="choices-form" class="space-y-6" novalidate>
<!-- 复选框验证 (至少选择一个) -->
<div class="space-y-4">
<h4 class="text-md font-medium text-gray-800">Interests</h4>
<p class="text-sm text-gray-600">Select at least one interest (required)</p>
<div class="space-y-2" id="interests-group">
<div class="flex items-center">
<input type="checkbox" id="tech" name="interests" value="technology"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="tech" class="ml-2 block text-sm text-gray-700">Technology</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="health" name="interests" value="health"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="health" class="ml-2 block text-sm text-gray-700">Health & Fitness</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="finance" name="interests" value="finance"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="finance" class="ml-2 block text-sm text-gray-700">Finance</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="art" name="interests" value="art"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="art" class="ml-2 block text-sm text-gray-700">Art & Culture</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="travel" name="interests" value="travel"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="travel" class="ml-2 block text-sm text-gray-700">Travel</label>
</div>
</div>
<div class="interests-error text-xs text-red-500 mt-1 hidden">Please select at least one interest</div>
</div>
<!-- 单选按钮验证 (必选) -->
<div class="space-y-4">
<h4 class="text-md font-medium text-gray-800">Subscription Plan</h4>
<p class="text-sm text-gray-600">Choose a subscription plan (required)</p>
<div class="space-y-2" id="plan-group">
<div class="flex items-center">
<input type="radio" id="free" name="plan" value="free"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" />
<label for="free" class="ml-2 block text-sm text-gray-700">
<span class="font-medium">Free</span>
<span class="block text-xs text-gray-500">Basic access with limited features</span>
</label>
</div>
<div class="flex items-center">
<input type="radio" id="pro" name="plan" value="pro"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" />
<label for="pro" class="ml-2 block text-sm text-gray-700">
<span class="font-medium">Pro - $9.99/month</span>
<span class="block text-xs text-gray-500">Full access with premium features</span>
</label>
</div>
<div class="flex items-center">
<input type="radio" id="team" name="plan" value="team"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300" />
<label for="team" class="ml-2 block text-sm text-gray-700">
<span class="font-medium">Team - $49.99/month</span>
<span class="block text-xs text-gray-500">Unlimited access for up to 10 members</span>
</label>
</div>
</div>
<div class="plan-error text-xs text-red-500 mt-1 hidden">Please select a subscription plan</div>
</div>
<!-- 嵌套的复选框验证 (条件验证) -->
<div class="space-y-4 border-t border-gray-200 pt-6">
<h4 class="text-md font-medium text-gray-800">Communication Preferences</h4>
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" id="marketing" name="marketing"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label for="marketing" class="font-medium text-gray-700">Marketing Communications</label>
<p class="text-gray-500">Receive updates about new features and promotions</p>
</div>
</div>
<!-- 条件选项 (仅当营销选项选中时才需要验证) -->
<div id="marketing-options" class="pl-7 space-y-2 mt-1 hidden">
<p class="text-sm text-gray-700">Choose preferred channels (at least one required):</p>
<div class="flex items-center">
<input type="checkbox" id="email-marketing" name="channels" value="email"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="email-marketing" class="ml-2 block text-sm text-gray-700">Email</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="sms-marketing" name="channels" value="sms"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="sms-marketing" class="ml-2 block text-sm text-gray-700">SMS</label>
</div>
<div class="flex items-center">
<input type="checkbox" id="push-marketing" name="channels" value="push"
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
<label for="push-marketing" class="ml-2 block text-sm text-gray-700">Push Notifications</label>
</div>
<div class="channels-error text-xs text-red-500 mt-1 hidden">Please select at least one communication channel</div>
</div>
</div>
<!-- 同意条款 (必选) -->
<div class="space-y-4 border-t border-gray-200 pt-6">
<div class="flex items-start">
<div class="flex items-center h-5">
<input type="checkbox" id="terms" name="terms" required
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" />
</div>
<div class="ml-3 text-sm">
<label for="terms" class="font-medium text-gray-700">Terms and Conditions</label>
<p class="text-gray-500">I agree to the <a href="#" class="text-blue-600 hover:underline">Terms of Service</a> and <a href="#" class="text-blue-600 hover:underline">Privacy Policy</a> (required)</p>
</div>
</div>
<div class="terms-error text-xs text-red-500 mt-1 hidden">You must agree to the terms and conditions</div>
</div>
<!-- 表单提交 -->
<div class="pt-2">
<button type="submit" class="w-full py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Submit Form
</button>
<div class="form-status text-sm mt-3 hidden"></div>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('choices-form');
const marketingCheckbox = document.getElementById('marketing');
const marketingOptions = document.getElementById('marketing-options');
const formStatus = document.querySelector('.form-status');
// 显示/隐藏营销渠道选项
marketingCheckbox.addEventListener('change', function() {
if (this.checked) {
marketingOptions.classList.remove('hidden');
} else {
marketingOptions.classList.add('hidden');
// 取消选中所有渠道
document.querySelectorAll('input[name="channels"]').forEach(checkbox => {
checkbox.checked = false;
});
// 隐藏错误消息
document.querySelector('.channels-error').classList.add('hidden');
}
});
// 表单提交验证
form.addEventListener('submit', function(e) {
e.preventDefault();
let isValid = true;
// 重置所有错误消息
resetAllErrors();
// 验证兴趣选择(至少选一个)
const interests = document.querySelectorAll('input[name="interests"]:checked');
if (interests.length === 0) {
document.querySelector('.interests-error').classList.remove('hidden');
document.getElementById('interests-group').classList.add('border', 'border-red-300', 'rounded-md', 'p-2');
isValid = false;
}
// 验证订阅计划选择(必选)
const plan = document.querySelector('input[name="plan"]:checked');
if (!plan) {
document.querySelector('.plan-error').classList.remove('hidden');
document.getElementById('plan-group').classList.add('border', 'border-red-300', 'rounded-md', 'p-2');
isValid = false;
}
// 验证营销渠道(如果选中了营销选项)
if (marketingCheckbox.checked) {
const channels = document.querySelectorAll('input[name="channels"]:checked');
if (channels.length === 0) {
document.querySelector('.channels-error').classList.remove('hidden');
isValid = false;
}
}
// 验证条款同意
const termsAccepted = document.getElementById('terms').checked;
if (!termsAccepted) {
document.querySelector('.terms-error').classList.remove('hidden');
isValid = false;
}
// 显示表单结果
if (isValid) {
// 显示成功消息
formStatus.textContent = 'Form submitted successfully!';
formStatus.classList.remove('hidden', 'text-red-500');
formStatus.classList.add('text-green-500', 'p-3', 'bg-green-50', 'border', 'border-green-200', 'rounded-md');
// 模拟表单提交 - 在实际应用中,这里会发送到服务器
console.log('Form data would be submitted here');
// 可选:重置表单
// form.reset();
} else {
// 显示错误消息
formStatus.textContent = 'Please fix the errors above before submitting.';
formStatus.classList.remove('hidden', 'text-green-500');
formStatus.classList.add('text-red-500', 'p-3', 'bg-red-50', 'border', 'border-red-200', 'rounded-md');
// 滚动到页面顶部以便用户看到错误
window.scrollTo({ top: 0, behavior: 'smooth' });
}
});
// 添加实时验证
document.querySelectorAll('input[name="interests"]').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const interestsChecked = document.querySelectorAll('input[name="interests"]:checked').length > 0;
if (interestsChecked) {
document.querySelector('.interests-error').classList.add('hidden');
document.getElementById('interests-group').classList.remove('border', 'border-red-300', 'rounded-md', 'p-2');
}
});
});
document.querySelectorAll('input[name="plan"]').forEach(radio => {
radio.addEventListener('change', function() {
document.querySelector('.plan-error').classList.add('hidden');
document.getElementById('plan-group').classList.remove('border', 'border-red-300', 'rounded-md', 'p-2');
});
});
document.querySelectorAll('input[name="channels"]').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const channelsChecked = document.querySelectorAll('input[name="channels"]:checked').length > 0;
if (channelsChecked) {
document.querySelector('.channels-error').classList.add('hidden');
}
});
});
document.getElementById('terms').addEventListener('change', function() {
if (this.checked) {
document.querySelector('.terms-error').classList.add('hidden');
}
});
// 重置所有错误消息
function resetAllErrors() {
const errorElements = document.querySelectorAll('.interests-error, .plan-error, .channels-error, .terms-error');
errorElements.forEach(element => {
element.classList.add('hidden');
});
document.getElementById('interests-group').classList.remove('border', 'border-red-300', 'rounded-md', 'p-2');
document.getElementById('plan-group').classList.remove('border', 'border-red-300', 'rounded-md', 'p-2');
}
});
</script>
</section>
<section class="p-6 bg-white rounded-lg shadow-sm">
<div class="max-w-md mx-auto space-y-8">
<!-- 基本验证器 -->
<div class="space-y-4">
<h3 class="text-lg font-medium text-gray-900">Basic Input Validation</h3>
<div class="space-y-3">
<!-- 邮箱验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
<input type="email" required placeholder="[email protected]"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
<p class="mt-1 text-xs text-red-500 invisible peer-invalid:visible">Please enter a valid email address</p>
</div>
<!-- 必填输入 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Full Name <span class="text-red-500">*</span></label>
<input type="text" required placeholder="John Doe"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500" />
<p class="mt-1 text-xs text-red-500 invisible peer-invalid:visible">This field is required</p>
</div>
<!-- 长度限制 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Username</label>
<input type="text" required minlength="3" maxlength="20" placeholder="username"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
<p class="mt-1 text-xs text-red-500 invisible peer-invalid:visible">Username must be 3-20 characters long</p>
</div>
</div>
</div>
<!-- 高级验证模式 -->
<div class="space-y-4">
<h3 class="text-lg font-medium text-gray-900">Advanced Pattern Validation</h3>
<div class="space-y-3">
<!-- 密码验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<input type="password" required minlength="8"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
placeholder="Enter your password"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
<div class="mt-1 text-xs text-red-500 invisible peer-invalid:visible space-y-1">
<p>Password must contain:</p>
<ul class="list-disc pl-5">
<li>At least 8 characters</li>
<li>At least one number</li>
<li>At least one uppercase letter</li>
<li>At least one lowercase letter</li>
</ul>
</div>
</div>
<!-- 电话号码验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Phone Number</label>
<input type="tel" required pattern="[0-9]{10}" placeholder="1234567890"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500 tabular-nums" />
<p class="mt-1 text-xs text-red-500 invisible peer-invalid:visible">Enter a 10-digit phone number</p>
</div>
<!-- URL验证 -->
<div class="form-control">
<label class="block text-sm font-medium text-gray-700 mb-1">Website URL</label>
<input type="url" required placeholder="https://example.com"
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 peer invalid:border-red-500 valid:border-green-500" />
<p class="mt-1 text-xs text-red-500 invisible peer-invalid:visible">Enter a valid URL starting with http:// or https://</p>
</div>
</div>
</div>
</div>
</section>
Copied to clipboard