> ## 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.

# Core Concepts

> Understand the component model, styling approach, and event-driven architecture

## 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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="iOS">
    Styling is controlled through **design tokens** via the `PrimerCheckoutTheme` class. Tokens cover colors, 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.
  </Tab>
</Tabs>

## Content and layout customization

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

<Tabs>
  <Tab title="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>

  <Tab title="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>

  <Tab title="iOS">
    Slots are **`@ViewBuilder` closures** on the composable SwiftUI views. Each slot receives the relevant session so you can read state and call actions from your own SwiftUI views:

    ```swift theme={"dark"}
    @StateObject private var session = 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` builder, so unspecified sections keep their default styling.
  </Tab>
</Tabs>

## Communication: events

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

<Tabs>
  <Tab title="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](/docs/checkout/primer-checkout/configuration/events).
  </Tab>

  <Tab title="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>

  <Tab title="iOS">
    Outcomes are delivered through the `onCompletion` closure with the terminal `PrimerCheckoutState`. The closure fires once per session, with `.success`, `.failure`, or `.dismissed`:

    ```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:
        // .initializing and .ready are lifecycle states and are
        // never delivered to onCompletion.
        break
      }
    }
    ```

    For inline checkout, attach `onCompletion` with the `.primerCheckoutSession(_: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`          | Terminal state (`.success`, `.failure`, `.dismissed`) |
    | Observing `session.phase` (`@Published`, type `Phase`) | Every lifecycle change (`.initializing`, `.ready`)    |

    For details, see the [Events Guide](/docs/checkout/primer-checkout/configuration/events).
  </Tab>
</Tabs>

## 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

<CardGroup cols={2}>
  <Card title="Styling" icon="palette" href="/docs/checkout/primer-checkout/build-your-ui/styling-customization">
    Customize colors, fonts, and spacing
  </Card>

  <Card title="Layout" icon="table-layout" href="/docs/checkout/primer-checkout/build-your-ui/layout-customization">
    Control component arrangement and layout
  </Card>

  <Card title="Events" icon="bolt" href="/docs/checkout/primer-checkout/configuration/events">
    Handle payment lifecycle events
  </Card>

  <Card title="Components" icon="puzzle-piece" href="/docs/checkout/primer-checkout/configuration/components">
    Explore all available components
  </Card>
</CardGroup>
