About customisation

Learn about how you can customise card components.

Overview

Card components come with responsive and accessible default styling, but are designed to be fully customisable.

Core styling architecture

State-based styling system

All styling configurations support multiple states to provide dynamic visual feedback:

interface StateStyles {
  base?: CSSProperties;      // Default appearance
  valid?: CSSProperties;     // When field passes validation
  invalid?: CSSProperties;   // When field fails validation
}

interface ComponentStyles {
  base?: CSSProperties;      // Container styles
  valid?: CSSProperties;     // Valid state container
  invalid?: CSSProperties;   // Invalid state container
  input?: CSSProperties;     // Input element specific styles
}

CSS properties integration

All styling options use React's CSSProperties interface, providing:

  • Type safety: IntelliSense support and compile-time validation.
  • Comprehensive properties: Access to all CSS properties.
  • Pseudo-selectors: Support for :hover, :focus, :active states.
  • Camel case conversion: Automatic conversion from camelCase to kebab-case.

Component-specific styling options

Field components (input-based)

All field components inherit from FieldComponentConfig and support comprehensive styling:

interface FieldComponentConfig {
  // Container styling with state support
  componentStyles?: ComponentStyles;
    
  // Input element styling with states
  inputStyles?: StateStyles;
    
  // Label styling with states
  labelStyles?: StateStyles;
    
  // Label positioning
  labelPosition?: 'above' | 'below' | 'left' | 'right';
  isFloatingLabel?: boolean;
    
  // Error message styling
  invalidTextStyles?: CSSProperties;
  invalidTextPosition?: 'above' | 'below' | 'left' | 'right';
    
  // Icon customisation
  validIconSrc?: string;
  invalidIconSrc?: string;
}

Example

Here's an example of a customised card number component.

const cardNumberConfig: CardNumberComponentConfig = {
  // Container styling
  componentStyles: {
    base: {
      backgroundColor: '#f8f9fa',
      borderRadius: '8px',
      padding: '16px',
      boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
    },
    valid: {
      borderColor: '#28a745',
      backgroundColor: '#f8fff8'
    },
    invalid: {
      borderColor: '#dc3545',
      backgroundColor: '#fff8f8'
    }
  },
    
  // Input field styling
  inputStyles: {
    base: {
      fontSize: '18px',
      fontWeight: '500',
      color: '#2c3e50',
      border: '2px solid #e9ecef',
      borderRadius: '6px',
      padding: '12px 16px',
      transition: 'all 0.3s ease'
    },
    valid: {
      borderColor: '#28a745',
      ':focus': {
        borderColor: '#20c997',
        boxShadow: '0 0 0 3px rgba(40, 167, 69, 0.1)'
      }
    },
    invalid: {
      borderColor: '#dc3545',
      ':focus': {
        borderColor: '#c82333',
        boxShadow: '0 0 0 3px rgba(220, 53, 69, 0.1)'
      }
     }
    },
    
  // Label styling
  labelStyles: {
    base: {
      fontSize: '14px',
      fontWeight: '600',
      color: '#495057',
      marginBottom: '8px',
      textTransform: 'uppercase',
      letterSpacing: '0.5px'
    },
    invalid: {
      color: '#dc3545'
    }
  },
    
  // Floating label configuration
  isFloatingLabel: true,
  labelPosition: 'above',
    
  // Error message styling
  invalidTextStyles: {
    fontSize: '12px',
    color: '#dc3545',
    fontStyle: 'italic',
    marginTop: '4px'
  },
    
  // Icon customisation
  showHintIcon: true,
  hintIconSrc: 'https://example.com/card-icon.svg'
};

Card submit

const submitButtonConfig: CardSubmitComponentConfig = {
  submitText: 'Complete payment',
  styles: {
    base: {
      backgroundColor: '#6c5ce7',
      color: '#ffffff',
      fontSize: '16px',
        fontWeight: '600',
        padding: '14px 32px',
        borderRadius: '8px',
        border: 'none',
        cursor: 'pointer',
        transition: 'all 0.2s ease',
        textTransform: 'uppercase',
        letterSpacing: '0.5px'
      },
      hover: {
        backgroundColor: '#5a52d5',
        transform: 'translateY(-1px)',
        boxShadow: '0 4px 12px rgba(108, 92, 231, 0.3)'
      },
      disabled: {
        backgroundColor: '#95a5a6',
        cursor: 'not-allowed',
        transform: 'none',
        boxShadow: 'none'
      }
    }
  }
};

Country selection

const countryConfig: CountrySelectionComponentConfig = {
  // Base field styling
  componentStyles: {
    base: {
      position: 'relative',
      width: '100%'
    }
  },
    
  // Dropdown container styling
  dropdownStyles: {
    backgroundColor: '#ffffff',
    border: '1px solid #dee2e6',
    borderRadius: '0 0 12px 12px',
    boxShadow: '0 8px 25px rgba(0, 0, 0, 0.15)',
    maxHeight: '250px',
    overflowY: 'auto',
    zIndex: 1000
  },
    
  // Dropdown option styling
  dropdownOptionStyles: {
    unselected: {
      padding: '12px 16px',
      cursor: 'pointer',
      borderBottom: '1px solid #f8f9fa',
      transition: 'background-color 0.2s ease'
    },
    hover: {
      backgroundColor: '#e3f2fd',
      borderLeft: '3px solid #2196f3'
    },
    selected: {
      backgroundColor: '#e8f5e8',
      color: '#2e7d32',
      fontWeight: '600',
      borderLeft: '3px solid #4caf50'
    }
  }
};

Checkbox components

const checkboxConfig: CheckboxComponentConfig = {
  label: 'Save payment method for future use',
  checkedColor: '#4caf50', 
  labelStyles: {
    checked: {
      color: '#2e7d32',
      fontWeight: '500'
    },
    unchecked: {
      color: '#6c757d',
      fontWeight: '400'
    }
  }
};

Advanced styling patterns

Pseudo-selectors support

const advancedInputStyles = {
  base: {
    fontSize: '16px',
    padding: '12px',
    // Pseudo-selectors using key prefixes
    ':hover': {
      borderColor: '#007bff'
    },
    ':focus': {
      borderColor: '#0056b3',
      boxShadow: '0 0 0 3px rgba(0, 123, 255, 0.1)'
    },
    ':active': {
      transform: 'translateY(1px)'
    },
    '::placeholder': {
      color: '#6c757d',
      fontStyle: 'italic'
    }
  }
};

Responsive design

const responsiveStyles = {
  base: {
    fontSize: '16px',
    padding: '12px',
    // Use CSS custom properties for responsive behavior
    '--mobile-padding': '8px',
    '--desktop-padding': '16px',
    
    // Media queries via CSS-in-JS (if supported)
    '@media (max-width: 768px)': {
      fontSize: '14px',
      padding: 'var(--mobile-padding)'
    },
    '@media (min-width: 769px)': {
      padding: 'var(--desktop-padding)'
    }
  }
};

Typography and branding

Consistent typography system

const typographyTheme = {
  // Font families
  primaryFont: 'Inter, system-ui, -apple-system, sans-serif',
  monospaceFont: 'SF Mono, Monaco, monospace',
    
  // Font sizes
  fontSizes: {
    xs: '12px',
    sm: '14px',
    base: '16px',
    lg: '18px',
    xl: '20px'
  },
    
  // Line heights
  lineHeights: {
    tight: '1.2',
    normal: '1.5',
    relaxed: '1.7'
  }
};

// Apply to components
const cardNumberConfig = {
  inputStyles: {
    base: {
      fontFamily: typographyTheme.primaryFont,
      fontSize: typographyTheme.fontSizes.base,
      lineHeight: typographyTheme.lineHeights.normal
    }
  },
  labelStyles: {
    base: {
      fontFamily: typographyTheme.primaryFont,
      fontSize: typographyTheme.fontSizes.sm,
      lineHeight: typographyTheme.lineHeights.tight
    }
  }
};

Brand colour system

const brandColors = {
  primary: '#6c5ce7',
  secondary: '#00b894',
  accent: '#fd79a8',
  neutral: {
    50: '#f8f9fa',
    100: '#e9ecef',
    200: '#dee2e6',
    500: '#6c757d',
    900: '#212529'
  },
  semantic: {
    success: '#00b894',
    warning: '#fdcb6e',
    error: '#e17055',
    info: '#74b9ff'
  }
};

// Apply brand colours
const brandedComponentStyles = {
  base: {
    backgroundColor: brandColors.neutral[50],
    borderColor: brandColors.neutral[200]
  },
  valid: {
    borderColor: brandColors.semantic.success,
    color: brandColors.semantic.success
  },
  invalid: {
    borderColor: brandColors.semantic.error,
    color: brandColors.semantic.error
  }
};

Animation and transitions

const animatedStyles = {
  base: {
    transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
    transform: 'translateY(0)',
    opacity: 1
  },
  invalid: {
    transform: 'translateX(2px)',
    animation: 'shake 0.5s ease-in-out'
  }
};

// Add keyframes via CSS injection
const keyframes = `
  @keyframes shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-2px); }
    75% { transform: translateX(2px); }
  }
`;