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.
- A
themepreset, if you name one. - The flat keys,
primaryColorand its neighbours. variablesandrules, 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.
await mount('#checkout', {
sessionId,
appearance: {
theme: 'flat',
primaryColor: '#6366f1',
colorScheme: 'auto',
labels: 'floating',
variables: { colorText: '#0f172a', fontSizeBase: '16px' },
rules: { '.Input': { borderRadius: '8px' } },
},
});Anything the SDK does not recognise has no effect
An unknown key, a selector outside the short list below, or a property outside the allowed list is ignored. Nothing throws and nothing appears in the console, so a typo simply has no effect.
Presets
theme layers a set of flat keys under yours. These are the exact values.
| Preset | What it sets |
|---|---|
stripe | primaryColor: #0570de, borderRadius: 4px, the system font stack, shadow: low |
night | colorScheme: dark, primaryColor: #7c3aed, backgroundColor: #0f172a, buttonTextColor: #f8fafc, inputBackgroundColor: #1e293b, inputBorderColor: #334155, textMutedColor: #94a3b8, borderRadius: 8px, shadow: none |
flat | shadow: none, borderRadius: 0 |
none | Nothing. 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.
| Key | Type | What it paints |
|---|---|---|
primaryColor | CSS color | The accent, primary buttons, links |
secondaryColor | CSS color | Secondary accents |
backgroundColor | CSS color | The surface behind the form |
buttonTextColor | CSS color | Text on primary buttons |
inputBackgroundColor | CSS color | The fields' own background. The page around them keeps its own |
inputBorderColor | CSS color | The fields' own border. The page's other borders keep theirs |
textMutedColor | CSS color | Helper and secondary text |
successColor | CSS color | Success states and badges |
errorColor | CSS color | Errors and validation messages |
fontFamily | CSS font stack | The whole checkout |
borderRadius | CSS length | Default corner radius |
shadow | low or none | Elevation |
colorScheme | light, dark, auto | light 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 |
labels | above or floating | Where field labels sit |
disableAnimations | boolean | Removes 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.
| Key | What it sets |
|---|---|
colorPrimary | The accent colour |
colorBackground | The surface behind the form |
colorText | Main text, in the fields too |
colorTextSecondary | Secondary text, the field labels included |
colorTextPlaceholder | Placeholder text |
colorDanger | Error text |
colorSuccess | Success text |
fontFamily | The font stack. A Google Font such as Poppins or DM Sans is loaded for you |
fontSizeBase | The size of the text typed in the fields, at least 16px on a phone or a tablet. Labels and hints are a step smaller |
fontWeightNormal | Regular text weight |
fontWeightMedium | Medium text weight |
fontWeightBold | Bold text weight |
spacingUnit | The base spacing unit |
borderRadius | Corner radius |
spacingGridRow | The gap between rows of fields |
spacingGridColumn | The gap between columns of fields |
Sixteen keys, and nothing else. Any other key has no effect.
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.
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.
| Group | Allowed |
|---|---|
| Colour | color, backgroundColor, opacity |
| Border | border, borderColor, borderWidth, borderStyle, borderRadius |
| Box | padding and its four sides, margin and its four sides |
| Type | fontFamily, fontSize, fontWeight, fontStyle, lineHeight, letterSpacing, textAlign, textDecoration |
| Effect | boxShadow, 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.
A rule that does not fit is not an error
.Tab, .AccordionHeader, display, position and a background image all pass through
appearance without complaint and without effect. If a rule does nothing, check it against the
three lists above before checking anything else.
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.
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.
| Element | What to name | What it gives |
|---|---|---|
input | A text field of your form | variables.fontFamily, fontSizeBase, colorText, colorTextPlaceholder and borderRadius, inputBackgroundColor, inputBorderColor |
label | That field's label | variables.colorTextSecondary |
button | Your form's submit button | variables.colorPrimary |
background | The element behind the checkout | colorScheme, variables.colorBackground |
- Your keys win. Every key you set, and every key your
themepreset sets, wins over what is taken from the page. - The scheme follows your background, light or dark, unless you set
colorSchemeor a dark preset such asnight. - What cannot be used is left out, such as a colour that would not read on the background.
inheritnever throws. - It is read at mount, and again on each
update({ appearance }). The overlay fromopen()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?).
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().
let checkout;
await mount('#checkout', { sessionId, onReady: (instance) => (checkout = instance) });
darkToggle.onchange = (event) =>
checkout.update({ appearance: { colorScheme: event.target.checked ? 'dark' : 'light' } });| Call | Hosted checkout | Payment field |
|---|---|---|
update({ appearance }) | Applied live | Applied live |
update({ locale }) | Applied live | Applied live |
update({ currency }) | Applied live, multi currency sessions | Not applicable |
update({ layout }) | Applied live | Applied 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.
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 brandingWith 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.
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.
| Value | What the payer sees |
|---|---|
classic | A vertical list, the default |
compact | The same list, denser |
tabs | One tab per category |
accordion | Collapsible sections |
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.
| Goal | Where |
|---|---|
| Your logo and colours on every checkout | Dashboard branding |
| A dark mode toggle on your site | appearance.colorScheme and update() |
| One campaign, one page | appearance on that mount |
| A Google Font, such as Poppins or DM Sans | appearance.variables.fontFamily |
| Your own web font, or a background image | Not 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.