Migrating to Stripe Elements

Learn how to migrate your existing Stripe.js v2 checkout flow to Stripe Elements.

If you’re using Stripe.js v2 to securely collect card information, you’ve needed to build and configure your own input fields, validation, and formatting when creating your payment form. With Stripe Elements, our prebuilt UI components, this is all handled by Stripe. Elements is fully customizable and seamlessly integrates into your checkout flow.

Using Elements, you can create UI components and insert them into your payment form. These components securely collect card information from your customers. When the payment form is submitted, the information is passed directly to Stripe. If you’re using the Charges API, a Token is returned that is then used to make a charge request or save the payment details for later. If you’re using the Payment Intents API, you can directly call confirmCardPayment to complete the payment or use createPaymentMethod to retrieve a PaymentMethod that can be passed to your server.

Migrating to Elements requires the following steps:

  1. Initial setup of Elements
  2. Convert your existing payment form
  3. Securely collect card information
  4. Handle events and errors
  5. Customize style and formatting

Step 1: Initial setup of Elements

Include the following script in the head section on every page on your site. This script must always load directly from js.stripe.com to remain PCI compliant. You can’t include the script in a bundle or host a copy of it yourself.

<script src="https://js.stripe.com/v3/"></script>

To best leverage Stripe’s advanced fraud functionality and ability to detect anomalous browsing behavior, include the script in the head section on every page on your site, not just the checkout page.

If you need to use both versions of Stripe.js for some period of time, they can be loaded and used on the same page. For example:

<script src="https://js.stripe.com/v2/"></script> <script src="https://js.stripe.com/v3/"></script>

Step 2: Convert your existing payment form

At the moment, your existing payment form might look something like this:

<form action="/charge" method="post" id="payment-form"> <div class="card-errors"></div> <div class="form-row"> <label> <span>Card number</span> <input type="text" size="20" data-stripe="number"> </label> </div> <div class="form-row"> <label> <span>Expiration (MM/YY)</span> <input type="text" size="2" data-stripe="exp_month"> </label> <span> / </span> <input type="text" size="2" data-stripe="exp_year"> </div> <div class="form-row"> <label> <span>CVC</span> <input type="text" size="4" data-stripe="cvc"> </label> </div> <div class="form-row"> <label> <span>Billing Zip</span> <input type="text" size="6" data-stripe="address_zip"> </label> </div> <input type="submit" class="submit" value="Submit Payment"> </form>

To use Elements, you create empty DOM elements (containers) instead of directly using DOM <input>s. Elements inserts a Stripe-hosted UI component within your container. Migrating to Elements allows you to make use of the card Element. This flexible UI component simplifies your form by minimizing the number of fields you need, requiring much less markup.

<form action="/charge" method="post" id="payment-form"> <div class="form-row"> <label for="card-element"> Credit or debit card </label> <div id="card-element"> <!-- a Stripe Element will be inserted here. --> </div> <!-- Used to display form errors --> <div id="card-errors" role="alert"></div> </div> <input type="submit" class="submit" value="Submit Payment"> </form>

Next, initialize the Stripe client by providing your publishable API key, and create an instance of Elements.

var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); var elements = stripe.elements();

You can now create a card Element and add it to your page using the mount() method.

var card = elements.create('card'); // Add an instance of the card UI component into the `card-element` <div> card.mount('#card-element');

Step 3: Securely collect card details

Using Elements to collect payment information is very similar to your current approach. The difference is that you’re passing a Stripe Element to the appropriate method, rather than a form or set of input fields.

Your current payment form submission might look like this:

var stripeResponseHandler = function(status, response) { // Grab the form: var form = document.getElementById('payment-form'); if (response.error) { // Problem! // Show the errors on the form: } else { // Token was created! // Get the token ID: var token = response.id; // Insert the token ID into the form so it gets submitted to the server var form = document.getElementById('payment-form'); var hiddenInput = document.createElement('input'); hiddenInput.setAttribute('type', 'hidden'); hiddenInput.setAttribute('name', 'stripeToken'); hiddenInput.setAttribute('value', token); form.appendChild(hiddenInput); // Submit the form form.submit(); } }; // Create a token when the form is submitted var form = document.getElementById('payment-form'); form.addEventListener('submit', function(e) { e.preventDefault(); Stripe.card.createToken(form, stripeResponseHandler); });

Switching to Elements requires two key changes:

  • Pass the createToken() method a Stripe Element as its first argument (instead of a form or values)
  • Use the Elements API to simplify the response handling logic
function stripeTokenHandler(token) { // Insert the token ID into the form so it gets submitted to the server var form = document.getElementById('payment-form'); var hiddenInput = document.createElement('input'); hiddenInput.setAttribute('type', 'hidden'); hiddenInput.setAttribute('name', 'stripeToken'); hiddenInput.setAttribute('value', token.id); form.appendChild(hiddenInput); // Submit the form form.submit(); } function createToken() { stripe.createToken(card).then(function(result) { if (result.error) { // Inform the user if there was an error var errorElement = document.getElementById('card-errors'); errorElement.textContent = result.error.message; } else { // Send the token to your server stripeTokenHandler(result.token); } }); }; // Create a token when the form is submitted. var form = document.getElementById('payment-form'); form.addEventListener('submit', function(e) { e.preventDefault(); createToken(); });

Step 4: Handle events and errors

Elements includes real-time input validation that helps facilitate your users’ checkout flow, increasing conversion. If you’re not doing any validation today, you can just rely on the default visual validation indicators of Elements to alert users of errors.

Elements also provides specific information about validation errors in real-time, helping you communicate them to your users.

card.on('change', function(event) { var displayError = document.getElementById('card-errors'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } });
card.on('change', ({error}) => { let displayError = document.getElementById('card-errors'); if (error) { displayError.textContent = error.message; } else { displayError.textContent = ''; } });

Step 5: Customize style and formatting

Elements comes with built-in formatting and masking for all inputs. If you have been using Stripe’s jquery.payment library or something that you have built in-house, you should be able to safely remove these from your integration.

Card elements can be customized to fit perfectly within your checkout page. For instance, you can make sure that the font is styled correctly by providing some additional options when creating the component:

var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); var elements = stripe.elements(); // Create an instance of the card UI component var card = elements.create('card', { 'style': { 'base': { 'fontFamily': 'Arial, sans-serif', 'fontSize': '8px', 'color': '#C1C7CD', }, 'invalid': { 'color': 'red', }, } }); // Mount the UI card component into the `card-element` <div> card.mount('#card-element');

You can find a full list of supported CSS properties that can be used as style parameters in our Stripe.js reference documentation.

Elements demo

This is a live demo of a payment form created using Elements and custom styling. Try it out, using some of our test card information, a random three-digit CVC number, and any expiration date in the future. Upon tokenization, the contents of the form are validated before being sent to the server.

Check out our other payment form examples to see how Elements can be customized.

<script src="https://js.stripe.com/v3/"></script> <form> <label> <input name="cardholder-name" class="field is-empty" placeholder="Jane Doe" /> <span><span>Name</span></span> </label> <label> <input class="field is-empty" type="tel" placeholder="(123) 456-7890" /> <span><span>Phone number</span></span> </label> <label> <div id="card-element" class="field is-empty"></div> <span><span>Credit or debit card</span></span> </label> <button type="submit">Pay $25</button> <div class="outcome"> <div class="error" role="alert"></div> <div class="success"> Success! Your Stripe token is <span class="token"></span> </div> </div> </form>
* { font-family: 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-variant: normal; padding: 0; margin: 0; } html { height: 100%; } body { background: #424770; display: flex; align-items: center; min-height: 100%; } form { width: 480px; margin: 20px auto; } label { height: 35px; position: relative; color: #8798AB; display: block; margin-top: 30px;
See all 144 lines margin-bottom: 20px; } label > span { position: absolute; top: 0; left: 0; width: 100%; height: 100%; font-weight: 300; line-height: 32px; color: #8798AB; border-bottom: 1px solid #586A82; transition: border-bottom-color 200ms ease-in-out; cursor: text; pointer-events: none; } label > span span { position: absolute; top: 0; left: 0; transform-origin: 0% 50%; transition: transform 200ms ease-in-out; cursor: text; } label .field.is-focused + span span, label .field:not(.is-empty) + span span { transform: scale(0.68) translateY(-36px); cursor: default; } label .field.is-focused + span { border-bottom-color: #34D08C; } .field { background: transparent; font-weight: 300; border: 0; color: white; outline: none; cursor: text; display: block; width: 100%; line-height: 32px; padding-bottom: 3px; transition: opacity 200ms ease-in-out; } .field::-webkit-input-placeholder { color: #8898AA; } .field::-moz-placeholder { color: #8898AA; } /* IE doesn't show placeholders when empty+focused */ .field:-ms-input-placeholder { color: #424770; } .field.is-empty:not(.is-focused) { opacity: 0; } button { float: left; display: block; background: #34D08C; color: white; border-radius: 2px; border: 0; margin-top: 20px; font-size: 19px; font-weight: 400; width: 100%; height: 47px; line-height: 45px; outline: none; } button:focus { background: #24B47E; } button:active { background: #159570; } .outcome { float: left; width: 100%; padding-top: 8px; min-height: 20px; text-align: center; } .success, .error { display: none; font-size: 15px; } .success.visible, .error.visible { display: inline; } .error { color: #E4584C; } .success { color: #34D08C; } .success .token { font-weight: 500; font-size: 15px; }
var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); var elements = stripe.elements(); var card = elements.create('card', { iconStyle: 'solid', style: { base: { iconColor: '#8898AA', color: 'white', lineHeight: '36px', fontWeight: 300, fontFamily: '"Helvetica Neue", Helvetica, sans-serif', fontSize: '19px', '::placeholder': { color: '#8898AA', }, }, invalid: { iconColor: '#e85746', color: '#e85746', } }, classes: { focus: 'is-focused', empty: 'is-empty', }, }); card.mount('#card-element');
See all 76 lines var inputs = document.querySelectorAll('input.field'); Array.prototype.forEach.call(inputs, function(input) { input.addEventListener('focus', function() { input.classList.add('is-focused'); }); input.addEventListener('blur', function() { input.classList.remove('is-focused'); }); input.addEventListener('keyup', function() { if (input.value.length === 0) { input.classList.add('is-empty'); } else { input.classList.remove('is-empty'); } }); }); function setOutcome(result) { var successElement = document.querySelector('.success'); var errorElement = document.querySelector('.error'); successElement.classList.remove('visible'); errorElement.classList.remove('visible'); if (result.token) { // Use the token to create a charge or a customer // https://stripe.com/docs/payments/charges-api successElement.querySelector('.token').textContent = result.token.id; successElement.classList.add('visible'); } else if (result.error) { errorElement.textContent = result.error.message; errorElement.classList.add('visible'); } } card.on('change', function(event) { setOutcome(event); }); document.querySelector('form').addEventListener('submit', function(e) { e.preventDefault(); var form = document.querySelector('form'); var extraDetails = { name: form.querySelector('input[name=cardholder-name]').value, }; stripe.createToken(card, extraDetails).then(setOutcome); });

Next steps

Now you have all the information you need to migrate from Stripe.js v2 to Stripe Elements. To learn more, continue reading:

Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.