**Last updated**: 31 March 2026 | [**Change log**](/access/products/checkout/web/changelog/)

# Shadow DOM

You can inject the SDK in a Shadow DOM, making sure that the encapsulation of your HTML code/custom elements is preserved.
Both `open` and `closed` modes are supported.

#### Example configuration

To inject the SDK in a Shadow DOM, you must pass a reference to the `shadowRoot` of your Shadow DOM in the SDK configuration:


```javascript
Worldpay.checkout.init(
  {
    id: "your-checkout-id",
    form: "#card-form",
    fields: {
      pan: {
        selector: "#card-pan",
        placeholder: "4444333322221111"
      },
      cvv: {
        selector: "#card-cvv",
        placeholder: "123"
      },
      expiry: {
        selector: "#card-expiry",
        placeholder: "MM/YY"
      }
    },
    shadowRoot: myShadowRoot // a reference to a shadow root is passed in the shadowRoot property
  },
  function (error, checkout) {
    if (error) {
      console.error(error);
      return;
    }
    
    // rest of your code run at the time where the SDK is initialized
  }
);
```

### Integration with custom elements

You can also leverage the power of Shadow DOM to encapsulate all your checkout code in a custom element.

Here is a full example:

HTML

```html
<script language="JavaScript">
  // Place the JavaScript code here
</script>

<template id="custom-checkout-template">
    <style>
        p {
            color: white;
            background-color: #666;
            padding: 5px;
        }

        body {
            font: 11px/22px sans-serif;
            text-transform: uppercase;
            background-color: #f7f7f7;
            color: black;
        }

        .container {
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .card {
            position: relative;
            background: white;
            padding: 40px 30px;
            top: -30px;
            width: 100%;
            max-width: 300px;
            border-radius: 12px;
            box-shadow: 3px 3px 60px 0px rgba(0, 0, 0, 0.1);
        }

        .card .checkout .col-2 {
            display: flex;
        }

        .card .checkout .col-2 .col:first-child {
            margin-right: 15px;
        }

        .card .checkout .col-2 .col:last-child {
            margin-left: 15px;
        }

        .card .checkout .label .type {
            color: green;
        }

        .card .checkout.visa .label .type:before {
            content: "(visa)";
        }

        .card .checkout.mastercard .label .type:before {
            content: "(master card)";
        }

        .card .checkout.amex .label .type:before {
            content: "(american express)";
        }

        .card .checkout .field {
            height: 40px;
            border-bottom: 1px solid lightgray;
        }

        .card .checkout .field#card-pan {
            margin-bottom: 30px;
        }

        .card .checkout .field.is-onfocus {
            border-color: black;
        }

        .card .checkout .field.is-empty {
            border-color: orange;
        }

        .card .checkout .field.is-invalid {
            border-color: red;
        }

        .card .checkout .field.is-valid {
            border-color: green;
        }

        .card .checkout .submit {
            background: red;
            position: absolute;
            cursor: pointer;
            left: 50%;
            bottom: -60px;
            width: 200px;
            margin-left: -100px;
            color: white;
            outline: 0;
            font-size: 14px;
            border: 0;
            border-radius: 30px;
            text-transform: uppercase;
            font-weight: bold;
            padding: 15px 0;
            transition: background 0.3s ease;
        }

        .card .checkout.is-valid .submit {
            background: green;
        }

        .clear {
            background: grey;
            position: absolute;
            cursor: pointer;
            left: 50%;
            bottom: -120px;
            width: 200px;
            margin-left: -100px;
            color: white;
            outline: 0;
            font-size: 14px;
            border: 0;
            border-radius: 30px;
            text-transform: uppercase;
            font-weight: bold;
            padding: 15px 0;
            transition: background 0.3s ease;
        }
    </style>

    <section class="container">
        <section class="card">
            <form class="checkout" id="card-form">
                <div class="label">Card number <span class="type"></span></div>
                <section id="card-pan" class="field"></section>
                <section class="col-2">
                    <section class="col">
                        <div class="label">Expiry date</div>
                        <section id="card-expiry" class="field"></section>
                    </section>
                    <section class="col">
                        <div class="label">CVV</div>
                        <section id="card-cvv" class="field"></section>
                    </section>
                </section>
                <button class="submit" type="submit">Pay Now</button>
            </form>
            <button class="clear" id="clear">Clear</button>
        </section>
    </section>
</template>

<script src="https://try.access.worldpay-bsh.securedataplatform.com/access-checkout/v2/checkout.js" onload="addCheckout()"></script>

// For production change to "https://access.worldpay-bsh.securedataplatform.com/access-checkout/v2/checkout.js"
```

JavaScript

```javascript
customElements.define(
  'custom-checkout',
  class extends HTMLElement {
    get checkout() {
      return this._checkout;
    }

    set checkout(value) {
      return this._checkout = value;
    }

    connectedCallback() {
      const shadow = this.attachShadow({ mode: 'closed' });

      const template = document.getElementById('custom-checkout-template');
      shadow.appendChild(template.content.cloneNode(true));

      const thisInstance = this;

      Worldpay.checkout.init(
        {
          id: 'identity',
          form: '#card-form',
          fields: {
            pan: {
              selector: '#card-pan',
              placeholder: '4444 3333 2222 1111'
            },
            expiry: {
              selector: '#card-expiry',
              placeholder: 'MM/YY'
            },
            cvv: {
              selector: '#card-cvv',
              placeholder: '123'
            }
          },
          styles: {
            'input': {
              'color': 'black',
              'font-weight': 'bold',
              'font-size': '20px',
              'letter-spacing': '3px'
            },
            'input#pan': {
              'font-size': '24px'
            },
            'input.is-valid': {
              'color': 'green'
            },
            'input.is-invalid': {
              'color': 'red'
            },
            'input.is-onfocus': {
              'color': 'black'
            }
          },
          enablePanFormatting: true,
          shadowRoot: shadow,
          allowNonLuhnCompliantCards: true,
        },
        function (error, checkout) {
          if (error) {
            console.error(error);
            return;
          }

          thisInstance.checkout = checkout;

          const form = shadow.getElementById('card-form');
          form.addEventListener('submit', function (event) {
            event.preventDefault();

            checkout.generateSessionState(function (error, sessionState) {
              if (error) {
                console.error(error);
                return;
              }

              // session state for card details
              console.log(sessionState);
            });
          });

          const clear = shadow.getElementById('clear');
          clear.addEventListener('click', function (event) {
            event.preventDefault();

            checkout.clearForm(function () {
              console.log('Form successfully cleared');
            });
          });
        }
      );
    }

    disconnectedCallback() {
      this.checkout.remove();
    }
  }
);

function addCheckout() {
  document.body.appendChild(document.createElement('custom-checkout'));
}
```

### Limitations

The SDK is not compatible with nested Shadow DOMs per se, because it can only be initialized in a Shadow DOM when the host element belongs to the document's Light DOM.

If your Checkout form must be in a nested Shadow DOM, then a workaround consists in embedding your Checkout form into an iframe. Within the iframe's document context, the SDK can initialize normally either directly in the Light DOM or within a Shadow DOM.