Payment Card Details
Follow this pattern when you need to collect credit or debit card details to take a payment.
When to use this pattern
Use this pattern when you need to collect payment card details from users. Present all fields on a single page, in the order in which they appear on a credit or debit card. This arrangement helps users transcribe information directly from their payment card.
Interactive example
Try entering a card number to see the card type detection in action:
How it works
Field order
Present fields in the order they appear on a physical card:
- Card number — the long number on the front
- Expiry date — usually below the card number
- Name on card — the cardholder's name
- Card security code — on the back (or front for Amex)
Accepted cards
Show which cards you accept using recognisable logos. When the user starts entering their card number, identify the card type and highlight only that logo while greying out others.
Accepted cards:
<div class="payment-card-icons">
<img src="/icons/payment/visa.svg" alt="Visa">
<img src="/icons/payment/mastercard.svg" alt="Mastercard">
<img src="/icons/payment/american-express.svg" alt="American Express">
<img src="/icons/payment/maestro.svg" alt="Maestro">
</div>Card number formatting
Allow users to enter payment card numbers in whatever format is familiar to them. Allow additional spaces, hyphens and dashes. Format the display as the user types for easier reading.
function formatCardNumber(value) {
// Remove all non-digits
const digits = value.replace(/\D/g, '');
// Add space every 4 digits
const formatted = digits.match(/.{1,4}/g)?.join(' ') || digits;
return formatted.slice(0, 19); // Max 16 digits + 3 spaces
}
// Example: 4111111111111111 → 4111 1111 1111 1111Card type detection
Use the first digits of the card number (IIN/BIN) to detect the card type and provide appropriate validation:
| Card type | Starts with | Length | Security code |
|---|---|---|---|
| Visa | 4 | 16 digits | 3 digits (back) |
| Mastercard | 51-55, 2221-2720 | 16 digits | 3 digits (back) |
| American Express | 34, 37 | 15 digits | 4 digits (front) |
| Maestro | 5018, 5020, 5038, etc. | 12-19 digits | 3 digits (back) |
Security code hint
Do not use acronyms like "CVV" or "CVC". Instead, describe where to find the code in plain language. Update the hint based on detected card type:
- Most cards: "The last 3 digits on the back of your card"
- American Express: "The 4 digits on the front of your card"
Available payment icons
Error messages
Use clear, specific error messages:
| Scenario | Error message |
|---|---|
| Card number is empty | "Enter your card number" |
| Card number is invalid | "Enter a valid card number" |
| Card type not accepted | "We do not accept this type of card. Try another card." |
| Expiry date is empty | "Enter the expiry date" |
| Expiry date format wrong | "Enter the expiry date in the format MM/YY" |
| Card has expired | "Card has expired. Check the expiry date or try another card." |
| Name on card is empty | "Enter the name on your card" |
| Security code is empty | "Enter the card security code" |
| Security code wrong length | "The security code should be 3 digits" (or 4 for Amex) |
| Payment declined | "Your payment was declined. Check your card details or try a different card." |
Security
Never store full card details. Use a PCI DSS compliant payment provider (like Stripe, Adyen, or PayPal) to handle card processing securely.
- Use a PCI DSS compliant payment provider
- Use HTTPS on all payment pages
- Never log or store full card numbers
- Show only last 4 digits in confirmations
- Consider using payment provider's hosted fields or iframes
Autocomplete attributes
Use correct autocomplete values for better browser autofill:
| Field | Autocomplete value |
|---|---|
| Card number | cc-number |
| Expiry date (combined) | cc-exp |
| Expiry month | cc-exp-month |
| Expiry year | cc-exp-year |
| Security code | cc-csc |
| Name on card | cc-name |
| Card type | cc-type |
Accessibility
- Use
inputmode="numeric"for number fields to show numeric keyboard on mobile - Provide clear labels and hint text
- Do not disable paste functionality — users may use password managers
- Allow sufficient time to complete payment (session timeouts should be generous)
- Make error messages clear and specific
- Do not assume the cardholder's name matches any other name in the service
HTML structure
<fieldset class="form-group">
<legend class="form-label form-label-lg">Card details</legend>
<!-- Accepted cards -->
<div class="payment-card-icons">
<img src="/icons/payment/visa.svg" alt="Visa">
<img src="/icons/payment/mastercard.svg" alt="Mastercard">
</div>
<!-- Card number - full width -->
<div class="form-group">
<label class="form-label" for="card-number">Card number</label>
<input class="form-input" id="card-number" type="text"
inputmode="numeric" autocomplete="cc-number">
</div>
<!-- Expiry date and Security code - side by side -->
<div class="form-row form-row-1-1">
<div class="form-group">
<label class="form-label" for="card-expiry">Expiry date</label>
<span class="form-hint">For example, 03/26</span>
<input class="form-input" id="card-expiry" type="text"
inputmode="numeric" autocomplete="cc-exp" maxlength="5">
</div>
<div class="form-group">
<label class="form-label" for="card-cvc">Card security code</label>
<span class="form-hint">The last 3 digits on the back of your card</span>
<input class="form-input" id="card-cvc" type="text"
inputmode="numeric" autocomplete="cc-csc" maxlength="4">
</div>
</div>
<!-- Name on card - full width -->
<div class="form-group">
<label class="form-label" for="card-name">Name on card</label>
<input class="form-input" id="card-name" type="text"
autocomplete="cc-name">
</div>
</fieldset>