Skip to content

Card Element

The Card Element provides card-specific payment collection. Use it when you only need to accept card payments or want maximum control over the card input UI.

Choosing Between Card Elements

ComponentUse Case
VueStripeCardElementSingle input for card number, expiry, and CVC
Split elementsSeparate inputs for custom layouts

Single Card Element

The simplest way to collect card details:

vue
<script setup>
import { ref } from 'vue'
import {
  StripeProvider,
  StripeElements,
  StripeCardElement,
  useStripe,
  useStripeElements
} from '@vue-stripe/vue-stripe'

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const clientSecret = ref('pi_..._secret_...')

const handleSubmit = async () => {
  const { stripe } = useStripe()
  const { elements } = useStripeElements()

  const cardElement = elements.value?.getElement('card')

  const { error, paymentIntent } = await stripe.value.confirmCardPayment(
    clientSecret.value,
    {
      payment_method: {
        card: cardElement
      }
    }
  )

  if (error) {
    console.error(error.message)
  } else if (paymentIntent.status === 'succeeded') {
    console.log('Payment succeeded!')
  }
}
</script>

<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements>
      <form @submit.prevent="handleSubmit">
        <VueStripeCardElement />
        <button type="submit">Pay</button>
      </form>
    </VueStripeElements>
  </VueStripeProvider>
</template>

No clientSecret required

Unlike Payment Element, Card Element can work without a clientSecret on StripeElements. You pass the secret directly to confirmCardPayment.

Split Card Elements

For custom layouts, use separate elements for each field:

vue
<script setup>
import {
  StripeProvider,
  StripeElements,
  StripeCardNumberElement,
  StripeCardExpiryElement,
  StripeCardCvcElement
} from '@vue-stripe/vue-stripe'
</script>

<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements>
      <div class="card-form">
        <div class="field">
          <label>Card number</label>
          <VueStripeCardNumberElement />
        </div>
        <div class="row">
          <div class="field">
            <label>Expiry</label>
            <VueStripeCardExpiryElement />
          </div>
          <div class="field">
            <label>CVC</label>
            <VueStripeCardCvcElement />
          </div>
        </div>
      </div>
    </VueStripeElements>
  </VueStripeProvider>
</template>

Confirming with Split Elements

When using split elements, reference the card number element:

js
const handleSubmit = async () => {
  const { stripe } = useStripe()
  const { elements } = useStripeElements()

  // Get the card number element (expiry and CVC are linked automatically)
  const cardNumberElement = elements.value?.getElement('cardNumber')

  const { error, paymentIntent } = await stripe.value.confirmCardPayment(
    clientSecret.value,
    {
      payment_method: {
        card: cardNumberElement
      }
    }
  )
}

Complete Example

vue
<script setup lang="ts">
import { ref } from 'vue'
import {
  StripeProvider,
  StripeElements,
  StripeCardElement,
  useStripe,
  useStripeElements
} from '@vue-stripe/vue-stripe'

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const clientSecret = 'pi_..._secret_...' // From your backend

const processing = ref(false)
const cardComplete = ref(false)
const errorMessage = ref('')
const success = ref(false)

const handleChange = (event: { complete: boolean; error?: { message: string } }) => {
  cardComplete.value = event.complete
  errorMessage.value = event.error?.message || ''
}

const handleSubmit = async () => {
  const { stripe } = useStripe()
  const { elements } = useStripeElements()

  if (!stripe.value || !elements.value) return

  processing.value = true
  errorMessage.value = ''

  const cardElement = elements.value.getElement('card')

  const { error, paymentIntent } = await stripe.value.confirmCardPayment(
    clientSecret,
    {
      payment_method: {
        card: cardElement!,
        billing_details: {
          name: 'Customer Name'
        }
      }
    }
  )

  if (error) {
    errorMessage.value = error.message || 'Payment failed'
    processing.value = false
  } else if (paymentIntent?.status === 'succeeded') {
    success.value = true
  }
}

const cardStyles = {
  base: {
    fontSize: '16px',
    color: '#424770',
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
    '::placeholder': {
      color: '#aab7c4'
    }
  },
  invalid: {
    color: '#9e2146',
    iconColor: '#9e2146'
  }
}
</script>

<template>
  <div class="card-form">
    <div v-if="success" class="success">
      Payment successful!
    </div>

    <VueStripeProvider v-else :publishable-key="publishableKey">
      <VueStripeElements>
        <form @submit.prevent="handleSubmit">
          <div class="card-field">
            <label>Card details</label>
            <VueStripeCardElement
              :options="{ style: cardStyles, hidePostalCode: true }"
              @change="handleChange"
            />
          </div>

          <div v-if="errorMessage" class="error">
            {{ errorMessage }}
          </div>

          <button
            type="submit"
            :disabled="processing || !cardComplete"
          >
            {{ processing ? 'Processing...' : 'Pay' }}
          </button>
        </form>
      </VueStripeElements>
    </VueStripeProvider>
  </div>
</template>

<style scoped>
.card-form {
  max-width: 400px;
  margin: 0 auto;
}

.card-field {
  margin-bottom: 16px;
}

.card-field label {
  display: block;
  margin-bottom: 8px;
  font-size: 14px;
  font-weight: 500;
}

:deep(.vue-stripe-card-element) {
  padding: 12px;
  border: 1px solid #e6e6e6;
  border-radius: 4px;
  background: white;
}

:deep(.vue-stripe-card-element:focus-within) {
  border-color: #5469d4;
  box-shadow: 0 0 0 1px #5469d4;
}

button {
  width: 100%;
  background: #5469d4;
  color: white;
  padding: 12px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.error {
  color: #9e2146;
  font-size: 14px;
  margin-bottom: 16px;
}

.success {
  color: #00d66b;
  padding: 20px;
  text-align: center;
}
</style>

Card Element vs Payment Element

FeatureCard ElementPayment Element
Payment methodsCards only40+ methods
Layout controlHighMedium
Setup complexityLowerHigher
Automatic updatesNoYes
clientSecret requirementOptionalRequired

When to Use Card Element

  • You only accept card payments
  • You need precise control over field layout
  • You want to match an existing design system
  • You're building a POS-style interface

Next Steps

Released under the MIT License.