Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to validate card inputs using Stripe.js

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.

What you'll learn

  • How to mount Stripe Elements and listen for validation events
  • How to display real-time error messages for invalid card inputs
  • How to disable the submit button until the form is complete and valid
  • The difference between CardElement and individual card field elements
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner5 min read10 minutesStripe.js v3+, any frontend frameworkMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
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.

2

Mount a CardElement with styling

Create a CardElement and mount it to a DOM element. You can customize the appearance with a style object.

typescript
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});
14
15cardElement.mount('#card-element');

Expected result: A styled card input field appears in your #card-element container with fields for number, expiry, and CVC.

3

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).

typescript
1const errorDisplay = document.getElementById('card-errors');
2const submitButton = document.getElementById('submit-btn');
3
4cardElement.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.

4

Test with Stripe test card numbers

Use Stripe's test cards to verify validation behavior. Valid test cards pass validation; invalid numbers trigger errors.

typescript
1// Valid test cards (pass validation):
2// 4242 4242 4242 4242 — Visa, always succeeds
3// 5555 5555 5555 4444 — Mastercard
4// 3782 822463 10005 — Amex
5
6// Invalid inputs that trigger validation errors:
7// 1234 5678 9012 3456 — fails Luhn check
8// 4242 4242 4242 4241 — fails Luhn check
9// 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

public/payment.html
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>
41
42 <script>
43 const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
44 const elements = stripe.elements();
45
46 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');
57
58 const errorDisplay = document.getElementById('card-errors');
59 const submitBtn = document.getElementById('submit-btn');
60
61 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 });
70
71 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 here
76 });
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.

ChatGPT Prompt

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.

Stripe Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.