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

# PrimerCardForm

> SwiftUI card payment form view with slot-based layout

> [!WARNING]
>
> Primer Checkout iOS SDK is currently in **beta** (v3.0.0-beta.8).
> The API is subject to change before the stable release.

`PrimerCardForm` is a SwiftUI `View` that renders a complete card payment form, composed from three section slots: card details, billing address, and submit button. Each section is a `@ViewBuilder` slot you can replace.

The form resolves its [`PrimerCardFormSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session) from the environment. The `.primerCheckoutSession(_:theme:onCompletion:)` modifier provides it, so you can embed the form anywhere in your own SwiftUI layout. The modal [`PrimerCheckout`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout) renders its own card screen and does not take composable views.

> [!NOTE]
>
> Requires iOS 15.0+. `PrimerCardForm` is `@available(iOS 15.0, *)`.

## Signature

```swift theme={"dark"}
@available(iOS 15.0, *)
public struct PrimerCardForm<CardDetails: View, Billing: View, Submit: View>: View {

  public init(
    @ViewBuilder cardDetails: @escaping (PrimerCardFormSession) -> CardDetails = { CardFormDefaults.cardDetails($0) },
    @ViewBuilder billingAddress: @escaping (PrimerCardFormSession) -> Billing = { CardFormDefaults.billingAddress($0) },
    @ViewBuilder submitButton: @escaping (PrimerCardFormSession) -> Submit = { CardFormDefaults.submitButton($0) }
  )
}
```

## Parameters

| Parameter | Type | Default | Description |
| - | - | - | - |
| `cardDetails` | `(PrimerCardFormSession) -> CardDetails` | `{ CardFormDefaults.cardDetails($0) }` | Slot for the card input fields (number, expiry, CVV, cardholder name). |
| `billingAddress` | `(PrimerCardFormSession) -> Billing` | `{ CardFormDefaults.billingAddress($0) }` | Slot for the billing address fields. The default content renders nothing unless the session's configuration requires billing fields. A custom slot is always shown. To hide it, check `session.state.configuration.requiresBillingAddress` yourself. |
| `submitButton` | `(PrimerCardFormSession) -> Submit` | `{ CardFormDefaults.submitButton($0) }` | Slot for the submit/pay button. |

Each slot receives the active [`PrimerCardFormSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session), which exposes the published `state` and the field-update and `submit()` methods you need to drive custom UI.

> [!NOTE]
>
> Every slot is generic and defaults to a [`CardFormDefaults`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-defaults) builder. Because the defaults are supplied per-parameter, you can override any single slot and leave the rest at their default styling — you never have to re-implement the whole form.

## Default layout

The default form stacks the three slots vertically:

| Order | Slot | Default content |
| - | - | - |
| 1 | `cardDetails` | `CardFormDefaults.cardDetails` — card number, expiry, CVV, cardholder name |
| 2 | `billingAddress` | `CardFormDefaults.billingAddress` — billing address fields, rendered only when the session requires them |
| 3 | `submitButton` | `CardFormDefaults.submitButton` — the pay button |

When no card-form session is available, the form renders `CardFormDefaults.unavailable()` (an empty view) instead. This happens when the `.primerCheckoutSession(_:theme:onCompletion:)` modifier is missing, or when the checkout session is still initializing (its `phase` is not yet `.ready`). It also happens when the checkout session has no card payment method.

While a payment is processing, the default submit button and the input fields rendered by `CardFormDefaults` are disabled automatically. This covers the `cardDetails` and `billingAddress` sections and the per-field builders, except `cardNetwork(_:)`. The `cardNetwork(_:)` selector and views of your own inside a slot stay enabled, so disable them with `session.state.isLoading`.

## Usage

### Default form

Render the fully styled default card form with no arguments:

```swift theme={"dark"}
import SwiftUI
import PrimerSDK

struct CardPaymentScreen: View {
  var body: some View {
    PrimerCardForm()
  }
}
```

> [!TIP]
>
> Place `PrimerCardForm()` inside a view that has the `.primerCheckoutSession(_:theme:onCompletion:)` modifier applied, so the form can resolve its `PrimerCardFormSession` from the environment.

### Overriding a single slot

To customize one section, pass a **labeled** argument and leave the others at their defaults. Here only the submit button is replaced — `cardDetails` and `billingAddress` keep the default styling:

```swift theme={"dark"}
PrimerCardForm(submitButton: { session in
  MyPayButton(isLoading: session.state.isLoading) {
    session.submit()
  }
})
```

> [!WARNING]
>
> Always pass slots with their labels (`cardDetails:`, `billingAddress:`, `submitButton:`). Which slot a bare trailing closure such as `PrimerCardForm { session in ... }` fills depends on your Swift language mode. In Swift 6 mode it fills the first slot, `cardDetails`. In Swift 5 mode it fills the last slot, `submitButton`, and the compiler shows a trailing-closure matching warning.

### Recomposing the card section from building blocks

You don't have to discard the default fields when customizing a slot. Compose the default content with your own views using the per-field builders on [`CardFormDefaults`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-defaults):

```swift theme={"dark"}
PrimerCardForm(cardDetails: { session in
  VStack {
    CardFormDefaults.cardDetails(session)
    MyPromoBanner()
  }
})
```

You can also assemble the card section from the individual field building blocks — `CardFormDefaults.cardNumber`, `expiryDate`, `cvv`, and `cardholderName`:

```swift theme={"dark"}
PrimerCardForm(cardDetails: { session in
  VStack(spacing: 16) {
    CardFormDefaults.cardNumber(session)
    HStack(spacing: 16) {
      CardFormDefaults.expiryDate(session)
      CardFormDefaults.cvv(session)
    }
    CardFormDefaults.cardholderName(session)
  }
})
```

See [Card field components](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-field-components.md) for the full set of field building blocks.

## See also

> [card] [PrimerCardFormSession](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session.md)
>
> State, field-update methods, and `submit()` passed into every slot.

> [card] [CardFormDefaults](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-defaults.md)
>
> Default section and field builders used by each slot.

> [card] [Card field components](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-field-components.md)
>
> Individual field building blocks for recomposing the card section.

> [card] [PrimerPaymentMethods](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/primer-payment-methods.md)
>
> Render the list of available payment methods alongside the card form.


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