Skip to content

Split Card Elements

Three separate input components for card number, expiration, and CVC. Use these when you need more control over layout and per-field validation.

When to Use

Use Split Card Elements when you need:

  • Custom form layouts (horizontal, multi-column)
  • Different styling per field
  • Per-field validation display
  • Better accessibility with separate labels

For simpler integration, consider StripeCardElement instead.

What are Split Card Elements?

Split Card Elements give you granular control over card input layout:

ComponentDescription
StripeCardNumberElementCard number input with brand detection and Luhn validation
StripeCardExpiryElementMM/YY expiration date with auto-formatting
StripeCardCvcElement3-4 digit security code (adapts to card brand)

How It Works

┌─────────────────────────────────────────────────────────────┐
│  All three elements mount inside StripeElements             │
│  (Share the same Elements instance and context)             │
└─────────────────────────────────────────────────────────────┘

        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
┌───────────────┐     ┌───────────────┐     ┌───────────────┐
│ CardNumber    │     │ CardExpiry    │     │ CardCvc       │
│               │     │               │     │               │
│ 4242 4242 ... │     │ MM / YY       │     │ CVC           │
│               │     │               │     │               │
│ Detects brand │     │ Auto-formats  │     │ 3 or 4 digits │
│ Shows icon    │     │ Validates     │     │ based on brand│
└───────────────┘     └───────────────┘     └───────────────┘
        │                     │                     │
        ▼                     ▼                     ▼
┌─────────────────────────────────────────────────────────────┐
│  Each element emits @change independently                   │
│  Track all three completion states for submit button        │
└─────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│  On submit: Reference the CardNumber element                │
│  stripe.createPaymentMethod({                               │
│    type: 'card',                                            │
│    card: cardNumberElement  ← Just the number element!      │
│  })                                                         │
│  Stripe automatically collects expiry and CVC from siblings │
└─────────────────────────────────────────────────────────────┘

Components

  • StripeCardNumberElement - Card number input with brand detection
  • StripeCardExpiryElement - MM/YY expiration date input
  • StripeCardCvcElement - 3 or 4 digit security code input

Usage

vue
<template>
  <VueStripeProvider :publishable-key="publishableKey">
    <VueStripeElements>
      <div class="card-form">
        <div class="field">
          <label>Card Number</label>
          <VueStripeCardNumberElement
            :options="elementStyle"
            @change="onNumberChange"
          />
        </div>

        <div class="row">
          <div class="field">
            <label>Expiration</label>
            <VueStripeCardExpiryElement
              :options="elementStyle"
              @change="onExpiryChange"
            />
          </div>

          <div class="field">
            <label>CVC</label>
            <VueStripeCardCvcElement
              :options="elementStyle"
              @change="onCvcChange"
            />
          </div>
        </div>

        <button :disabled="!allComplete">Pay</button>
      </div>
    </VueStripeElements>
  </VueStripeProvider>
</template>

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

const publishableKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY

const numberComplete = ref(false)
const expiryComplete = ref(false)
const cvcComplete = ref(false)

const allComplete = computed(() =>
  numberComplete.value && expiryComplete.value && cvcComplete.value
)

const elementStyle = {
  style: {
    base: {
      fontSize: '16px',
      color: '#32325d'
    }
  }
}

const onNumberChange = (event) => {
  numberComplete.value = event.complete
}

const onExpiryChange = (event) => {
  expiryComplete.value = event.complete
}

const onCvcChange = (event) => {
  cvcComplete.value = event.complete
}
</script>

Props

All three components share the same props interface:

PropTypeRequiredDescription
optionsobjectNoElement configuration options

Options Object

ts
interface SplitCardElementOptions {
  style?: {
    base?: StripeElementStyle
    complete?: StripeElementStyle
    empty?: StripeElementStyle
    invalid?: StripeElementStyle
  }
  disabled?: boolean
  // CardNumber specific:
  showIcon?: boolean        // Show card brand icon
  iconStyle?: 'default' | 'solid'
}

Events

All three components emit the same events:

EventPayloadDescription
@readyStripeElementEmitted when the element is fully rendered
@changeChangeEventEmitted 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 Differences

StripeCardNumberElement

ts
interface CardNumberChangeEvent {
  elementType: 'cardNumber'
  empty: boolean
  complete: boolean
  brand: 'visa' | 'mastercard' | 'amex' | 'discover' | 'diners' | 'jcb' | 'unionpay' | 'unknown'
  error?: { message: string }
}

StripeCardExpiryElement

ts
interface CardExpiryChangeEvent {
  elementType: 'cardExpiry'
  empty: boolean
  complete: boolean
  error?: { message: string }
}

StripeCardCvcElement

ts
interface CardCvcChangeEvent {
  elementType: 'cardCvc'
  empty: boolean
  complete: boolean
  error?: { message: string }
}

Exposed Methods

Access via template ref on each component:

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

const numberRef = ref()
const expiryRef = ref()
const cvcRef = ref()

const focusNumber = () => numberRef.value?.focus()
const clearAll = () => {
  numberRef.value?.clear()
  expiryRef.value?.clear()
  cvcRef.value?.clear()
}
</script>

<template>
  <VueStripeCardNumberElement ref="numberRef" />
  <VueStripeCardExpiryElement ref="expiryRef" />
  <VueStripeCardCvcElement ref="cvcRef" />
</template>
MethodDescription
focus()Focus the input
blur()Blur the input
clear()Clear the input value

Examples

Horizontal Layout

vue
<template>
  <div class="horizontal-card-form">
    <VueStripeCardNumberElement class="number-field" />
    <VueStripeCardExpiryElement class="expiry-field" />
    <VueStripeCardCvcElement class="cvc-field" />
  </div>
</template>

<style scoped>
.horizontal-card-form {
  display: flex;
  gap: 1rem;
}

.number-field {
  flex: 2;
}

.expiry-field,
.cvc-field {
  flex: 1;
}
</style>

With Per-Field Errors

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

const numberError = ref('')
const expiryError = ref('')
const cvcError = ref('')

const onNumberChange = (event) => {
  numberError.value = event.error?.message || ''
}

const onExpiryChange = (event) => {
  expiryError.value = event.error?.message || ''
}

const onCvcChange = (event) => {
  cvcError.value = event.error?.message || ''
}
</script>

<template>
  <div class="field">
    <label>Card Number</label>
    <VueStripeCardNumberElement @change="onNumberChange" />
    <span v-if="numberError" class="error">{{ numberError }}</span>
  </div>

  <div class="field">
    <label>Expiration</label>
    <VueStripeCardExpiryElement @change="onExpiryChange" />
    <span v-if="expiryError" class="error">{{ expiryError }}</span>
  </div>

  <div class="field">
    <label>CVC</label>
    <VueStripeCardCvcElement @change="onCvcChange" />
    <span v-if="cvcError" class="error">{{ cvcError }}</span>
  </div>
</template>

Card Brand Detection

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

const cardBrand = ref('unknown')

const brandIcon = computed(() => {
  const icons = {
    visa: 'https://js.stripe.com/v3/fingerprinted/img/visa-365725566f9578a9589553aa9296d178.svg',
    mastercard: 'https://js.stripe.com/v3/fingerprinted/img/mastercard-4d8844094130711885b5e41b28c9848f.svg',
    amex: 'https://js.stripe.com/v3/fingerprinted/img/amex-a49b82f46c5cd6a96a6e418a6ca1717c.svg',
    unknown: null
  }
  return icons[cardBrand.value] || null
})

const onNumberChange = (event) => {
  cardBrand.value = event.brand
}
</script>

<template>
  <div class="card-number-field">
    <VueStripeCardNumberElement @change="onNumberChange" />
    <img v-if="brandIcon" :src="brandIcon" class="brand-icon" />
  </div>
</template>

Payment Submission

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

const { stripe } = useStripe()
const { elements } = useStripeElements()

const processing = ref(false)

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

  processing.value = true

  // Get the card number element
  const cardNumberElement = elements.value.getElement('cardNumber')

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

  processing.value = false

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

TypeScript

ts
import { ref } from 'vue'
import {
  StripeCardNumberElement,
  StripeCardExpiryElement,
  StripeCardCvcElement
} from '@vue-stripe/vue-stripe'
import type {
  StripeCardNumberElement as StripeCardNumberElementType,
  StripeCardNumberElementChangeEvent,
  StripeCardNumberElementOptions
} from '@stripe/stripe-js'

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

// Event handlers
const handleNumberChange = (event: StripeCardNumberElementChangeEvent) => {
  console.log('Brand:', event.brand)
  console.log('Complete:', event.complete)
}

// Template refs
const numberRef = ref<InstanceType<typeof StripeCardNumberElement>>()
const expiryRef = ref<InstanceType<typeof StripeCardExpiryElement>>()
const cvcRef = ref<InstanceType<typeof StripeCardCvcElement>>()

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

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

Comparison: Split vs Unified

FeatureSplit ElementsUnified CardElement
Layout ControlFull controlLimited
Per-field StylingYesShared styles
Per-field ErrorsYesSingle error
AccessibilityBetter (separate labels)Basic
ImplementationMore codeSimpler
Brand DetectionVia change eventVia change event

See Also

Released under the MIT License.