> ## Documentation Index
> Fetch the complete documentation index at: https://primer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# How Primer Checkout works

> The component model, styling approach, and event-driven architecture behind Primer Checkout.

## What is a Primer Checkout component?

Primer Checkout is built from reusable UI components.

Each component represents a specific part of the checkout experience — such as the payment method selector, the card form, or the pay button — and is designed to be **configured, styled, and combined** without rewriting payment logic.

You don't build a checkout from scratch. You assemble and customize trusted building blocks.

## What this means for you

Using Primer Checkout components allows you to:

* Integrate a full checkout UI without managing sensitive payment inputs
* Customize layout and appearance without breaking functionality
* Keep a predictable and stable integration over time
* Upgrade components without refactoring your application

## Styling model

> [tab] Web
>
> Each Primer component uses **Shadow DOM**, which isolates its internal HTML and CSS from your application.
>
> You **cannot** style internal component elements with CSS selectors. Instead, Primer exposes **CSS variables** as the official customization API:
>
> ```css theme={"dark"}
> :root {
>   --primer-color-brand: #6c5ce7;
>   --primer-radius-small: 6px;
> }
> ```
>
> This allows safe customization, consistent theming, and runtime updates without rebuilding components.

> [tab] Android
>
> Styling is controlled through **design tokens** via the `PrimerTheme` data class. Tokens cover colors, spacing, typography, and shapes:
>
> ```kotlin theme={"dark"}
> val theme = PrimerTheme(
>     lightColorTokens = object : LightColorTokens() {
>         override val primerColorBrand: Color = Color(0xFF6C5CE7)
>     },
>     radiusTokens = RadiusTokens(medium = 12.dp),
> )
>
> PrimerCheckoutSheet(checkout = checkout, theme = theme)
> ```
>
> Token changes propagate to all SDK components automatically through `CompositionLocal`. This means you can apply a custom theme, and all Primer components will inherit the values.

> [tab] iOS
>
> Styling is controlled through **design tokens** via the `PrimerCheckoutTheme` struct. Tokens cover colors (separately for light and dark mode), spacing, typography, radius, sizes, and border widths:
>
> ```swift theme={"dark"}
> let theme = PrimerCheckoutTheme(
>   colors: ColorOverrides(
>     primerColorBrand: .init(.systemIndigo)
>   ),
>   radius: RadiusOverrides(
>     primerRadiusMedium: 12
>   )
> )
>
> PrimerCheckout(clientToken: clientToken, primerTheme: theme)
> ```
>
> Token changes propagate to all SDK components automatically.

## Content and layout customization

Each platform provides a way to customize the content and layout of checkout components.

> [tab] Web
>
> Slots are named placeholders inside Web Components:
>
> ```html theme={"dark"}
> <primer-checkout client-token="...">
>   <primer-main slot="main">
>     <primer-card-form slot="cardForm"></primer-card-form>
>     <primer-payment-methods slot="paymentMethods"></primer-payment-methods>
>   </primer-main>
> </primer-checkout>
> ```

> [tab] Android
>
> Slots are Compose lambda parameters on SDK composables:
>
> ```kotlin theme={"dark"}
> PrimerCheckoutSheet(
>     checkout = checkout,
>     cardForm = {
>         val controller = rememberCardFormController(checkout)
>         PrimerCardForm(
>             controller = controller,
>             submitButton = { /* your custom button */ },
>         )
>     },
>     success = { data -> /* your custom success screen */ },
> )
> ```

> [tab] iOS
>
> Slots are closures on the composable SwiftUI views: `PrimerCardForm`, `PrimerPaymentMethods`, and `PrimerVaultedPaymentMethods`. Most slots receive the relevant session (`PrimerCardFormSession` or `PrimerSelectionSession`) so you can read state and call actions from your own SwiftUI views. The `method` slot of `PrimerPaymentMethods` and the `item` and `submitButton` slots of `PrimerVaultedPaymentMethods` receive their item data and action instead. `PrimerCardForm` and `PrimerPaymentMethods` take `@ViewBuilder` closures. `PrimerVaultedPaymentMethods` slots return `AnyView`:
>
> ```swift theme={"dark"}
> @StateObject private var session: PrimerCheckoutSession
>
> init(clientToken: String) {
>   _session = StateObject(wrappedValue: PrimerCheckoutSession(clientToken: clientToken))
> }
>
> var body: some View {
>   ScrollView {
>     PrimerCardForm(
>       submitButton: { cardForm in
>         Button("Complete Purchase") {
>           cardForm.submit()
>         }
>       }
>     )
>     PrimerPaymentMethods()
>   }
>   .primerCheckoutSession(session) { state in
>     handle(state)
>   }
> }
> ```
>
> Override only the slots you need — every slot defaults to a `CardFormDefaults` / `PaymentMethodsDefaults` / `VaultedPaymentMethodsDefaults` builder, so unspecified sections keep their default styling.

## Communication: events

Primer Checkout uses an **event-driven architecture** to communicate with your application.

> [tab] Web
>
> Standard DOM events work with any framework:
>
> ```javascript theme={"dark"}
> const checkout = document.querySelector('primer-checkout');
>
> checkout.addEventListener('primer:payment-success', (event) => {
>   const { payment } = event.detail;
>   window.location.href = `/confirmation?order=${payment.orderId}`;
> });
> ```
>
> | Event | When it fires |
> | - | - |
> | `primer:ready` | SDK is initialized and ready |
> | `primer:state-change` | Checkout state changes |
> | `primer:payment-success` | Payment completed successfully |
> | `primer:payment-failure` | Payment failed |
>
> For details, see the [Events Guide](https://primer.io/docs/checkout/primer-checkout/configuration/events.md).

> [tab] Android
>
> Payment outcomes are terminal cases of the `PrimerCheckoutState` sealed interface, observed from `checkout.state`:
>
> ```kotlin theme={"dark"}
> val state by checkout.state.collectAsStateWithLifecycle()
>
> LaunchedEffect(state) {
>     when (val s = state) {
>         is PrimerCheckoutState.Success -> { /* payment completed */ }
>         is PrimerCheckoutState.Failure -> { /* payment failed */ }
>         else -> Unit
>     }
> }
>
> PrimerCheckoutSheet(checkout = checkout)
> ```
>
> | State | When it emits |
> | - | - |
> | `PrimerCheckoutState.Success` | Payment completed |
> | `PrimerCheckoutState.Failure` | Payment failed or error occurred |

> [tab] iOS
>
> Outcomes are delivered through the `onCompletion` closure as `PrimerCheckoutState` values. Once the checkout is ready, `onCompletion` receives `.failure` for each failed payment attempt, and the checkout stays open for a retry. It then receives `.success` or `.dismissed` exactly once, and nothing after that. A failed initialization is also delivered as `.failure`. With `PrimerCheckout`, the SDK error screen offers a retry unless you set `isErrorScreenEnabled` to `false`. With inline checkout (`.primerCheckoutSession`), a failed initialization ends the session:
>
> ```swift theme={"dark"}
> PrimerCheckout(clientToken: clientToken) { state in
>   switch state {
>   case let .success(result):
>     print("Payment completed: \(result.paymentId)")
>   case let .failure(error, _):
>     print("Payment failed: \(error.localizedDescription)")
>   case .dismissed:
>     print("Dismissed")
>   default:
>     break
>   }
> }
> ```
>
> For inline checkout, attach `onCompletion` with the `.primerCheckoutSession(_:theme:onCompletion:)` modifier, and observe the session's `@Published` `phase` (type `Phase`, cases `.initializing`/`.ready`) to react to lifecycle changes. For per-feature field or selection state, observe the child sessions via `session.cardForm?.state` / `session.selection?.state`.
>
> | Mechanism | When it fires |
> | - | - |
> | `onCompletion: ((PrimerCheckoutState) -> Void)?` | `.failure` for each failed attempt, then `.success` or `.dismissed` once |
> | Observing `session.phase` (`@Published`, type `Phase`) | Every lifecycle change (`.initializing`, `.ready`) |
>
> For details, see the [Events Guide](https://primer.io/docs/checkout/primer-checkout/configuration/events.md).

## What the SDK handles

Primer Checkout handles this for you:

* Payment method logic and processor communication
* Input validation and error display
* 3DS challenges and redirect flows
* Loading states and visual feedback
* Security and PCI compliance

You focus on integration and UX, not infrastructure.

## Key takeaways

* Primer Checkout is built from reusable, isolated components
* Customization happens through configuration, layout customization, and styling tokens
* Events provide decoupled communication between your app and the SDK

## See also

> [card] [Styling](https://primer.io/docs/checkout/primer-checkout/build-your-ui/styling-customization.md)
>
> Customize colors, fonts, and spacing

> [card] [Layout](https://primer.io/docs/checkout/primer-checkout/build-your-ui/layout-customization.md)
>
> Control component arrangement and layout

> [card] [Events](https://primer.io/docs/checkout/primer-checkout/configuration/events.md)
>
> Handle payment lifecycle events

> [card] [Components](https://primer.io/docs/checkout/primer-checkout/configuration/components.md)
>
> Explore all available components


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.