Skip to content

Appearance

Presets, colours, design tokens, field rules, locale and layout.

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. inherit can put your own page's look under all three: see Taking your page's look.

All three layers at once
await mount('#checkout', {
  sessionId,
  appearance: {
    theme: 'flat',
    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.

light, dark, system and auto are not presets. theme set to one of them, with no colorScheme, sets the colour scheme, system meaning auto.

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 colorThe fields' own background. The page around them keeps its own
inputBorderColorCSS colorThe fields' own border. The page's other borders keep theirs
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, autolight or dark wins over the payer's device and the choice they saved on a hosted page. auto follows the device. Once you set it, the payer no longer sees a light and dark switch
labelsabove or floatingWhere field labels sit
disableAnimationsbooleanRemoves transitions and motion

Variables

The token layer. Each key sets one design token, so it reaches every component that uses that token rather than one surface.

KeyWhat it sets
colorPrimaryThe accent colour
colorBackgroundThe surface behind the form
colorTextMain text, in the fields too
colorTextSecondarySecondary text, the field labels included
colorTextPlaceholderPlaceholder text
colorDangerError text
colorSuccessSuccess text
fontFamilyThe font stack. A Google Font such as Poppins or DM Sans is loaded for you
fontSizeBaseThe size of the text typed in the fields, at least 16px on a phone or a tablet. Labels and hints are a step smaller
fontWeightNormalRegular text weight
fontWeightMediumMedium text weight
fontWeightBoldBold text weight
spacingUnitThe base spacing unit
borderRadiusCorner radius
spacingGridRowThe gap between rows of fields
spacingGridColumnThe gap between columns of fields

Sixteen keys, and nothing else. Any other key has no effect.

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. .Input is a field's box, its border, background and corners, focused while its input is and disabled with it. .Label is a field's label. The color and the font of a .Input rule also apply to the text typed in the field.

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 one, in camelCase.

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 of 256 characters at most. A value that would load an external resource or break out of its CSS declaration is refused, so background images and font imports are not possible. The same limits apply to flat keys and variables.

Taking your page's look

appearance.inherit gives a payment field or an embedded checkout the look of your own form: its font, its text and field colours, its rounding, its accent and its background.

Found near the container, or named
factory.create('card', { appearance: { inherit: true, labels: 'floating' } }).mount('#card-fields');

factory.create('mobileMoney', {
  appearance: {
    inherit: {
      input: '#billing_email',
      label: 'label[for=billing_email]',
      button: '#place-order',
      background: '.checkout-box',
    },
  },
});

inherit: true looks for your form near the container. When it picks the wrong elements, name them in an InheritSource, as CSS selectors or as the elements themselves. Any you leave out are still looked for.

ElementWhat to nameWhat it gives
inputA text field of your formvariables.fontFamily, fontSizeBase, colorText, colorTextPlaceholder and borderRadius, inputBackgroundColor, inputBorderColor
labelThat field's labelvariables.colorTextSecondary
buttonYour form's submit buttonvariables.colorPrimary
backgroundThe element behind the checkoutcolorScheme, variables.colorBackground
  • Your keys win. Every key you set, and every key your theme preset sets, wins over what is taken from the page.
  • The scheme follows your background, light or dark, unless you set colorScheme or a dark preset such as night.
  • What cannot be used is left out, such as a colour that would not read on the background. inherit never throws.
  • It is read at mount, and again on each update({ appearance }). The overlay from open() has no container, so it looks across the whole page.

To see what would be taken, or to adjust it before passing it yourself, call wajub.appearanceFrom(container, source?).

Read, adjust, pass
const look = wajub.appearanceFrom('#card-fields'); // {} when there is nothing to read

factory
  .create('card', { appearance: { ...look, variables: { ...look.variables, fontSizeBase: '16px' } } })
  .mount('#card-fields');

appearanceFrom() returns {} when there is nothing to read, and never throws. The Wajub() client has it too, and @wajub/js exports it as an async helper that resolves to null on the server.

Changing it after mount

What you pass at mount is fixed when the checkout loads. update() changes what the table below covers, and anything else needs a remount. Both instance types take update().

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 })Applied liveApplied live

update({ appearance }) merges what you give into what the checkout or the field already has. The keys you give change, and the others stay. A key given as undefined or null is cleared. appearance: undefined or null clears the whole of it: the checkout or the field goes back to your Dashboard branding, and its next mount() starts without it.

Change one key, clear one, clear all
card.update({ appearance: { borderRadius: '16px' } }); // the other keys stay
card.update({ appearance: { primaryColor: undefined } }); // clears this key
card.update({ appearance: undefined }); // back to your Dashboard branding

With inherit, each update({ appearance }) reads the page again, and clearing inherit also clears what it had read. update({ appearance: {} }) changes no key.

A new theme preset replaces the previous one and everything it brought: update({ appearance: { theme: 'flat' } }) after night keeps none of the colours of night.

In React, Vue, Svelte and Angular, change the props (the inputs, in Angular) rather than calling update(). A field component applies a new appearance, locale, layout or frameTitle live, and the checkout embed does the same for appearance, locale and layout. The appearance prop replaces the whole appearance: a key you remove from it is cleared, and an appearance that becomes undefined or {} clears all of it.

Locale

locale at mount is the language the checkout or the field opens in. It wins over the language the payer chose on an earlier visit and over the session default, and a code outside the list below is ignored. update({ locale }) changes it afterwards, and the payer can still switch language inside the hosted checkout.

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 checkout opens in the language the payer last chose in this browser, or else in the session default, 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 page design chosen in Settings → Branding, which picks the design of the hosted page itself.

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.

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 Google Font, such as Poppins or DM Sansappearance.variables.fontFamily
Your own web font, or a background imageNot available

Client appearance applies on top of your Dashboard branding, so you only override what you name. What you can set in the Dashboard is on Branding and theming.

What did you think of this content?