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

# Card Field Components

> Individual per-field building blocks for composing custom card form layouts

<Warning>
  Primer Checkout iOS SDK is currently in **beta** (v3.0.0-beta.1).
  The API is subject to change before the stable release.
</Warning>

`CardFormDefaults` exposes a set of per-field building blocks alongside its full section renderers. Each building block is a static function with the signature `(PrimerCardFormSession) -> CardFieldContent`. Drop any of them into a custom [`PrimerCardForm`](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form) section slot to recompose the form one field at a time, while keeping the SDK's built-in formatting, validation, keyboard configuration, and inline error display.

<Note>
  Every building block is **self-hiding**: it renders nothing unless its field is part of the active [`CardFormConfiguration`](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session) for the current session. You can drop every block into a layout without worrying about which fields the current session actually requires — only the configured ones appear.
</Note>

## How the building blocks work

The full-section renderers — `CardFormDefaults.cardDetails(_:)` and `CardFormDefaults.billingAddress(_:)` — produce the complete, config-aware form. The field-level building blocks let you interleave your own content (banners, headings, custom layout) between individual fields instead.

Each field block forwards to the same shared, configuration-aware renderer the section helpers use, so a hand-composed layout behaves identically to the default form. The block checks whether its field appears in the session's `configuration.cardFields` or `configuration.billingFields`; if not, it resolves to an `EmptyView`.

```swift theme={"dark"}
@available(iOS 15.0, *)
public enum CardFormDefaults {
    public static func cardNumber(_ session: PrimerCardFormSession) -> CardFieldContent
    public static func expiryDate(_ session: PrimerCardFormSession) -> CardFieldContent
    // ...one function per field
}
```

All field blocks take a single unlabeled [`PrimerCardFormSession`](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session) parameter and return a concrete `View`. Read `session.state` for rendering and call into the session for mutations and submission.

## Card detail fields

These map to the `cardFields` in the active `CardFormConfiguration` (the default configuration is `.cardNumber`, `.expiryDate`, `.cvv`, `.cardholderName`).

| Method               | Return type        | Renders when                                   | Description                                                                                   |
| -------------------- | ------------------ | ---------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `cardNumber(_:)`     | `CardFieldContent` | `.cardNumber` is in `configuration.cardFields` | Card number field with network-aware grouping, Luhn validation, and detected-network icon.    |
| `expiryDate(_:)`     | `CardFieldContent` | `.expiryDate` is configured                    | Expiry field; auto-inserts the `/` separator (MM/YY) and validates for a valid, future month. |
| `cvv(_:)`            | `CardFieldContent` | `.cvv` is configured                           | CVV field; masked input, max length adapts to the detected network (4 for Amex, 3 otherwise). |
| `cardholderName(_:)` | `CardFieldContent` | `.cardholderName` is configured                | Cardholder name field with word capitalization.                                               |

```swift theme={"dark"}
@available(iOS 15.0, *)
public static func cardNumber(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func expiryDate(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func cvv(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func cardholderName(_ session: PrimerCardFormSession) -> CardFieldContent
```

### Co-badged card network selector

`cardNetwork(_:)` is a standalone selector for co-badged cards. Unlike the other blocks it does **not** read `CardFormConfiguration`; it renders only when more than one network is detected (`session.state.availableNetworks.count > 1`), reusing the SDK's built-in dropdown. Tapping a network commits the choice through `session.selectCardNetwork(_:)`.

| Method            | Return type               | Renders when                  | Description                                                            |
| ----------------- | ------------------------- | ----------------------------- | ---------------------------------------------------------------------- |
| `cardNetwork(_:)` | `CardNetworkFieldContent` | `availableNetworks.count > 1` | Dropdown selector for co-badged cards (e.g., Cartes Bancaires + Visa). |

```swift theme={"dark"}
@available(iOS 15.0, *)
public static func cardNetwork(_ session: PrimerCardFormSession) -> CardNetworkFieldContent
```

<Warning>
  Use `cardNetwork(_:)` only when you recompose the individual card fields. The full `cardDetails(_:)` section already renders the selector when multiple networks are detected — combining the two would show two selectors.
</Warning>

## Billing address fields

These map to the `billingFields` in the active `CardFormConfiguration`. They appear only when billing address collection is enabled for the session (`configuration.requiresBillingAddress` and the corresponding field present in `billingFields`).

| Method             | Return type        | Renders when                                     | Description                                                    |
| ------------------ | ------------------ | ------------------------------------------------ | -------------------------------------------------------------- |
| `firstName(_:)`    | `CardFieldContent` | `.firstName` is in `configuration.billingFields` | First name field with word capitalization.                     |
| `lastName(_:)`     | `CardFieldContent` | `.lastName` is configured                        | Last name field with word capitalization.                      |
| `email(_:)`        | `CardFieldContent` | `.email` is configured                           | Email field for receipts and notifications.                    |
| `phoneNumber(_:)`  | `CardFieldContent` | `.phoneNumber` is configured                     | Phone number field.                                            |
| `addressLine1(_:)` | `CardFieldContent` | `.addressLine1` is configured                    | Primary street address line.                                   |
| `addressLine2(_:)` | `CardFieldContent` | `.addressLine2` is configured                    | Secondary address line (apartment, suite, etc.).               |
| `city(_:)`         | `CardFieldContent` | `.city` is configured                            | City field.                                                    |
| `state(_:)`        | `CardFieldContent` | `.state` is configured                           | State or province field; label adapts to the selected country. |
| `postalCode(_:)`   | `CardFieldContent` | `.postalCode` is configured                      | Postal/ZIP code field; format and label vary by country.       |
| `countryCode(_:)`  | `CardFieldContent` | `.countryCode` is configured                     | Country selector field.                                        |

```swift theme={"dark"}
@available(iOS 15.0, *)
public static func firstName(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func lastName(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func email(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func phoneNumber(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func addressLine1(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func addressLine2(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func city(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func state(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func postalCode(_ session: PrimerCardFormSession) -> CardFieldContent
@available(iOS 15.0, *)
public static func countryCode(_ session: PrimerCardFormSession) -> CardFieldContent
```

## Composing a custom card detail layout

Pass a `@ViewBuilder` closure to the `cardDetails` slot on [`PrimerCardForm`](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form). Lay out the field blocks in any order, and add your own content between them. Because each block is self-hiding, the extra fields you reference (such as `cardholderName`) appear only if the session's configuration includes them.

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

@available(iOS 15.0, *)
struct CustomCardDetails: View {
    var body: some View {
        PrimerCardForm(cardDetails: { session in
            VStack(alignment: .leading, spacing: 16) {
                Text("Card details")
                    .font(.headline)

                CardFormDefaults.cardNumber(session)

                HStack(spacing: 12) {
                    CardFormDefaults.expiryDate(session)
                    CardFormDefaults.cvv(session)
                }

                CardFormDefaults.cardholderName(session)

                // Co-badged selector: shown only when multiple networks are detected.
                CardFormDefaults.cardNetwork(session)
            }
        })
    }
}
```

<Tip>
  Because you replaced the default `cardDetails` section, include `CardFormDefaults.cardNetwork(session)` so co-badged cards still get a selector — the built-in one is suppressed once you recompose the individual fields.
</Tip>

### Recomposing billing fields too

The `billingAddress` slot works the same way. Group billing blocks into your own layout; only the configured fields render.

```swift theme={"dark"}
@available(iOS 15.0, *)
struct CustomBilling: View {
    var body: some View {
        PrimerCardForm(billingAddress: { session in
            VStack(alignment: .leading, spacing: 16) {
                Text("Billing address")
                    .font(.headline)

                HStack(spacing: 12) {
                    CardFormDefaults.firstName(session)
                    CardFormDefaults.lastName(session)
                }

                CardFormDefaults.addressLine1(session)
                CardFormDefaults.addressLine2(session)

                HStack(spacing: 12) {
                    CardFormDefaults.city(session)
                    CardFormDefaults.state(session)
                }

                CardFormDefaults.postalCode(session)
                CardFormDefaults.countryCode(session)
            }
        })
    }
}
```

## Reacting to field state

Each building block manages its own input and inline error display, so you don't have to wire bindings manually. When you need to react to the form as a whole — for example to drive your own submit button — read the published `session.state`:

```swift theme={"dark"}
@available(iOS 15.0, *)
struct CustomSubmit: View {
    @ObservedObject var session: PrimerCardFormSession

    var body: some View {
        Button("Pay now") { session.submit() }
            .disabled(!session.state.isValid || session.state.isLoading)
    }
}
```

Per-field errors are available on `session.state` via `fieldErrors`, `hasError(for:)`, and `errorMessage(for:)`. See [Card Form Session](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session) for the full state surface.

## See also

<CardGroup cols={2}>
  <Card title="Card Form Defaults" icon="layer-group" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-defaults">
    The full section renderers and submit button these blocks complement.
  </Card>

  <Card title="PrimerCardForm" icon="credit-card" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form">
    The composable form view and its `@ViewBuilder` section slots.
  </Card>

  <Card title="Card Form Session" icon="signal-stream" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session">
    Observable state, field updates, and submission for the card form.
  </Card>
</CardGroup>
