Skip to content

About customisation

Learn how to customise card components to match your app's design.

Overview

PXP Android components come with sensible defaults but can be customised to match your app's design system. The SDK uses a simplified styling approach that works with Jetpack Compose and Material Design.

By customising components, you can:

  • Create a consistent visual experience that matches your brand.
  • Provide clear visual feedback for validation states.
  • Ensure accessibility compliance with proper colour contrast.
  • Adapt to different screen sizes and device orientations.

Styling architecture

Component-specific styles

Each component type has its own style class for targeted customisation. This allows you to apply different visual treatments to different components, while maintaining consistency across your payment form.

// Card number styling - organised into logical groups
CardNumberStyle(
    colors = CardNumberStyle.ColorStyles(/* colour properties */),
    textStyles = CardNumberStyle.TextStyles(/* text properties */),
    shapes = CardNumberStyle.ShapeStyles(/* shape properties */),
    iconStyles = CardNumberStyle.IconStyles(/* icon properties */),
    spacingStyles = CardNumberStyle.SpacingStyles(/* spacing properties */),
    sizeStyles = CardNumberStyle.SizeStyles(/* size properties */)
)

// Card CVC styling - comprehensive style groups
CardCvcStyle(
    colors = CardCvcStyle.ColorStyles(/* colour properties */),
    textStyles = CardCvcStyle.TextStyles(/* text properties */),
    dimensions = CardCvcStyle.DimensionStyles(/* dimension properties */),
    iconStyles = CardCvcStyle.IconStyles(/* icon properties */),
    tooltipStyles = CardCvcStyle.TooltipStyles(/* tooltip properties */),
    spacing = CardCvcStyle.SpacingStyles(/* spacing properties */)
)

// Other components follow the same logical grouping pattern
DynamicCardImageStyle(
    colors = DynamicCardImageStyle.ColorStyles(/* colour properties */),
    textStyles = DynamicCardImageStyle.TextStyles(/* text properties */),
    cvcStyles = DynamicCardImageStyle.CvcStyles(/* CVC-specific properties */),
    effectStyles = DynamicCardImageStyle.EffectStyles(/* effect properties */)
)

Basic styling example

Here's how to apply basic styling to a card number component:

val cardNumberConfig = CardNumberComponentConfig(
    label = "Card number",
    placeholder = "1234 5678 9012 3456",
    style = CardNumberStyle(
        colors = CardNumberStyle.ColorStyles(
            textColor = Color(0xFF1A1A1A),
            backgroundColor = Color.White,
            focusedContainerColor = Color.White,
            unfocusedContainerColor = Color(0xFFF5F5F5),
            borderColor = Color(0xFFE0E0E0),
            focusedBorderColor = Color(0xFF2196F3),
            errorColor = Color(0xFFD32F2F),
            errorBorderColor = Color(0xFFD32F2F),
            cursorColor = Color(0xFF2196F3)
        ),
        textStyles = CardNumberStyle.TextStyles(
            textStyle = TextStyle(
                fontSize = 16.sp,
                fontWeight = FontWeight.Normal
            ),
            labelStyle = TextStyle(
                fontSize = 14.sp,
                fontWeight = FontWeight.Medium
            )
        ),
        shapes = CardNumberStyle.ShapeStyles(
            shape = RoundedCornerShape(8.dp)
        )
    ),
    showValidIcon = true,
    showInvalidIcon = true,
    validIcon = R.drawable.custom_check_icon,
    invalidIcon = R.drawable.custom_error_icon
)

Visual customisation options

Icons and visual elements

Most components support customising visual elements to provide better user feedback and match your design system:

CardNumberComponentConfig(
    // Validation icons
    showValidIcon = true,
    validIcon = R.drawable.ic_check_custom,
    showInvalidIcon = true, 
    invalidIcon = R.drawable.ic_error_custom,
    
    // Card brand icon
    showTrailingIcon = true,
    useTransparentCardBrandImage = false,
    
    // Hint icon
    showHintIcon = true,
    hintIcon = R.drawable.ic_card_hint
)

CVC component customisation

The CVC component offers comprehensive customisation options for security code input:

CardCvcComponentConfig(
    // Labels and text
    label = "Security code",
    placeholder = "123",
    guideText = "3-4 digit code on your card",
    
    // Visual elements
    showTooltip = true,
    showMaskToggle = true,
    applyMask = false,
    
    // Icons
    validIcon = R.drawable.ic_check,
    invalidIcon = R.drawable.ic_error,
    visibilityOnIcon = R.drawable.ic_visibility,
    visibilityOffIcon = R.drawable.ic_visibility_off,
    
    // Comprehensive styling
    style = CardCvcStyle(
        colors = CardCvcStyle.ColorStyles(
            textColor = Color(0xFF1A1A1A),
            focusedContainerColor = Color.White,
            unfocusedContainerColor = Color(0xFFF8F9FA),
            backgroundColor = Color.White,
            borderColor = Color(0xFFE1E5E9),
            focusedBorderColor = Color(0xFF4285F4),
            errorColor = Color(0xFFE53E3E),
            cursorColor = Color(0xFF4285F4),
            labelColor = Color(0xFF374151),
            placeholderColor = Color(0xFF9CA3AF),
            iconColor = Color(0xFF6B7280),
            hintColor = Color(0xFF6B7280),
            guideTextColor = Color(0xFF6B7280)
        ),
        textStyles = CardCvcStyle.TextStyles(
            textStyle = TextStyle(
                fontSize = 16.sp,
                fontWeight = FontWeight.Normal,
                lineHeight = 24.sp
            ),
            labelStyle = TextStyle(
                fontSize = 14.sp,
                fontWeight = FontWeight.Medium,
                lineHeight = 20.sp
            ),
            errorStyle = TextStyle(
                fontSize = 12.sp,
                fontWeight = FontWeight.Normal,
                lineHeight = 16.sp
            ),
            guideTextStyle = TextStyle(
                fontSize = 12.sp,
                fontWeight = FontWeight.Normal,
                lineHeight = 16.sp
            )
        ),
        dimensions = CardCvcStyle.DimensionStyles(
            shape = RoundedCornerShape(8.dp)
        )
    ),
    
    // Tooltip configuration
    tooltipData = TooltipData(
        useAdvancedTooltip = true,
        tooltipEntries = listOf(
            CardTooltipData(
                text = "Find your security code on the back of your card",
                highlightedText = "security code",
                cardImageRes = R.drawable.custom_card_back
            )
        )
    )
)

Label and text customisation

Field labels

Customise labels and placeholder text to match your application's language and tone:

CardNumberComponentConfig(
    label = "Card number",
    placeholder = "Enter your card number",
    displayRequiredIcon = true
)

CardExpiryDateComponentConfig(
    label = "Expiry date",
    placeholder = "MM/YY"
)

CardCvcComponentConfig(
    label = "Security code",
    placeholder = "123",
    guideText = "3-4 digit code on your card"
)

Validation messages

CardNumberValidations(
    required = "Please enter your card number",
    invalid = "Please enter a valid card number",
    tooShort = "Card number is too short",
    typeNotRecognized = "Card type not recognised",
    typeNotSupported = "This card type is not supported",
    luhnFailed = "Please check your card number"
)

CardCvcValidations(
    required = "Security code is required",
    invalid = "Invalid security code",
    tooShort = "Security code is too short",
    tooLong = "Security code is too long"
)

Component integration styling

Tooltip customisation

Create helpful tooltips to guide users through the payment process:

TooltipData(
    useAdvancedTooltip = true,
    tooltipEntries = listOf(
        CardTooltipData(
            text = "Your card number is the 16-digit number on the front of your card",
            highlightedText = "16-digit number",
            cardImageRes = R.drawable.card_front_highlight
        ),
        CardTooltipData(
            text = "Some cards may have 15 digits",
            highlightedText = "15 digits", 
            cardImageRes = R.drawable.card_amex_front
        )
    )
)

Compose integration

Using Material Theme

Components integrate seamlessly with your existing Material Theme configuration:

@Composable
fun PaymentScreen() {
    MaterialTheme {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp)
        ) {
            // Components inherit theme colors and typography
            pxpCheckout.buildComponentView(
                component = cardNumberComponent,
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

Custom component wrapper

Create reusable wrapper components to add consistent styling across your payment form:

@Composable
fun StyledPaymentField(
    component: Component<*>,
    label: String,
    modifier: Modifier = Modifier
) {
    Card(
        modifier = modifier,
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.surfaceVariant
        ),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Text(
                text = label,
                style = MaterialTheme.typography.labelMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                modifier = Modifier.padding(bottom = 8.dp)
            )
            
            pxpCheckout.buildComponentView(
                component = component,
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

Custom layout

Create custom layouts that arrange multiple components for optimal user experience:

@Composable
fun CustomPaymentForm() {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        item {
            StyledPaymentField(
                component = cardNumberComponent,
                label = "Card Information"
            )
        }
        
        item {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.spacedBy(12.dp)
            ) {
                StyledPaymentField(
                    component = expiryComponent,
                    label = "Expiry",
                    modifier = Modifier.weight(1f)
                )
                
                StyledPaymentField(
                    component = cvcComponent,
                    label = "CVC",
                    modifier = Modifier.weight(1f)
                )
            }
        }
        
        item {
            pxpCheckout.buildComponentView(
                component = submitComponent,
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

Accessibility customisation

Content descriptions

Components automatically generate appropriate accessibility labels, but you can enhance them with additional context:

CardNumberComponentConfig(
    label = "Card number",
    // Components automatically generate accessibility labels
    // You can enhance with additional context if needed
)

Focus order

Define logical focus order to help users navigate your payment form efficiently:

CardNumberComponentConfig(
    focusOrder = 1
)

CardExpiryDateComponentConfig(
    focusOrder = 2  
)

CardCvcComponentConfig(
    focusOrder = 3
)

Responsive design

Screen size adaptation

Adapt your payment form layout for different screen sizes to optimise the user experience:

@Composable
fun ResponsivePaymentForm() {
    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp.dp
    
    if (screenWidth > 600.dp) {
        // Tablet layout - side by side
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            pxpCheckout.buildComponentView(
                component = cardNumberComponent,
                modifier = Modifier.weight(2f)
            )
            pxpCheckout.buildComponentView(
                component = expiryComponent,
                modifier = Modifier.weight(1f)
            )
            pxpCheckout.buildComponentView(
                component = cvcComponent,
                modifier = Modifier.weight(1f)
            )
        }
    } else {
        // Phone layout - stacked
        Column(
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            pxpCheckout.buildComponentView(
                component = cardNumberComponent,
                modifier = Modifier.fillMaxWidth()
            )
            Row(
                horizontalArrangement = Arrangement.spacedBy(12.dp)
            ) {
                pxpCheckout.buildComponentView(
                    component = expiryComponent,
                    modifier = Modifier.weight(1f)
                )
                pxpCheckout.buildComponentView(
                    component = cvcComponent,
                    modifier = Modifier.weight(1f)
                )
            }
        }
    }
}

Dark theme support

Components automatically adapt to system theme preferences for better user experience:

@Composable
fun ThemedPaymentScreen() {
    MaterialTheme(
        colorScheme = if (isSystemInDarkTheme()) {
            darkColorScheme()
        } else {
            lightColorScheme()
        }
    ) {
        PaymentForm()
    }
}

Animation and transitions

State changes

Create smooth animations when component states change to provide clear visual feedback:

@Composable
fun AnimatedPaymentField(component: Component<*>) {
    val isValid by component.state.collectAsState()
    
    val borderColor by animateColorAsState(
        targetValue = if (isValid.isValid) {
            MaterialTheme.colorScheme.primary
        } else {
            MaterialTheme.colorScheme.error
        }
    )
    
    Card(
        modifier = Modifier.fillMaxWidth(),
        border = BorderStroke(2.dp, borderColor)
    ) {
        pxpCheckout.buildComponentView(
            component = component,
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )
    }
}

Advanced styling patterns

Creating a consistent design system

Use consistent styling across multiple components to create a cohesive design system:

// Define common style values
object PaymentTheme {
    object Colors {
        val primary = Color(0xFF2563EB)
        val surface = Color.White
        val surfaceVariant = Color(0xFFF8FAFC)
        val outline = Color(0xFFE2E8F0)
        val error = Color(0xFFDC2626)
        val onSurface = Color(0xFF1E293B)
        val onSurfaceVariant = Color(0xFF64748B)
    }
    
    object Typography {
        val fieldInput = TextStyle(
            fontSize = 16.sp,
            fontWeight = FontWeight.Normal,
            lineHeight = 24.sp
        )
        val fieldLabel = TextStyle(
            fontSize = 14.sp,
            fontWeight = FontWeight.Medium,
            lineHeight = 20.sp
        )
    }
    
    val fieldShape = RoundedCornerShape(8.dp)
}

// Apply consistent styling to card number component
val cardNumberConfig = CardNumberComponentConfig(
    style = CardNumberStyle(
        colors = CardNumberStyle.ColorStyles(
            textColor = PaymentTheme.Colors.onSurface,
            unfocusedContainerColor = PaymentTheme.Colors.surfaceVariant,
            focusedContainerColor = PaymentTheme.Colors.surface,
            borderColor = PaymentTheme.Colors.outline,
            focusedBorderColor = PaymentTheme.Colors.primary,
            errorBorderColor = PaymentTheme.Colors.error,
            cursorColor = PaymentTheme.Colors.primary
        ),
        textStyles = CardNumberStyle.TextStyles(
            textStyle = PaymentTheme.Typography.fieldInput,
            labelStyle = PaymentTheme.Typography.fieldLabel
        ),
        shapes = CardNumberStyle.ShapeStyles(
            shape = PaymentTheme.fieldShape
        )
    )
)

// Apply the same design system to CVC component
val cardCvcConfig = CardCvcComponentConfig(
    style = CardCvcStyle(
        colors = CardCvcStyle.ColorStyles(
            textColor = PaymentTheme.Colors.onSurface,
            unfocusedContainerColor = PaymentTheme.Colors.surfaceVariant,
            focusedContainerColor = PaymentTheme.Colors.surface,
            borderColor = PaymentTheme.Colors.outline,
            focusedBorderColor = PaymentTheme.Colors.primary,
            errorColor = PaymentTheme.Colors.error,
            cursorColor = PaymentTheme.Colors.primary,
            labelColor = PaymentTheme.Colors.onSurface,
            placeholderColor = PaymentTheme.Colors.onSurfaceVariant
        ),
        textStyles = CardCvcStyle.TextStyles(
            textStyle = PaymentTheme.Typography.fieldInput,
            labelStyle = PaymentTheme.Typography.fieldLabel
        ),
        dimensions = CardCvcStyle.DimensionStyles(
            shape = PaymentTheme.fieldShape
        )
    )
)

Complete customisation example

Here's a comprehensive example that demonstrates advanced customisation techniques:

@Composable
fun CompleteCustomPaymentForm() {
    val backgroundColor = MaterialTheme.colorScheme.surface
    val primaryColor = MaterialTheme.colorScheme.primary
    
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        colors = CardDefaults.cardColors(
            containerColor = backgroundColor
        ),
        elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
    ) {
        Column(
            modifier = Modifier.padding(24.dp),
            verticalArrangement = Arrangement.spacedBy(20.dp)
        ) {
            Text(
                text = "Payment Details",
                style = MaterialTheme.typography.headlineSmall,
                color = primaryColor,
                fontWeight = FontWeight.Bold
            )
            
            // Card number with custom wrapper
            CustomFieldWrapper(
                label = "Card number",
                icon = Icons.Default.CreditCard
            ) {
                pxpCheckout.buildComponentView(
                    component = cardNumberComponent,
                    modifier = Modifier.fillMaxWidth()
                )
            }
            
            // Row for expiry and CVC
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.spacedBy(16.dp)
            ) {
                CustomFieldWrapper(
                    label = "Expiry date",
                    icon = Icons.Default.CalendarToday,
                    modifier = Modifier.weight(1f)
                ) {
                    pxpCheckout.buildComponentView(
                        component = expiryComponent,
                        modifier = Modifier.fillMaxWidth()
                    )
                }
                
                CustomFieldWrapper(
                    label = "Security code",
                    icon = Icons.Default.Security,
                    modifier = Modifier.weight(1f)
                ) {
                    pxpCheckout.buildComponentView(
                        component = cvcComponent,
                        modifier = Modifier.fillMaxWidth()
                    )
                }
            }
            
            // Submit button
            pxpCheckout.buildComponentView(
                component = submitComponent,
                modifier = Modifier.fillMaxWidth()
            )
        }
    }
}

@Composable
fun CustomFieldWrapper(
    label: String,
    icon: ImageVector,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Column(modifier = modifier) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier.padding(bottom = 8.dp)
        ) {
            Icon(
                imageVector = icon,
                contentDescription = null,
                tint = MaterialTheme.colorScheme.primary,
                modifier = Modifier.size(16.dp)
            )
            Spacer(modifier = Modifier.width(8.dp))
            Text(
                text = label,
                style = MaterialTheme.typography.labelMedium,
                color = MaterialTheme.colorScheme.onSurface
            )
        }
        content()
    }
}