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.
- 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.
await mount('#checkout', {
sessionId,
appearance: {
theme: 'stripe',
primaryColor: '#6366f1',
colorScheme: 'auto',
labels: 'floating',
variables: { colorText: '#0f172a', fontSizeBase: '16px' },
rules: { '.Input': { borderRadius: '8px' } },
},
});Anything the SDK does not recognise is dropped without a word
The object is filtered before it leaves the browser. An unknown key, a selector outside the short list below, a property outside the whitelist, all disappear silently. 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.
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 | Field background |
inputBorderColor | CSS color | Field border |
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 | auto follows the payer's system setting |
labels | above or floating | Where field labels sit |
disableAnimations | boolean | Removes 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.
| Key | CSS 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.
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.
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.
| 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 only. A value containing url(, expression(, @import,
javascript:, < or > is refused, which rules out background images and font imports.
A rule that does not fit is not an error
.Tab, .AccordionHeader, display, position and a url() background 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.
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.
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 }) | Stored locally, nothing changes | Applied 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.
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.
| 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 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.
| Layer | On the URL |
|---|---|
| Preset | theme_preset= |
| Flat keys | theme_primary=, theme_bg=, theme_radius=, and so on |
colorScheme, labels | color_scheme=, labels= |
disableAnimations | disable_animations=1 |
variables | One var_<key>= per token |
rules | rules_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.
| 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 font or a background image | custom_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.