Validate card inputs in real time using Stripe Elements' built-in change event. Mount a CardElement or PaymentElement, listen for the 'change' event, and check the error and complete properties. Stripe.js handles format validation, card number checks, and expiry validation — you just display the messages.
Real-Time Card Validation with Stripe.js Elements
Stripe Elements handles card validation automatically. When you mount a CardElement (or the newer PaymentElement), Stripe validates card numbers using the Luhn algorithm, checks expiry dates, and verifies CVC length — all in real time. You never see or handle raw card data. Your job is simply to listen for the 'change' event and display the error messages Stripe provides. This gives your users instant feedback while keeping you PCI compliant.
Prerequisites
- Stripe.js loaded on your page (<script src="https://js.stripe.com/v3/"></script>)
- Your publishable key (pk_test_) — this is the only key used on the frontend
- A container element in your HTML for mounting the card input
Step-by-step guide
Load Stripe.js and create an Elements instance
Load Stripe.js and create an Elements instance
Initialize Stripe with your publishable key (pk_test_) and create an Elements instance. The publishable key is safe for frontend use — never use your secret key in the browser.
1<script src="https://js.stripe.com/v3/"></script>2<script>3 const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');4 const elements = stripe.elements();5</script>Expected result: The stripe and elements objects are ready to use.
Mount a CardElement with styling
Mount a CardElement with styling
Create a CardElement and mount it to a DOM element. You can customize the appearance with a style object.
1const cardElement = elements.create('card', {2 style: {3 base: {4 fontSize: '16px',5 color: '#32325d',6 '::placeholder': { color: '#aab7c4' },7 },8 invalid: {9 color: '#fa755a',10 iconColor: '#fa755a',11 },12 },13});1415cardElement.mount('#card-element');Expected result: A styled card input field appears in your #card-element container with fields for number, expiry, and CVC.
Listen for the change event to show validation errors
Listen for the change event to show validation errors
Attach a 'change' event listener to the CardElement. The event object has an error property (null if valid) and a complete property (true when all fields are filled correctly).
1const errorDisplay = document.getElementById('card-errors');2const submitButton = document.getElementById('submit-btn');34cardElement.on('change', (event) => {5 if (event.error) {6 errorDisplay.textContent = event.error.message;7 submitButton.disabled = true;8 } else {9 errorDisplay.textContent = '';10 submitButton.disabled = !event.complete;11 }12});Expected result: Typing an invalid card number shows 'Your card number is invalid' in real time. The submit button enables only when all fields are complete and valid.
Test with Stripe test card numbers
Test with Stripe test card numbers
Use Stripe's test cards to verify validation behavior. Valid test cards pass validation; invalid numbers trigger errors.
1// Valid test cards (pass validation):2// 4242 4242 4242 4242 — Visa, always succeeds3// 5555 5555 5555 4444 — Mastercard4// 3782 822463 10005 — Amex56// Invalid inputs that trigger validation errors:7// 1234 5678 9012 3456 — fails Luhn check8// 4242 4242 4242 4241 — fails Luhn check9// Expired date (e.g., 01/20) — shows 'expiration date is in the past'Expected result: Valid test cards show no errors and enable the submit button. Invalid inputs display descriptive error messages.
Complete working example
1<!DOCTYPE html>2<html>3<head>4 <title>Card Payment</title>5 <script src="https://js.stripe.com/v3/"></script>6 <style>7 #card-element {8 border: 1px solid #ccc;9 padding: 12px;10 border-radius: 4px;11 max-width: 400px;12 }13 #card-errors {14 color: #fa755a;15 margin-top: 8px;16 font-size: 14px;17 }18 #submit-btn {19 margin-top: 16px;20 padding: 10px 24px;21 background: #5469d4;22 color: white;23 border: none;24 border-radius: 4px;25 cursor: pointer;26 font-size: 16px;27 }28 #submit-btn:disabled {29 background: #aab7c4;30 cursor: not-allowed;31 }32 </style>33</head>34<body>35 <form id="payment-form">36 <label for="card-element">Credit or debit card</label>37 <div id="card-element"></div>38 <div id="card-errors" role="alert"></div>39 <button id="submit-btn" type="submit" disabled>Pay $20.00</button>40 </form>4142 <script>43 const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');44 const elements = stripe.elements();4546 const cardElement = elements.create('card', {47 style: {48 base: {49 fontSize: '16px',50 color: '#32325d',51 '::placeholder': { color: '#aab7c4' },52 },53 invalid: { color: '#fa755a', iconColor: '#fa755a' },54 },55 });56 cardElement.mount('#card-element');5758 const errorDisplay = document.getElementById('card-errors');59 const submitBtn = document.getElementById('submit-btn');6061 cardElement.on('change', (event) => {62 if (event.error) {63 errorDisplay.textContent = event.error.message;64 submitBtn.disabled = true;65 } else {66 errorDisplay.textContent = '';67 submitBtn.disabled = !event.complete;68 }69 });7071 document.getElementById('payment-form').addEventListener('submit', async (e) => {72 e.preventDefault();73 submitBtn.disabled = true;74 submitBtn.textContent = 'Processing...';75 // Confirm payment with your PaymentIntent client_secret here76 });77 </script>78</body>79</html>Common mistakes when validating card inputs using Stripe.js
Why it's a problem: Building your own card number validation instead of using Stripe Elements
How to avoid: Stripe Elements validates card numbers, expiry, and CVC automatically. Building your own validation means handling raw card data, which violates PCI compliance.
Why it's a problem: Using the secret key (sk_) on the frontend
How to avoid: Only use the publishable key (pk_test_ or pk_live_) in browser code. The secret key must never leave your server.
Why it's a problem: Not disabling the submit button until the form is complete
How to avoid: Check event.complete in the change handler and disable the button until all fields pass validation. This prevents premature submission attempts.
Why it's a problem: Not showing validation errors to the user
How to avoid: Always display event.error.message in a visible element. Stripe provides clear, user-friendly error messages like 'Your card number is invalid'.
Best practices
- Always use Stripe Elements for card input — never collect raw card numbers in your own input fields
- Use the 'change' event to provide real-time validation feedback as the user types
- Disable the submit button until event.complete is true to prevent invalid submissions
- Style the 'invalid' state with a contrasting color (like red) for clear error visibility
- Use the publishable key (pk_test_) on the frontend — never the secret key (sk_)
- Test with both valid (4242 4242 4242 4242) and invalid card numbers to verify error handling
- Consider using the newer PaymentElement instead of CardElement for automatic multi-method support
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write an HTML page with Stripe.js that mounts a CardElement, listens for validation errors via the change event, displays error messages in real time, and disables the submit button until the card is complete and valid.
Add Stripe card validation to my payment form. Mount a CardElement with custom styling, show real-time validation errors below the input, and disable the Pay button until the card fields are complete. Use the Stripe.js change event.
Frequently asked questions
Does Stripe validate the card number in real time?
Yes. Stripe Elements runs Luhn algorithm checks, verifies the card brand, checks expiry dates, and validates CVC length as the user types. No API call is needed for this basic validation.
Can I use separate fields for card number, expiry, and CVC?
Yes. Use elements.create('cardNumber'), elements.create('cardExpiry'), and elements.create('cardCvc') for individual fields. Each has its own change event for validation.
What errors does the change event return?
Common errors include: 'Your card number is invalid', 'Your card's expiration date is in the past', 'Your card's security code is incomplete'. Stripe provides user-friendly messages you can display directly.
Is it PCI compliant to use Stripe Elements?
Yes. Stripe Elements renders card fields in a secure iframe. Your server never sees the raw card data, which qualifies you for the simplest PCI compliance level (SAQ A).
What if I need a fully custom payment form design?
Stripe Elements are customizable via the style object, but for highly custom payment UIs with specific business logic, RapidDev can help build a tailored integration that maintains PCI compliance while matching your exact design requirements.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation