Styling the Card Fields

The Payments Vault card fields are hosted by Vrio, so your stylesheet can't reach them. Pass a styles object when you create each field to make them match the rest of your checkout.

The card fields are hosted by Vrio, which is what keeps the card out of your page — but it also means your stylesheet can't reach the inputs inside them. Instead, you hand the SDK a styles object when you create each field, and it applies those styles on your behalf.

Everything around the input — your labels, error text, spacing, the box the field sits in — is ordinary CSS on your page and works normally. Only the input itself goes through styles.


Applying styles

Pass one styles object per field. Most integrations build a single object and reuse it across all five fields.

const cardStyles = {
  fontFamily: "'Manrope', 'Segoe UI', sans-serif",
  fontSize: '16px',
  fontWeight: '500',
  color: '#0f1e33',
  placeholderColor: '#9ca9bf',
  backgroundColor: '#ffffff',
  borderColor: '#c8d5e8',
  borderWidth: '1px',
  borderRadius: '10px',
  padding: '0 12px',
  outlineColor: '#4f46e5',

  focus:    { borderColor: '#635bff', boxShadow: '0 0 0 3px #635bff29' },
  complete: { color: '#14532d', borderColor: '#2f9e44' },
  error:    { color: '#991b1b', borderColor: '#d73333' },
  disabled: { backgroundColor: '#f1f3f5' },
};

vault.createField('cardNumber', {
  container: document.getElementById('card-number'),
  placeholder: 'Card number',
  styles: cardStyles,
});

Two things to note about the format. Property names are camelCase, not CSS namesfontSize, not font-size. And every value is a string, so fontSize: '16px' works where fontSize: 16 does nothing.

Styles are applied when the field is created and can't be changed afterward. To re-theme — a dark mode toggle, say — destroy the fields and create them again.

📘

A value that isn't understood is quietly ignored

Rather than throwing an error and breaking your checkout, the SDK skips anything it doesn't recognize and applies the rest. That's the safe behavior in production, but it does mean a typo shows up as a style that simply didn't take.

If part of your styling isn't appearing, it's almost always one specific value being skipped rather than something wrong with the whole object. Check it against the accepted values below.


What you can style

GroupProperties
TypefontFamily fontSize fontWeight fontStyle color
PlaceholderplaceholderColor
Spacingpadding margin
BorderborderColor borderWidth borderRadius
BackgroundbackgroundColor
EffectsboxShadow outlineColor
Sizeheight width — see Sizing and layout

Field states

Alongside the base styles, four nested blocks let you style the field in a particular state.

BlockApplies when
focusThe customer is typing in the field
disabledThe field is disabled
completeThe field has content and that content is valid
errorThe field has content and that content isn't valid yet

An empty field is in neither complete nor error, so if you want a required-but-untouched field to look different, style your own container rather than reaching for error.

🚧

Go easy on the error state

A field counts as "error" the moment it has content that isn't yet valid — which includes the first fifteen digits of a sixteen-digit card number. Anything you put on error will flash while the customer is simply still typing.

Subtle cues work well here. Save the red border and the message for when they submit, or for when they leave the field.


Accepted values

Each property accepts a specific set of values. This is deliberately narrower than full CSS — the fields sit in the payment path, so the SDK only passes through values it can be sure about.

Colors — for color, placeholderColor, borderColor, backgroundColor and outlineColor:

  • Hex, in any of the usual lengths: #fff, #ffffff, #ffffffcc
  • rgb(), rgba(), hsl(), hsla()
  • These color names: transparent currentcolor black white red green blue yellow orange purple pink brown gray grey silver gold navy teal lime cyan magenta maroon olive aqua fuchsia indigo violet tan beige ivory khaki salmon coral crimson turquoise

Newer color syntax — oklch(), color-mix(), lab() — isn't supported yet, and neither are names outside that list. royalblue and rebeccapurple are the two that catch people out. Use a hex value instead.

Lengthspx, em, rem, %, vh, vw, ch, pt, or plain 0. calc() isn't supported; work the arithmetic out in JavaScript and pass the result.

  • fontSize takes a single value
  • padding, borderWidth and borderRadius take one to four, like CSS shorthand
  • margin takes one to four and also accepts auto
  • height and width take one value, or auto

fontFamily — up to eight comma-separated families. Any family whose name has a space in it must be quoted: 'Avenir Next' works, Avenir Next doesn't. This is the single most common styling mistake, and because the whole value is skipped, the symptom is fields rendering in the default font.

fontWeight100 through 900, or normal, bold, lighter, bolder.

fontStylenormal, italic, oblique.

boxShadow — one shadow: an optional inset, two to four lengths, then a color. Multiple comma-separated shadows aren't supported.

Any property will also take inherit, initial, unset or revert.

A few limits

  • 256 characters per value
  • 64 properties in total across the base object and all four state blocks — generous for a card field, but worth knowing if you're generating styles programmatically

What you can't do

A handful of things are off the table, mostly because they'd let arbitrary content load into the payment fields.

Web fonts. The fields can only use fonts already available to them, so name your brand font first and follow it with a solid fallback stack. In practice the difference is rarely noticeable at card-field size, but it's worth checking against your design.

Background images and CSS icons. Put a brand icon or card-type logo on your container instead and position it over the field — that's outside the frame, so it's your CSS and works normally.

CSS custom properties. var(--brand-primary) won't resolve inside the field. If your design system runs on tokens, read them out before building the styles object:

const css = getComputedStyle(document.documentElement);

const cardStyles = {
  color:       css.getPropertyValue('--brand-text').trim(),
  borderColor: css.getPropertyValue('--brand-border').trim(),
};

This is the one that surprises teams with a token pipeline, and it's a two-line fix.

Removing Safari's native dropdown appearance. The expiration dropdowns pick up Safari's own gradient and arrow, and there's currently no way to suppress it from outside the field. On Safari they'll look slightly more like OS controls than your text inputs do, which is worth a look before you launch if Safari is a big share of your traffic.


Sizing and layout

📘

Size the field from your container, not from styles

height and width are accepted, but the field's own layout makes the input fill the space it's given, so your container is what actually decides the size. Set the dimensions in your own CSS.

The approach that gives the least trouble is to draw the field yourself: put the border, corner radius, background and height on your container, and make the input transparent inside it.

.card-field {
  height: 48px;
  padding: 0 12px;
  border: 1px solid #c8d5e8;
  border-radius: 10px;
  background: #fff;
}
const cardStyles = {
  backgroundColor: 'transparent',
  borderWidth: '0',
  outlineColor: 'transparent',
  fontSize: '16px',
  color: '#0f1e33',
};

Now the box is entirely yours — you control the geometry exactly, your focus and error states are plain CSS, and you're not trying to line your border up with one drawn inside a frame you can't see. Setting outlineColor: 'transparent' suppresses the browser's own focus ring so yours is the only one visible.

Floating labels

If your checkout floats the label into the field, drive it from the SDK's callbacks rather than watching the input: raise the label on onFocus, lower it on onBlur when the field is still empty, and use onChange to track whether there's content.

Dropdowns behave differently from text inputs

Browsers don't agree on how a <select> positions its text. Chrome respects vertical padding on one; Safari largely ignores it. So a padding value that centers your expiration dropdowns in one browser will look off in the other.

Use margin instead of padding to nudge dropdown text vertically. Margin moves the whole element, and both browsers treat that the same way.


Related Documentation


Did this page help you?