Styling a payments page with Tailwind CSS
Tags: stripe, tailwindcss, webdev, tutorial
Published: 2022-07-04 10:08:22

## Introduction

In the [previous post](https://dev.to/stripe/automatic-payment-methods-with-the-stripe-payment-element-2o6k) in the series, we learned how to enable automatic payment methods for our Payment Element integration allowing us to turn payment methods on and off via the dashboard with a single integration.

In my ongoing #learninpublic quest, I’ll add some basic styling to the checkout component using [Tailwind CSS](https://tailwindcss.com/). Tailwind is a utility-first CSS framework that will allow us to style our markup with predefined classes, allowing us to very quickly create consistent styling and design directly in our code.

### Follow along

This post builds on the branch `02-automatic-payment-methods` in the [GitHub repo](https://github.com/matthewling-stripe/vite-react-stripe/tree/02-automatic-payment-methods). If you would like to follow along, checkout that branch using `git checkout 02-automatic-payment-methods` and apply the changes in this tutorial.

This completed demo is available on [GitHub](https://github.com/matthewling-stripe/vite-react-stripe/tree/03-styling-with-tailwind-css) on the branch `03-styling-with-tailwind-css`.

### Prerequisites

This demo was built using Node version `16.10.0`, and npm version `7.24.0`. You’ll need a basic understanding of React components and [JSX](https://reactjs.org/docs/introducing-jsx.html). A basic understanding of CSS will be helpful, although we will not be writing any CSS in this tutorial directly, instead we’ll  use Tailwind classes to quickly style our component.

### What you’ll learn

In this post you’ll learn how to install Tailwind and its dependencies in a Vite project with React, and the basics of Tailwind CSS to style a checkout page.

### Software stack

This project uses Vite as a build and development server, React for a frontend framework, [React Stripe](https://stripe.com/docs/stripe-js/react) to accept payments, and the [Payment Element](https://stripe.com/docs/payments/payment-element) to present and confirm payments on the frontend, and Tailwind to style the page. You can read a deep dive end-to-end integration in the first and second posts.

### Installing Tailwind

We’ll start by installing Tailwind and its dependencies:

```bash
npm install -D tailwindcss postcss autoprefixer
```

Then we’ll initialize Tailwind:

```bash
npx tailwindcss init -p
```

This will create two config files for us; `tailwind.config.js` and `postcss.config.js`. In `tailwind.config.js` we’ll add the paths to all of the files that will contain our Tailwind styles:

```javascript
/** @type {import('tailwindcss').Config} */
module.exports = {
  content:["./src/**/*.{js,jsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

Next we’ll create an `index.css` file for the baseline Tailwind declarations. In the `src` directory create an `index.css` file with the following:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

We have to include this css file in our `App` component:

```javascript
import Checkout from "./Checkout.jsx";
import './index.css'

function App() {
  return <Checkout />;
}

export default App;
```

Now we can test this by adding some classes to our existing code. In the `Checkout` component, let’s wrap the `Elements` provider in a `div` (JSX requires that adjacent tags be wrapped in a closing tag) and add a `h1` above it with some Tailwind classes, in this case we’ll apply a `3xl` text size and an orange color:

```javascript
  return (
    <div>
      {clientSecretSettings.loading ? (
        <h1>Loading ...</h1>
      ) : (
        <div>
          <h1 class="text-3xl text-orange-600">Checkout</h1>
          <Elements
            stripe={stripePromise}
            options={{
              clientSecret: clientSecretSettings.clientSecret,
              appearance: { theme: "stripe" },
            }}
          >
            <CheckoutForm />
          </Elements>
        </div>
      )}
    </div>
  );
```

We should now see a large orange Checkout heading above the Payment Element.

![Orange heading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i82o88f2welfbkydxwa7.png)

### Order summary

Let’s add a list to reflect some products that we’re selling to our customers. In the Checkout component, we’ll add an order summary component. Firstly, let’s create a simple functional component in the `src` directory called `OrderSummary.jsx` that lists the products in the order. In this case, we’re selling a lot of llama related products:

```javascript
const OrderSummary = () => {
  return(
    <div>
      <h1>Order Summary</h1>
      <ul>
        <li>Llama food x 2</li>
        <li>Llama bedding x 1</li>
        <li>Llama lamps x 3</li>
      </ul>
      <div>
        Total: &euro; 10.00
      </div>
    </div>
  );
};

export default OrderSummary;
```

Normally we would pass the items as `props` from the `Checkout` component, but for the sake of this demo we’ll simply hardcode some list values. Next, we’ll import that in our `Checkout` component and include it on the page:

```javascript
import { useEffect, useState } from "react";
import axios from "axios";
import { Elements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";
import CheckoutForm from './CheckoutForm.jsx';
import OrderSummary from './OrderSummary.jsx';
```

```javascript
  return (
    <div>
      {clientSecretSettings.loading ? (
        <h1>Loading ...</h1>
      ) : (
        <div>
          <OrderSummary />
          <Elements
            stripe={stripePromise}
            options={{
              clientSecret: clientSecretSettings.clientSecret,
              appearance: { theme: "stripe" },
            }}
          >
            <CheckoutForm />
          </Elements>
        </div>
      )}
    </div>
  );
```

![Order summary](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mf0gdxqvue64i72efuuf.png)
 
We should now see our summary rendering above the payment element.

### Layout

Let’s apply some styling to make the layout better. We’d like to have the summary and the payment sheet side by side. For this we’ll use a flexbox and instruct the child items to take up half of the space. The order summary and payment element will be wrapped in their own `div`:

```javascript
  return (
    <div class="content-center flex flex-row">
      <div class="basis-1/2">
        <OrderSummary />
      </div>
      <div class="basis-1/2">
        {clientSecretSettings.loading ? (
          <h1 class="font-semibold text-3xl font-sans">Loading ...</h1>
        ) : (
          <Elements
            stripe={stripePromise}
            options={{
              clientSecret: clientSecretSettings.clientSecret,
              appearance: { theme: "stripe" },
            }}
          >
            <CheckoutForm />
          </Elements>
        )}
      </div>
    </div>
  );
```

![Layout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0qo32z9wwaq5yg5svmb6.png)

Next we’ll add some padding to the component, some background colors to separate the panels and rounded edges. `rounded-l` means round the left side of the div `rounded-r` the right side. `bg-gray` and `bg-blue` are set to `200` intensity and `p-10` adds 40 pixels of padding:

```javascript
  return (
    <div class="content-center flex flex-row">
      <div class="rounded-l-xl bg-gray-200 basis-1/2 p-10">
        <OrderSummary />
      </div>
      <div class="rounded-r-xl bg-blue-200 basis-1/2 p-10">
        {clientSecretSettings.loading ? (
          <h1 class="font-semibold text-3xl font-sans">Loading ...</h1>
        ) : (
          <Elements
            stripe={stripePromise}
            options={{
              clientSecret: clientSecretSettings.clientSecret,
              appearance: { theme: "stripe" },
            }}
          >
            <CheckoutForm />
          </Elements>
        )}
      </div>
    </div>
  );
```

![Add padding and panels](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qm5g00hwvjc1w2jzuyu.png)

Let’s center the component on the page and add a heading for our Llama store:
```javascript
  return (
    <div class="mx-auto w-9/12 m-10">
      <h1 class="ml-5 mb-5 font-semibold text-3xl font-sans text-indigo-600">Llama Store</h1>
      <div class="content-center flex flex-row">
        <div class="rounded-l-xl bg-gray-200 basis-1/2 p-10">
          <OrderSummary />
        </div>
        <div class="rounded-r-xl bg-blue-200 basis-1/2 p-10">
          {clientSecretSettings.loading ? (
            <h1 class="font-semibold text-3xl font-sans">Loading ...</h1>
          ) : (
            <Elements
              stripe={stripePromise}
              options={{
                clientSecret: clientSecretSettings.clientSecret,
                appearance: { theme: "stripe" },
              }}
            >
              <CheckoutForm />
            </Elements>
          )}
        </div>
      </div>
    </div>
  );
```

![Centered and with title](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rj1pq75d91bg706s6myx.png)
 
We have quite a number of classes here so let's take a look. `<div class="w-9/12 mx-auto mt-10">`. We’ve set the width of the root container to 75% of the screen. The `mx-auto` utility centers the container, and then we added a top margin of value 10.

For the store heading ` <h1 class="ml-5 mb-5 font-semibold text-3xl font-sans text-indigo-600">Llama Store</h1>` we’ve added some text size classes, a sans serif font, a nice indigo shade and some margins.

### Styling the order summary

This is already looking a lot nicer! The order summary is unstyled so let's tackle that. We’ll make the title more prominent, style the list of products with bullets, and move the order total down to separate it from the list:

```javascript
const OrderSummary = () => {

  return(
    <div>
      <h1 class="mb-5 font-semibold text-2xl font-sans text-indigo-600">Order Summary</h1>
      <ul class=" text-gray-600 font-light list-disc list-inside">
        <li>Llama food x 2</li>
        <li>Llama Bedding x 1</li>
        <li>Llama lamps x 3</li>
      </ul>
      <div class="mt-10 border-gray-300 border-t-2 pt-2 text-gray-600">
        Total: &euro; 10.00
      </div>
    </div>
  );
};

export default OrderSummary;
```

![Styled order summary](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bl4q1f9qxqv27ojvtzlc.png)

### Styling the submit button

Finally, let’s style the submit button, in the `CheckoutForm` component:

```javascript
  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button class="w-full mt-5 p-2 bg-black text-white rounded-md ">Pay &euro; 10.00</button>
      {/* Show error message to your customers */}
      {errorMessage && <div>{errorMessage}</div>}
    </form>
  )
```

I’d like the button to take up the full width of the available space to be aligned more with the Payment Element fields. Making it black with white text will also increase its importance on the page and feel more like a call to action.

![Styled submit button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n6lyh4fswght47774sfk.png)

### Conclusion

We’ve only really skimmed the surface of Tailwind CSS for styling and designing components, but in only a few minutes work we’ve gone from this:

![Unstyled checkout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p41tlts22qil549cpo8w.png)
 

... to this!

![Styled checkout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/evp03ga5pg6dxpsdaqgp.png)

There’s so much more that we could do, but this tutorial shows just how quickly we can style our components using Tailwind CSS. We’re super excited to hear about what you learn and build, so please don’t hesitate to reach out to us anytime!

### About the author

![Matthew Ling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c8rpgkqr91ivs8sxr73g.png) 

Matthew Ling ([@mattling_dev](@mattling_dev)) is a Developer Advocate at Stripe. Matt loves to tinker with new technology, adores Ruby and coffee and also moonlighted as a pro music photographer. His photo site is at [matthewling.com](https://matthewling.com) and developer site is at [mattling.dev](https://mattling.dev).

## Stay connected

In addition, you can stay up to date with Stripe in a few ways:

📣 Follow us on [Twitter](https://twitter.com/StripeDev)
💬 Join the official [Discord server](https://stripe.com/go/developer-chat)
📺 Subscribe to our [Youtube channel](https://www.youtube.com/StripeDevelopers)
📧 Sign up for the [Dev Digest](https://go.stripe.global/dev-digest)