Skip to content

Appearance

Presets, colours, design tokens and the rules the SDK silently drops.

One appearance object styles everything: the hosted checkout, the overlay, and every payment field component. It has three layers, and they apply in this order.

  1. A theme preset, if you name one.
  2. The flat keys, primaryColor and its neighbours.
  3. variables and rules, for token level and selector level control.

Each layer wins over the one above it, so a preset is a starting point rather than a constraint.

All three layers at once
await mount('#checkout', {
  sessionId,
  appearance: {
    theme: 'stripe',
    primaryColor: '#6366f1',
    colorScheme: 'auto',
    labels: 'floating',
    variables: { colorText: '#0f172a', fontSizeBase: '16px' },
    rules: { '.Input': { borderRadius: '8px' } },
  },
});

Presets

theme layers a set of flat keys under yours. These are the exact values.

PresetWhat it sets
stripeprimaryColor: #0570de, borderRadius: 4px, the system font stack, shadow: low
nightcolorScheme: dark, primaryColor: #7c3aed, backgroundColor: #0f172a, buttonTextColor: #f8fafc, inputBackgroundColor: #1e293b, inputBorderColor: #334155, textMutedColor: #94a3b8, borderRadius: 8px, shadow: none
flatshadow: none, borderRadius: 0
noneNothing. Only your own keys apply

stripe is the default look of a Wajub checkout, so naming it changes nothing unless you also override part of it.

Flat keys

The shorthand layer. Every value is a plain CSS string.

KeyTypeWhat it paints
primaryColorCSS colorThe accent, primary buttons, links
secondaryColorCSS colorSecondary accents
backgroundColorCSS colorThe surface behind the form
buttonTextColorCSS colorText on primary buttons
inputBackgroundColorCSS colorField background
inputBorderColorCSS colorField border
textMutedColorCSS colorHelper and secondary text
successColorCSS colorSuccess states and badges
errorColorCSS colorErrors and validation messages
fontFamilyCSS font stackThe whole checkout
borderRadiusCSS lengthDefault corner radius
shadowlow or noneElevation
colorSchemelight, dark, autoauto follows the payer's system setting
labelsabove or floatingWhere field labels sit
disableAnimationsbooleanRemoves transitions and motion

The deprecated theme object

theme: { primaryColor, fontFamily, borderRadius } on the mount config is the old shape. The SDK still fills it from appearance for backward compatibility, so you never set it yourself. Note that appearance.theme is a different thing, a preset name.

Variables

The token layer. Each key maps to a CSS custom property on the checkout's root element, so it reaches every component that reads that token rather than one surface.

KeyCSS custom property
colorPrimary--wj-color-primary-500
colorBackground--wj-color-bg-surface
colorText--wj-color-text-primary
colorTextSecondary--wj-color-text-secondary
colorTextPlaceholder--wj-color-text-tertiary
colorDanger--wj-color-text-danger
colorSuccess--wj-color-text-success
fontFamily--wj-font-family-sans
fontSizeBase--wj-font-size-base
fontWeightNormal--wj-font-weight-normal
fontWeightMedium--wj-font-weight-medium
fontWeightBold--wj-font-weight-bold
spacingUnit--wj-spacing-unit
borderRadius--wj-radius
spacingGridRow--wj-spacing-grid-row
spacingGridColumn--wj-spacing-grid-column

Sixteen keys, and nothing else. A seventeenth is dropped on the way out.

Typography in one place
appearance: {
  variables: {
    fontFamily: 'Inter, system-ui, sans-serif',
    fontSizeBase: '15px',
    fontWeightMedium: '600',
    spacingUnit: '4px',
  },
}

When a flat key and its token twin are both set, the flat key wins. Pick one and stay on it.

Rules

The selector layer, and the narrowest one. It exists to reshape the form fields, not to restyle the checkout.

Selectors. Only .Input and .Label, each with at most one of :hover, :focus, :focus-within, :disabled, ::placeholder, ::selection.

Everything you can target
appearance: {
  rules: {
    '.Input': { borderRadius: '8px', borderColor: '#cbd5e1', padding: '12px' },
    '.Input:focus': { borderColor: '#6366f1', outline: '2px solid #c7d2fe' },
    '.Input::placeholder': { color: '#94a3b8' },
    '.Label': { fontWeight: '500', fontSize: '14px', letterSpacing: '0.01em' },
  },
}

Properties. Thirty two, in camelCase, converted to kebab-case on arrival.

GroupAllowed
Colourcolor, backgroundColor, opacity
Borderborder, borderColor, borderWidth, borderStyle, borderRadius
Boxpadding and its four sides, margin and its four sides
TypefontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textAlign, textDecoration
EffectboxShadow, outline, outlineColor, outlineWidth, outlineOffset

Values. Non empty strings only. A value containing url(, expression(, @import, javascript:, < or > is refused, which rules out background images and font imports.

For styling beyond that, use custom_css on your account branding, which injects a real stylesheet. See Branding and theming.

Changing it after mount

Both instance types take update(), and they do not behave the same.

A dark mode toggle on your own page
let checkout;

await mount('#checkout', { sessionId, onReady: (instance) => (checkout = instance) });

darkToggle.onchange = (event) =>
  checkout.update({ appearance: { colorScheme: event.target.checked ? 'dark' : 'light' } });
CallHosted checkoutPayment field
update({ appearance })Applied liveApplied live
update({ locale })Applied liveApplied live
update({ currency })Applied live, multi currency sessionsNot applicable
update({ layout })Stored locally, nothing changesApplied live

To change the hosted checkout's layout after mount, remount it, or set the layout on the session.

In React, Vue and Svelte, the field components watch appearance, locale and layout and call update() themselves. CheckoutEmbed does not. Keep the instance from onReady and drive it.

Locale

The locale reaches the checkout as a query parameter, and the checkout decides what it understands.

At mount, and afterwards
await mount('#checkout', { sessionId, locale: 'fr' });
checkout.update({ locale: 'en' });

Supported: fr, en, es, pt, ar. Arabic renders right to left. Omit locale and the session default applies, which comes from your account branding or the customer's recorded preference.

Layout

Four ways to arrange the payment methods.

ValueWhat the payer sees
classicA vertical list, the default
compactThe same list, denser
tabsOne tab per category
accordionCollapsible sections
Set it at mount
await mount('#checkout', { sessionId, layout: 'tabs' });

Omit it and the session default applies. This is not the same thing as the layout field on account branding, which picks the design of the hosted page itself.

What travels, and how

Appearance is not injected into the iframe. It is encoded into the iframe URL before it loads, which is why it is filtered and why it is fixed at mount time for anything update() does not cover.

LayerOn the URL
Presettheme_preset=
Flat keystheme_primary=, theme_bg=, theme_radius=, and so on
colorScheme, labelscolor_scheme=, labels=
disableAnimationsdisable_animations=1
variablesOne var_<key>= per token
rulesrules_b64=, the JSON in base64url

Inside the checkout, colorScheme sets data-theme on the root element, auto removes it so the system preference decides, and rules become a single injected stylesheet.

Server branding, or client appearance

They are not alternatives. Branding is what your checkout looks like by default, everywhere. appearance is what this one embed looks like today.

GoalWhere
Your logo and colours on every checkoutDashboard branding
A dark mode toggle on your siteappearance.colorScheme and update()
One campaign, one pageappearance on that mount
A font or a background imagecustom_css on branding, rules cannot

Client appearance merges on top of session branding inside the iframe, so you only override what you name. The server side field list is on Branding and theming.

What did you think of this content?