# How to validate card inputs using Stripe.js

- Tool: Stripe
- Difficulty: Beginner
- Time required: 10 minutes
- Compatibility: Stripe.js v3+, any frontend framework
- Last updated: March 2026

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

## Before you start

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

```
<script src="https://js.stripe.com/v3/"></script>
<script>
  const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
  const elements = stripe.elements();
</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.

```
const cardElement = elements.create('card', {
  style: {
    base: {
      fontSize: '16px',
      color: '#32325d',
      '::placeholder': { color: '#aab7c4' },
    },
    invalid: {
      color: '#fa755a',
      iconColor: '#fa755a',
    },
  },
});

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

```
const errorDisplay = document.getElementById('card-errors');
const submitButton = document.getElementById('submit-btn');

cardElement.on('change', (event) => {
  if (event.error) {
    errorDisplay.textContent = event.error.message;
    submitButton.disabled = true;
  } else {
    errorDisplay.textContent = '';
    submitButton.disabled = !event.complete;
  }
});
```

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

```
// Valid test cards (pass validation):
// 4242 4242 4242 4242 — Visa, always succeeds
// 5555 5555 5555 4444 — Mastercard
// 3782 822463 10005 — Amex

// Invalid inputs that trigger validation errors:
// 1234 5678 9012 3456 — fails Luhn check
// 4242 4242 4242 4241 — fails Luhn check
// 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 code example

File: `public/payment.html`

```html
<!DOCTYPE html>
<html>
<head>
  <title>Card Payment</title>
  <script src="https://js.stripe.com/v3/"></script>
  <style>
    #card-element {
      border: 1px solid #ccc;
      padding: 12px;
      border-radius: 4px;
      max-width: 400px;
    }
    #card-errors {
      color: #fa755a;
      margin-top: 8px;
      font-size: 14px;
    }
    #submit-btn {
      margin-top: 16px;
      padding: 10px 24px;
      background: #5469d4;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
    }
    #submit-btn:disabled {
      background: #aab7c4;
      cursor: not-allowed;
    }
  </style>
</head>
<body>
  <form id="payment-form">
    <label for="card-element">Credit or debit card</label>
    <div id="card-element"></div>
    <div id="card-errors" role="alert"></div>
    <button id="submit-btn" type="submit" disabled>Pay $20.00</button>
  </form>

  <script>
    const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
    const elements = stripe.elements();

    const cardElement = elements.create('card', {
      style: {
        base: {
          fontSize: '16px',
          color: '#32325d',
          '::placeholder': { color: '#aab7c4' },
        },
        invalid: { color: '#fa755a', iconColor: '#fa755a' },
      },
    });
    cardElement.mount('#card-element');

    const errorDisplay = document.getElementById('card-errors');
    const submitBtn = document.getElementById('submit-btn');

    cardElement.on('change', (event) => {
      if (event.error) {
        errorDisplay.textContent = event.error.message;
        submitBtn.disabled = true;
      } else {
        errorDisplay.textContent = '';
        submitBtn.disabled = !event.complete;
      }
    });

    document.getElementById('payment-form').addEventListener('submit', async (e) => {
      e.preventDefault();
      submitBtn.disabled = true;
      submitBtn.textContent = 'Processing...';
      // Confirm payment with your PaymentIntent client_secret here
    });
  </script>
</body>
</html>
```

## Common mistakes

- **Building your own card number validation instead of using Stripe Elements** — undefined Fix: Stripe Elements validates card numbers, expiry, and CVC automatically. Building your own validation means handling raw card data, which violates PCI compliance.
- **Using the secret key (sk_) on the frontend** — undefined Fix: Only use the publishable key (pk_test_ or pk_live_) in browser code. The secret key must never leave your server.
- **Not disabling the submit button until the form is complete** — undefined Fix: Check event.complete in the change handler and disable the button until all fields pass validation. This prevents premature submission attempts.
- **Not showing validation errors to the user** — undefined Fix: 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

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

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-validate-card-inputs-using-stripe-js
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-validate-card-inputs-using-stripe-js
