Skip to content

VueStripeCardElement ​

A single card field that collects the card number, expiry date, CVC and (optionally) postal code. It needs no Intent on its parent: use it inside VueStripeElements without a clientSecret, and confirm with useConfirmCardPayment() or create a PaymentMethod with useCreatePaymentMethod(). For the steps, see Accept card payments with the Card Element; Choosing an integration compares it with the Payment Element.

Usage ​

vue
<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements>
      <CardForm :client-secret="clientSecret" />
    </VueStripeElements>
  </VueStripeProvider>
</template>

<script setup lang="ts">
import '@vue-stripe/vue-stripe/style.css' // once, e.g. in main.ts
import { VueStripeProvider, VueStripeElements } from '@vue-stripe/vue-stripe'
import CardForm from './CardForm.vue'

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY
const clientSecret = 'pi_xxx_secret_xxx' // From your backend
</script>
vue
<!-- CardForm.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import { VueStripeCardElement, useConfirmCardPayment } from '@vue-stripe/vue-stripe'

const props = defineProps<{ clientSecret: string }>()
const cardRef = ref<InstanceType<typeof VueStripeCardElement> | null>(null)
const complete = ref(false)
const paid = ref(false)
const { confirmCardPayment, loading, error } = useConfirmCardPayment()

const pay = async () => {
  const card = cardRef.value?.element
  if (!card) return
  const { paymentIntent } = await confirmCardPayment(props.clientSecret, {
    payment_method: { card }
  })
  // A succeeded PaymentIntent cannot be confirmed again
  if (paymentIntent?.status === 'succeeded') paid.value = true
}
</script>

<template>
  <p v-if="paid">Payment received.</p>
  <form v-else @submit.prevent="pay">
    <VueStripeCardElement
      ref="cardRef"
      :options="{ hidePostalCode: true }"
      @change="(event) => (complete = event.complete)"
    />
    <button :disabled="!complete || loading">Pay</button>
    <p v-if="error">{{ error }}</p>
  </form>
</template>

Props ​

PropTypeRequiredDescription
optionsStripeCardElementOptionsNoCard element configuration

Updating options

Changes to options are applied to the mounted element with element.update(), and only when they really change: a re-render that passes a new but deep-equal object (e.g. an inline :options="{ ... }") does not call update().

disableLink and preferredNetwork are read when the element is created and never passed to update(), because Stripe.js doesn't accept them there. To change one, re-mount the component (e.g. with a :key).

Options Object ​

ts
interface StripeCardElementOptions {
  style?: {
    base?: StripeElementStyle
    complete?: StripeElementStyle
    empty?: StripeElementStyle
    invalid?: StripeElementStyle
  }
  hidePostalCode?: boolean
  iconStyle?: 'default' | 'solid'
  disabled?: boolean
}

Style Properties ​

ts
interface StripeElementStyle {
  color?: string
  fontFamily?: string
  fontSize?: string
  fontSmoothing?: string
  fontStyle?: string
  fontVariant?: string
  fontWeight?: string
  iconColor?: string
  lineHeight?: string
  letterSpacing?: string
  textAlign?: string
  textDecoration?: string
  textShadow?: string
  textTransform?: string
  '::placeholder'?: { color?: string }
  ':-webkit-autofill'?: { color?: string }
  ':focus'?: StripeElementStyle
  ':hover'?: StripeElementStyle
  '::selection'?: { color?: string; backgroundColor?: string }
}

Events ​

EventPayloadDescription
@readyStripeCardElementEmitted when the element is fully rendered
@changeStripeCardElementChangeEventEmitted when the element value changes
@focus{ elementType: 'card' }Emitted when the element gains focus
@blur{ elementType: 'card' }Emitted when the element loses focus
@escape{ elementType: 'card' }Emitted when the escape key is pressed
@networkschange{ elementType: 'card'; networks: string[] | null; loading: boolean }Emitted when the card networks available for the entered number change (co-badged cards). networks lists them, for example ['cartes_bancaires', 'visa']; it is null while loading is true
@loaderror{ elementType: 'card'; error: StripeError }Emitted when the element fails to load

@loaderror also fires when creating the element fails (for example when the surrounding provider does not support it), with the payload { elementType: 'card', error: { message } }; the message is also shown through the #error slot.

Change Event ​

ts
interface StripeCardElementChangeEvent {
  elementType: 'card'
  empty: boolean
  complete: boolean
  error?: {
    type: string
    code: string
    message: string
  }
  value: {
    postalCode: string
  }
  brand: 'visa' | 'mastercard' | 'amex' | 'discover' | 'diners' | 'jcb' | 'unionpay' | 'unknown'
}

Slots ​

SlotSlot PropsDescription
#loading-Rendered while the element is loading (default: "Loading card form...")
#error{ error: string }Rendered when the element has an error: the validation message from @change, a load error or a creation error
vue
<VueStripeCardElement>
  <template #loading>
    <div class="skeleton-loader">Loading...</div>
  </template>

  <template #error="{ error }">
    <p class="my-error">{{ error }}</p>
  </template>
</VueStripeCardElement>

Exposed Methods & Properties ​

Access these via a template ref:

MemberTypeDescription
elementShallowRef<StripeCardElement | null>The Stripe element instance (null until created)
loadingRef<boolean>true until the element emits ready
errorRef<string | null>Current error message (validation, load or creation error)
focus()() => voidFocus the element
blur()() => voidBlur the element
clear()() => voidClear the element's value

The component owns the element's lifecycle: it creates, mounts and destroys the element. Removing the component (for example with v-if) destroys the element, which takes the place of Stripe's element.unmount(); for a one-time listener use Vue's .once modifier (@ready.once) instead of element.once().

Refs are unwrapped on the template ref, so cardRef.value.element is a StripeCardElement | null. Type the template ref with the component type and the element with the Stripe type:

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VueStripeCardElement } from '@vue-stripe/vue-stripe'
import type { StripeCardElement } from '@stripe/stripe-js'

const cardRef = ref<InstanceType<typeof VueStripeCardElement> | null>(null)

const focus = () => cardRef.value?.focus()
const clear = () => cardRef.value?.clear()
const stripeElement = (): StripeCardElement | null => cardRef.value?.element ?? null
</script>

<template>
  <VueStripeCardElement ref="cardRef" />
  <button type="button" @click="focus">Focus</button>
  <button type="button" @click="clear">Clear</button>
  <p v-if="stripeElement()">The Stripe element exists</p>
</template>

Styling ​

The component renders this markup around Stripe's iframe:

html
<div class="vue-stripe-card-element vue-stripe-element">
  <!-- only when there is an error; an #error slot replaces the inner div -->
  <div class="vue-stripe-card-element-error vue-stripe-element-error">
    <div class="vue-stripe-error-message">The error message</div>
  </div>
  <!-- Stripe mounts its iframe here; plus vue-stripe-card-element-loading / vue-stripe-element-loading while loading -->
  <div class="vue-stripe-card-element-mount vue-stripe-element-mount"></div>
  <!-- only while loading; a #loading slot replaces the inner div -->
  <div class="vue-stripe-card-element-loader vue-stripe-element-loader">
    <div class="vue-stripe-loading-message">Loading card form...</div>
  </div>
</div>

The default styles for these classes ship in the package stylesheet. Import it once in your app:

ts
import '@vue-stripe/vue-stripe/style.css'

Target .vue-stripe-element* to style every element at once, or .vue-stripe-card-element* for this one.

TypeScript ​

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VueStripeCardElement } from '@vue-stripe/vue-stripe'
import type {
  StripeCardElement,
  StripeCardElementChangeEvent,
  StripeCardElementOptions
} from '@stripe/stripe-js'

// Options
const options: StripeCardElementOptions = {
  style: {
    base: {
      fontSize: '16px'
    }
  },
  hidePostalCode: true
}

// Event handlers
const handleReady = (element: StripeCardElement) => {
  console.log('Ready:', element)
}

const handleChange = (event: StripeCardElementChangeEvent) => {
  console.log('Complete:', event.complete)
  console.log('Brand:', event.brand)
}

// Template ref
const cardRef = ref<InstanceType<typeof VueStripeCardElement> | null>(null)
</script>

<template>
  <VueStripeCardElement
    ref="cardRef"
    :options="options"
    @ready="handleReady"
    @change="handleChange"
  />
  <button type="button" @click="cardRef?.focus()">Focus</button>
</template>

See Also ​