Customize Universal Checkout


Primer Universal Checkout look and feel can be styled to suit your preferred branding and make it blend seamlessly in your website or mobile app.

This guide is about customizations available for Universal Checkout such as styling fonts, transitions, loading indicator, vault, success screen, styling errors, and spacing. Besides these, this guide also covers other customizations including how to add your own custom submit button, display custom Pay by Card button and show custom errors.

Styling Universal Checkout

💡

For styling Universal Checkout, do not use custom CSS. Rather pass the style object as a checkout option when calling showUniversalCheckout.

You can use the general styling options to style specific UI elements such as form inputs, text labels and several other elements within Universal Checkout.

1234567
const options = {  /* Other checkout options ... */   style: {    /* Style options */  },};
typescript
copy

Text Styling Options

Text elements within Universal Checkout can be styled using the following interface:

12345678910
interface TextStyle {  color?: string;  fontFamily?: string;  fontWeight?: string;  fontSize?: string;  fontSmoothing?: string;  lineHeight?: string;  textTransform?: string;  letterSpacing?: string;}
typescript
copy

Block Styling Options

Block elements within Universal Checkout can be styled using the following interface:

12345678
interface BlockStyle {  background?: string;  borderRadius?: number | string;  boxShadow?: string;  borderStyle?: string;  borderColor?: number | string;  borderWidth?: number | string;}
typescript
copy
💡

Both of the above mentioned interfaces will be referred to regularly in this guide when covering the styling of specific UI elements.

Styling Inputs

Styling Font Family (Card Input)

Universal Checkout style inherits from the style of its container as most of it is rendered directly within your page and not within an iframe.

So any font-family that you have set in the CSS file will be used by the Universal Checkout. The example below uses MyFont as the preferred font family for card input.

123
#my-checkout {  font-family: 'MyFont', sans-serif;}
css
copy

However, in order to be fully PCI-compliant, the card code field, the card expiry field and the CVV field are rendered in isolated iframe. This means that font-family declarations, and @font-face directives that load and define fonts are not passed to these specific fields.

To forward font-family and @font-face to the card input fields, you can set style.input.base.fontFamily and style.fontFaces.

Alternatively, to style.fontFaces, you can forward stylesheets to the iframe using the style.stylesheets option, which facilitates the integration of fonts coming from services such as Google Fonts.

1234567891011121314151617181920212223242526
const options = {  /* Other options ... */  style: {    /* Forwarded to iframes */    fontFaces: [      {        fontFamily: "Open Sans",        src: 'url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff")',      },    ],     /* Forwarded to iframes */    stylesheets: [      {        href: "<https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap>",      },    ],     /* Unified across all inputs: inside and outside iframes */    input: {      base: {        fontFamily: "Open Sans",      },    },  },};
typescript
copy

InputStyle Options

You can style all the inputs on Universal Checkout in all their different states.

By default, the InputStyle defined in input:base will be used everywhere. You need to add value for InputStyle only if you wish to override this style for inputLabel, hover, placeholder etc.

12345678910111213141516171819
interface InputStyle {  height?: number;  paddingHorizontal?: number;   background?: string;  borderRadius?: number | string;  boxShadow?: string;   borderStyle?: string;  borderColor?: string;  borderWidth?: number | string;   color?: string;  fontFamily?: string;  fontWeight?: string;  fontSize?: string;  fontSmoothing?: string;  lineHeight?: string;}
typescript
copy
12345678910111213141516
interface CheckoutStyle {  // Input styling  input?: {    // Base style    base?: InputStyle & {      hover?: InputStyle, // :hover      focus?: InputStyle, // :focus      placeholder?: InputStyle, //  ::placeholder      webkitAutofill?: InputStyle, // :-webkit-autofill      selection?: InputStyle, // ::selection    },     // Style of the label displayed above the input fields    inputLabel?: TextStyle,  };}
typescript
copy

Example

Styling Example

12345678910111213141516171819202122232425
const options = {  /* Other options ... */  style: {    inputLabel: {      fontFamily: "courier new",      color: "#000000",    },    input: {      base: {        borderStyle: "none none solid none",        borderColor: "#000000",        boxShadow: "0px",        background: "#ffffff",        fontFamily: "courier new",        borderRadius: "0px",        paddingHorizontal: "0px",        lineHeight: "",        color: "#000000",        placeholder: {          color: "#e1deda",        },      },    },  },};
typescript
copy
💡

You can hide the default input labels using the inputLabelsVisible option.

123456
const options = {  /* Other options ... */  form: {    inputLabelsVisible: false,  },};
typescript
copy

Styling Input Errors

You can customize the style of Universal Checkout error state.

By default, the InputStyle defined in input.base will be used everywhere. But you can override this where you need different error message styling by specifying the style for input.error and inputErrorText. inputErrorText also uses TextAlignmentStyle.

123
interface TextAlignmentStyle {  textAlign?: string;}
typescript
copy
123456789101112131415161718
const options = {  /* Other options ... */  style: {    input: {      error:        InputStyle &        {          hover: InputStyle,          focus: InputStyle,          placeholder: InputStyle,          webkitAutofill: InputStyle,          selection: InputStyle,        },    },    // Style of the error messages displayed below the input fields    inputErrorText: TextStyle & TextAlignmentStyle,  },};
typescript
copy

Example

Error Input Styling Example

12345678910111213141516171819202122232425262728293031323334
const options = {  /* Other options ... */  style: {    inputErrorText: {      fontFamily: "courier new",      textAlign: "left",      color: "#e70909",    },    inputLabel: {      fontFamily: "courier new",      color: "#000000",    },    input: {      base: {        borderStyle: "none none solid none",        borderColor: "#000000",        boxShadow: "0px",        background: "#ffffff",        fontFamily: "courier new",        borderRadius: "0px",        paddingHorizontal: "0px",        lineHeight: "",        color: "#000000",        placeholder: {          color: "#e1deda",        },        error: {          borderStyle: "solid",          borderColor: "#d0021b",        },      },    },  },};
typescript
copy

Styling Error Messages

You can also style the error message shown in the event of an unsuccessful payment using ErrorMessageStyle interface which extends Block Styling Options and Text Styling Options mentioned above.

123456789101112
// Error Message Interfaceinterface ErrorMessageStyle extends BlockStyle, TextStyle {  color?: string;} // Exampleconst options = {  /* Other options ... */  style: {    errorMessage: ErrorMessageStyle,  },};
typescript
copy
💡

Universal Checkout can display error messages, or you can create your own. Refer to Configuring Error Message Guide for details.

Styling Form Spacing

You can change the vertical spacing between inputs or the spacing between labels and inputs by using the formSpacings properties.

123456789101112131415
// Form Spacings Interfaceinterface FormSpacings {  betweenLabelAndInput?: string;  betweenInputs?: string;} // Exampleconst options = {  style: {    formSpacings: {      betweenLabelAndInput: "0px",      betweenInputs: "10px",    },  },};
typescript
copy

Styling Submit Button

The default submit button in Universal Checkout can be styled to match your brand. The submitButton uses TextStyle and BlockStyle to define the style of the button.

By default, the style defined in submitButton.base will be used everywhere. If you wish to override the default style for loading, disabled, hover or focus or other states, you will be required to add values for TextStyle and BlockStyle properties.

123456789101112131415161718192021
const options = {  /* Other options ... */  style: {    submitButton: {      base: {        hover: TextStyle & BlockStyle,        focus: TextStyle & BlockStyle,      },       disabled: {        hover: TextStyle & BlockStyle,        focus: TextStyle & BlockStyle,      },       loading: {        hover: TextStyle & BlockStyle,        focus: TextStyle & BlockStyle,      },    },  },};
typescript
copy

Example

Submit Button Styling Example

12345678910111213141516171819
const options = {  /* Other options ... */  style: {    submitButton: {      base: {        color: "#ffffff",        background: "#000000",        borderRadius: "3px",        fontFamily: "courier new",        fontWeight: "bold",        boxShadow: "none",      },      disabled: {        color: "#9b9b9b",        background: "#e1deda",      },    },  },};
typescript
copy
💡

You can also use custom submit button instead of styling the Universal Checkout default submit button.

Styling Back Button

Some scenes in Universal Checkout will have a back button which allows the user to navigate back to the previous scene. You can style the back button as shown below:

12345678
const options = {  /* Other options ... */  style: {    backButton: {      color: string,    },  },};
typescript
copy

Styling Transitions

When multiple scenes are present in Universal Checkout, you can override the default transitions to better match your website transitions.

123456789101112131415161718
type SceneOptions = {  // A callback for receiving the current scene that is entering  onEntering?: (sceneId: string) => void,   // Specify options relating to scene transitions  // Setting to false disables all scene transitions  // Defaults to a 'SLIDE_UP' type transition with a duration of 700ms  transition?: SceneTransitionOptions | false,}; type SceneTransitionOptions = {  // The type of transition animation which will be used  type: TransitionType,  // The duration of the transition animation  duration: number,}; type TransitionType = "SLIDE_UP" | "SLIDE_DOWN" | "SLIDE_HORIZONTAL";
typescript
copy
12345678910111213141516171819202122
const handleOnSceneEntering = (sceneId: string) => {  // perform action based on current scene being entered}; const options = {  /* Other options ... */   // Example 1 - scene with custom transitions enabled  scene: {    onEntering: handleOnSceneEntering,    transition: {      type: "SLIDE_DOWN",      duration: 650, // duration in milliseconds    },  },   // Example 2 - scene with all transitions disabled  scene: {    onEntering: handleOnSceneEntering,    transition: false,  },};
typescript
copy

Styling Loading indicator

While Universal Checkout loads, a loading screen indicator is displayed to the customers. You can customize the color of the loading screen indicator as shown below:

123456789
const options = {  /* Other options ... */  style: {    loadingScreen: {      // Color of the loading screen indicator      color: string,    },  },};
typescript
copy

Styling Vault

By default, any previously vaulted payment methods are shown to customers in Universal Checkout. This happens only when the customerId is included in the client session.

12345678910111213
const options = {  /* Other options ... */  vault: {    // Fetch the vaulted payment methods for the customerId    //of the current session and display them    // Default to true    visible: boolean,     // Disable the option to delete a saved payment method.    // Default to false    deletionDisabled: boolean,  },};
typescript
copy

Styling Vault Display

The vault display in Universal Checkout can be styled if you wish to change the defaults. showMorePaymentMethodsButton uses TextStyle to define the style of the button. By default, the style defined in showMorePaymentMethodsButton.base will be used everywhere. Add values for TextStyle only if you wish to override this style for the disabled state.

123456789
const options = {  /* Other options ... */  style: {    showMorePaymentMethodsButton: {      base: TextStyle,      disabled: TextStyle,    },  },};
typescript
copy

You also have the option to style the edit icons and menu. The actionButton uses TextStyle to define the style of the button. The confirmButton uses both TextStyle and BlockStyle interfaces.

123456789101112131415161718192021
// Pop-up menu to manage the vaulted payment methodsconst options = {  /* Other options ... */  // Pop-up menu to manage the vaulted payment methods  vaultMenu: {    // Pencil icon    editButton: {      // Backround of the pencil icon      background: string,      // Color of the pencil icon      color: string,    },     item: {      label: textStyle,      // Delete & Cancel button      actionButton: TextStyle,      confirmButton: TextStyle & BlockStyle,    },  },};
typescript
copy

Displaying Custom Errors

💡

Universal Checkout can display error messages, or you can create your own. Refer to the Configuring Error Message Guide for details.

In case of an error, Universal Checkout presents a default message to the customers. This error message can be disabled if you wish to display a custom error message.

1234567891011121314151617181920212223
const options = {  /* Other options ... */  style: {    errorMessage: {      // Disable the appearance of the      //default error message      // Default to false      disabled: boolean,       // A callback for when the error message      //should be displayed      onErrorMessageShow(message: string) {        // Choose to use provided message for own purposes      },       // A callback for when the error message      //should be hidden      onErrorMessageHide() {        // Update own UI accordingly      },    },  },};
typescript
copy

Displaying Pay by Card Button

By default, the card form is automatically displayed on the first scene of Universal Checkout.

You can replace it and display a 'Pay by Card' button instead of the form. Clicking on the button will show the card form on another scene.

Pay by Card

123456789
type CardPreferredFlow = "DEDICATED_SCENE" | "EMBEDDED_IN_HOME"; // ExamplePrimer.showUniversalCheckout(clientToken, {  // ...  card: {    preferredFlow: "DEDICATED_SCENE", // Show the card form on a separate scene  },});
typescript
copy

You can assign the following values to card.preferredFlow field:

  • DEDICATED_SCENE
  • EMBEDDED_IN_HOME (default)

The style of the card form in the second dedicated scene remains defined by the style.input property. The pay with card button can also be styled, if you prefer not to use the default styling. See Styling Payment Method Button for details.

Styling Payment Method Button

The paymentMethodButton uses TextStyle to define the style of the button.

123456789101112131415
const options = {  /* Other options ... */  style: {    paymentMethodButton: {      background: string,      borderRadius: number | string,      boxShadow: string,      borderColor: string,      height: number,      primaryText: TextStyle,      logoColor: logoColor,      marginTop: string,    },  },};
typescript
copy
💡

PayPal and Google Pay allows for additional customization to their buttons, view the respective Payment Method Guide for further details.

Displaying Custom Submit Button

The Universal Checkout now enables you to use your own custom submit button. Follow these three simple steps to replace default submit button with your own custom one:

Step 1. Hide the Built-in Submit Button

12345
const checkout = await primer.showUniversalCheckout(clientToken, {  submitButton: {    useBuiltInButton: false, // Hide the built-in submit button  },});
typescript
copy

Step 2. Forward clicks to checkout.submit()

1234
const checkout = await primer.showUniversalCheckout(clientToken, options);const handleMySubmitButtonClick = (e) => {  checkout.submit();};
typescript
copy

Step 3. Dynamically Update Your Submit Button

The Universal Checkout expects the submit button to be visible and enabled in specific conditions. You don't have to care about these conditions - the Universal Checkout will tell you when and how to display the Submit Button.

123456789101112131415161718192021222324
const checkout = await primer.showUniversalCheckout(clientToken, {  /* Other options ... */  submitButton: {    useBuiltInButton: false, // Hide the built-in submit button    // The Universal Checkout expects the button to be showed or hidden    onVisible: (isVisible: boolean) => {      // Show / Hide the submit button    },    // The Universal Checkout expects the button to be enabled or disabled    onDisable: (isDisabled: boolean) => {      // Enable / Disable the submit button    },    // The Universal Checkout expects the button to have a loading indicator    // to indicate that it is processing/loading something    // "isLoading: true" also means that the button is disabled    onLoading: (isLoading: boolean) => {      // Set the submit button to loading or not    },    // Set the content of your submit button    onContentChange: (content: string) => {      // Set the text content of the submit button    },  },});
typescript
copy

These callback functions are called every time the scene changes.

Handle Multiple Submit Buttons

Each callback has a second argument called context with the currentSceneId. This enables you to determine which submit button to show/hide, enable/disable, or modify the content.

In addition, the onVisible callback also contains previousSceneId in the context object. This enables you to hide the button of the previous scene if necessary.

123456789101112131415161718192021
const getSubmitButtonFromSceneId = (sceneId: string) => {  if (sceneId === "UniversalCheckout/Home") {    return "SideButton";  } else if (sceneId === "...") {    return "TopButton";  }  return "BottomButton";};const onVisible = (isVisible, context) => {  const { prevousSceneId, currentSceneId } = context;  if (!isVisible) {    hideAllSubmitButtons();  } else {    hideSubmitButton(getSubmitButtonFromSceneId(previousSceneId));    showSubmitButton(getSubmitButtonFromSceneId(currentSceneId));  }};const onContentChange = (content, context) => {  const { currentSceneId } = context;  setSubmitButtonContent(getSubmitButtonFromSceneId(currentSceneId), content);};
typescript
copy

Success Screen

By default, what happens after onTokenizeSuccess depends on the payment method that the customer chooses. Some of them, such as GoCardless requires the display of a screen that summarizes the order.

It is recommended best practice to show a success screen once the payment is validated. You can use the successScreen options to determine whether to show a built-in success screen or a custom one.

The available built-in success screen types are 'PAYMENT_METHOD' and 'CHECK'.

💡

The default successScreen used when no type is defined is 'CHECK'.

Success Screen - Check



Custom Title

When using 'CHECK' as a success screen type, it is also possible to specify a custom success title which will be displayed under the success check mark icon.


1234567
await primer.showUniversalCheckout(clientToken, {  /* Other options ... */  successScreen: {    type: "CHECK",    title: "My custom title",  },});
typescript
copy

Setting successScreen to false overrides the default behavior so that you can show a custom success screen after onTokenizeSuccess is called:

1234
await primer.showUniversalCheckout(clientToken, {  /* Other options ... */  successScreen: false,});
typescript
copy

Further Information

To find out more, explore the full API in our npm docs.

If there is anything beyond the styling options listed above that you’d like to modify or change, do get in touch with our support team.