Skip to content

VueStripeCardElement

A single, unified card input field that collects card number, expiration date, and CVC.

When to Use

Use StripeCardElement for a classic card-only payment experience. For modern payments that support cards, wallets, and bank transfers, consider StripePaymentElement instead.

What is Card Element?

Card Element is the classic Stripe card input that provides:

CapabilityDescription
Unified InputSingle field for card number, expiry, and CVC
Real-time ValidationValidates card number format, expiry, and CVC as user types
Card Brand DetectionAutomatically detects and displays card brand icon
Customizable StylingFull control over fonts, colors, and appearance
No clientSecret RequiredCan be used without a PaymentIntent (flexible integration)

How It Works

┌─────────────────────────────────────────────────────────────┐
│  CardElement mounts inside StripeElements                   │
│  (Does NOT require clientSecret - more flexible than        │
│   PaymentElement)                                           │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  Renders a single input field with embedded Stripe iframe   │
│  Emits @ready when mounted                                  │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  Customer types card details                                │
│  - Card number (auto-formats, detects brand)                │
│  - MM/YY expiry (auto-formats)                              │
│  - CVC (3 or 4 digits based on brand)                       │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  Emits @change on each keystroke                            │
│  { complete, empty, error, brand, value }                   │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  On submit: Two approaches available                        │
└─────────────────────────────────────────────────────────────┘
              │                               │
              ▼                               ▼
┌─────────────────────────┐     ┌─────────────────────────────┐
│  With PaymentIntent     │     │  Create Payment Method Only │
│                         │     │                             │
│  stripe.confirmCard-    │     │  stripe.createPayment-      │
│  Payment(clientSecret,  │     │  Method({ type: 'card',     │
│  { payment_method: {    │     │    card: cardElement })     │
│    card: cardElement}}) │     │  → Returns PaymentMethod ID │
└─────────────────────────┘     └─────────────────────────────┘

Usage

vue
<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements>
      <VueStripeCardElement
        :options="cardOptions"
        @ready="onReady"
        @change="onChange"
      />
      <button @click="handleSubmit">Pay</button>
    </VueStripeElements>
  </VueStripeProvider>
</template>

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

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY

const cardOptions = {
  style: {
    base: {
      fontSize: '16px',
      color: '#424770',
      '::placeholder': { color: '#aab7c4' }
    }
  }
}

const onReady = (element) => {
  console.log('Card element ready', element)
}

const onChange = (event) => {
  if (event.error) {
    console.error(event.error.message)
  }
}
</script>

Props

PropTypeRequiredDescription
optionsStripeCardElementOptionsNoCard element configuration

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
@readyVueStripeCardElementEmitted when the element is fully rendered
@changeStripeCardElementChangeEventEmitted when the element value changes
@focus-Emitted when the element gains focus
@blur-Emitted when the element loses focus
@escape-Emitted when the escape key is pressed

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

Loading Slot

Rendered while the card element is initializing:

vue
<VueStripeCardElement>
  <template #loading>
    <div class="skeleton-loader">Loading card form...</div>
  </template>
</VueStripeCardElement>

Error Slot

Rendered when there's an error (from the change event):

vue
<VueStripeCardElement>
  <template #error="{ error }">
    <div class="card-error">{{ error }}</div>
  </template>
</VueStripeCardElement>

Exposed Methods

Access these methods via template ref:

vue
<script setup>
import { ref } from 'vue'

const cardRef = ref()

const focusCard = () => cardRef.value?.focus()
const clearCard = () => cardRef.value?.clear()
const blurCard = () => cardRef.value?.blur()
</script>

<template>
  <VueStripeCardElement ref="cardRef" />
  <button @click="focusCard">Focus</button>
  <button @click="clearCard">Clear</button>
</template>
MethodDescription
focus()Focus the card input
blur()Blur the card input
clear()Clear the card input value

Exposed Properties

PropertyTypeDescription
elementRef<VueStripeCardElement | null>The Stripe card element instance
loadingRef<boolean>Whether the element is loading
errorRef<string | null>Current error message

Examples

Basic Styling

vue
<script setup>
const options = {
  style: {
    base: {
      fontSize: '16px',
      color: '#32325d',
      fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
      '::placeholder': {
        color: '#aab7c4'
      }
    },
    invalid: {
      color: '#fa755a',
      iconColor: '#fa755a'
    }
  }
}
</script>

<template>
  <VueStripeCardElement :options="options" />
</template>

Hide Postal Code

vue
<VueStripeCardElement
  :options="{ hidePostalCode: true }"
/>

Tracking Validation State

vue
<script setup>
import { ref } from 'vue'

const cardComplete = ref(false)
const cardError = ref('')

const handleChange = (event) => {
  cardComplete.value = event.complete
  cardError.value = event.error?.message || ''
}
</script>

<template>
  <VueStripeCardElement @change="handleChange" />
  <p v-if="cardError" class="error">{{ cardError }}</p>
  <button :disabled="!cardComplete">Pay</button>
</template>

Complete Payment Form

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

// In a child component inside StripeElements:
const { stripe } = useStripe()
const { elements } = useStripeElements()

const processing = ref(false)
const cardComplete = ref(false)

const handleSubmit = async (clientSecret) => {
  if (!stripe.value || !elements.value) return

  processing.value = true

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

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

  processing.value = false

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

With Custom Container Styling

vue
<template>
  <div class="card-container">
    <VueStripeCardElement
      :options="options"
      @change="onChange"
    />
  </div>
</template>

<style scoped>
.card-container {
  padding: 12px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: white;
  transition: border-color 0.2s ease;
}

.card-container:focus-within {
  border-color: #635bff;
  box-shadow: 0 0 0 3px rgba(99, 91, 255, 0.1);
}
</style>

TypeScript

ts
import { ref } from 'vue'
import { StripeCardElement } from '@vue-stripe/vue-stripe'
import type {
  StripeCardElement as StripeCardElementType,
  StripeCardElementChangeEvent,
  StripeCardElementOptions
} from '@stripe/stripe-js'

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

// Event handlers
const handleReady = (element: StripeCardElementType) => {
  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 StripeCardElement>>()

Test Cards

Use these test card numbers in test mode:

CardNumber
Visa4242 4242 4242 4242
Visa (debit)4000 0566 5566 5556
Mastercard5555 5555 5555 4444
American Express3782 822463 10005
Discover6011 1111 1111 1117
Declined4000 0000 0000 0002
Insufficient funds4000 0000 0000 9995

Use any future expiration date and any 3-digit CVC (4-digit for Amex).

See Also

Released under the MIT License.