Skip to main content
This guide covers advanced layout customization techniques for when you need complete control over your checkout experience.

Fully custom implementation

For complete control, you can bypass <primer-main> entirely and provide your own implementation. Choose one error display approach:
<primer-checkout client-token="your-token">
  <div slot="main" id="custom-checkout">
    <div id="payment-methods">
      <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>

      <!-- Pre-built component handles payment failure display automatically -->
      <primer-error-message-container></primer-error-message-container>
    </div>
  </div>
</primer-checkout>
Implementation responsibilityWhen using this approach:
  • You must handle state management yourself through events
  • You have complete freedom over the layout and user flow
  • You’re responsible for showing/hiding appropriate content based on checkout state
  • You need to handle payment failure display, either with the <primer-error-message-container> component or by implementing custom error handling with events

Events

When implementing a custom layout without <primer-main>, you’ll need to listen for events to manage checkout states. For comprehensive information on all available events, event payloads, and best practices, see the Events Guide.
document
  .querySelector('primer-checkout')
  .addEventListener('primer:state-change', (event) => {
    const state = event.detail;

    // Handle different checkout states
    if (state.isProcessing) {
      // Show loading indicator
    } else if (state.isSuccessful) {
      // Show success message
    } else if (state.primerJsError || state.paymentFailure) {
      // Show error message
      const errorMessage =
        state.primerJsError?.message || state.paymentFailure?.message;
      // Display error to user
    }
  });
1
Initialization
SDK loads and signals readiness.
SDK primer:ready
PrimerJS instance
Register callbacks via PrimerJS
2
Payment Methods Discovery
SDK resolves which methods are available for this session.
SDK primer:methods-update
available methods
Render payment method elements dynamically
3
State Changes
SDK communicates checkout state as the user progresses.
SDK primer:state-change
isProcessing: true
SDK primer:state-change
isSuccessful: true
Show loading UI, then redirect or display success
  • primer:state-change - Fired when checkout state changes
  • primer:methods-update - Fired when available payment methods are loaded
  • primer:ready - Fired when the SDK is ready

Configuring payment methods

When customizing the payment method layout, you can include specific payment methods:
<div slot="payments">
  <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>
  <primer-payment-method type="PAYPAL"></primer-payment-method>
</div>
The type attribute specifies which payment method to display. If a payment method isn’t available in your Dashboard configuration, it simply won’t render.

Payment method filtering with include, exclude and type

The primer-payment-method-container component provides a declarative way to organize payment methods:
<!-- Sectioned layout example -->
<div slot="payments">
  <!-- Quick pay options -->
  <primer-payment-method-container
    include="APPLE_PAY,GOOGLE_PAY"
  ></primer-payment-method-container>

  <!-- Alternative methods -->
  <primer-payment-method-container
    exclude="PAYMENT_CARD,APPLE_PAY,GOOGLE_PAY"
  ></primer-payment-method-container>

  <!-- Card form -->
  <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>
</div>
This approach automatically filters available payment methods without requiring event listeners or manual state management. See the Payment Method Container SDK Reference documentation for complete usage guide.

Dynamic rendering with events

You can also dynamically render payment methods by listening to the primer:methods-update event:
checkout.addEventListener('primer:methods-update', (event) => {
  const availableMethods = event.detail.toArray();
  const container = document.getElementById('payment-methods');

  // Create payment method elements based on available methods
  availableMethods.forEach((method) => {
    const element = document.createElement('primer-payment-method');
    element.setAttribute('type', method.type);
    container.appendChild(element);
  });
});
This approach ensures you only display payment methods that are actually available.

Avoiding duplicate card forms

When customizing your checkout layout, be careful not to render duplicate card forms. This commonly happens when:
  1. You create a custom card form using <primer-card-form>
  2. You also include <primer-payment-method type="PAYMENT_CARD"> in your layout
<!-- INCORRECT: Will result in duplicate card forms -->
<div slot="payments">
  <!-- Custom card form -->
  <primer-card-form>
    <!-- Custom card form content -->
  </primer-card-form>

  <!-- This will render ANOTHER card form -->
  <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>
</div>
If you’re using a custom card form implementation, you should not include the PAYMENT_CARD payment method in your layout.
Important: If you’re using a custom card form, you should filter out the PAYMENT_CARD type to avoid duplicate card forms:
checkout.addEventListener('primer:methods-update', (event) => {
  let availableMethods = event.detail.toArray();
  const container = document.getElementById('payment-methods');

  // If using a custom card form, filter out PAYMENT_CARD
  if (document.querySelector('primer-card-form')) {
    availableMethods = availableMethods.filter(
      (method) => method.type !== 'PAYMENT_CARD',
    );
  }

  // Create payment method elements based on filtered methods
  availableMethods.forEach((method) => {
    const element = document.createElement('primer-payment-method');
    element.setAttribute('type', method.type);
    container.appendChild(element);
  });
});

Best practices

  1. Listen for relevant events - Handle checkout state through event listeners
  2. Design responsively - Ensure your layout works on all device sizes
  3. Test thoroughly - Validate behavior across different payment methods and scenarios
  4. Prevent component flash - Use CSS or JavaScript techniques to hide content until components are defined
  5. Handle payment failures - Either use the <primer-error-message-container> component or implement custom payment failure handling using events