Custom Styling Example

This example demonstrates how to create a beautifully styled payment form using the PaySight Widget’s theming capabilities.

Complete Implementation

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>PaySight Widget - Custom Styling</title>

  <!-- Add Google Fonts -->
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">

  <style>
    :root {
      --primary-color: #6366f1;
      --primary-dark: #4f46e5;
      --error-color: #ef4444;
      --success-color: #22c55e;
      --text-color: #0f172a;
      --text-light: #64748b;
      --border-color: #e2e8f0;
      --background-color: #f8fafc;
      --surface-color: #ffffff;
    }

    /* Dark mode support */
    @media (prefers-color-scheme: dark) {
      :root {
        --primary-color: #818cf8;
        --primary-dark: #6366f1;
        --text-color: #f1f5f9;
        --text-light: #94a3b8;
        --border-color: #334155;
        --background-color: #0f172a;
        --surface-color: #1e293b;
      }
    }

    body {
      font-family: 'Inter', system-ui, -apple-system, sans-serif;
      line-height: 1.5;
      margin: 0;
      padding: 20px;
      background-color: var(--background-color);
      color: var(--text-color);
    }

    .container {
      max-width: 600px;
      margin: 40px auto;
      background-color: var(--surface-color);
      border-radius: 16px;
      box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
      padding: 32px;
    }

    .header {
      text-align: center;
      margin-bottom: 32px;
    }

    .header h1 {
      margin: 0;
      font-size: 24px;
      font-weight: 600;
      color: var(--text-color);
    }

    .header p {
      margin: 8px 0 0;
      color: var(--text-light);
    }

    .widget-container {
      border: 1px solid var(--border-color);
      border-radius: 12px;
      padding: 24px;
      background-color: var(--surface-color);
    }

    .status-message {
      padding: 12px 16px;
      border-radius: 8px;
      margin-bottom: 20px;
      display: none;
      font-size: 14px;
      font-weight: 500;
    }

    .error {
      background-color: rgb(239 68 68 / 0.1);
      border: 1px solid var(--error-color);
      color: var(--error-color);
    }

    .success {
      background-color: rgb(34 197 94 / 0.1);
      border: 1px solid var(--success-color);
      color: var(--success-color);
    }

    /* Theme toggle button */
    .theme-toggle {
      position: fixed;
      bottom: 20px;
      right: 20px;
      background-color: var(--surface-color);
      border: 1px solid var(--border-color);
      color: var(--text-color);
      padding: 8px 16px;
      border-radius: 8px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      display: flex;
      align-items: center;
      gap: 8px;
      transition: all 0.2s ease;
    }

    .theme-toggle:hover {
      background-color: var(--border-color);
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>Complete Your Payment</h1>
      <p>Secure payment processing by PaySight</p>
    </div>

    <div id="success-message" class="status-message success"></div>
    <div id="error-message" class="status-message error"></div>
    <div id="widget-container" class="widget-container"></div>
  </div>

  <button id="theme-toggle" class="theme-toggle">
    Toggle Theme
  </button>

  <!-- Add the PaySight Widget SDK -->
  <script src="https://payment.paysight.io/widget-sdk.js"></script>

  <script>
    // Theme configuration
    const lightTheme = {
      font: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap',
      css: {
        ":root": {
          "--primary-color": "#6366f1",
          "--primary-dark": "#4f46e5",
          "--error-color": "#ef4444",
          "--text-color": "#0f172a",
          "--text-light": "#64748b",
          "--border-color": "#e2e8f0",
          "--background": "#ffffff",
          "--input-height": "40px",
          "--border-radius": "8px",
          "--shadow-color": "rgb(0 0 0 / 0.1)"
        },
        // Input styles
        input: {
          height: "var(--input-height)",
          padding: "0 16px",
          fontSize: "15px",
          fontWeight: "500",
          borderRadius: "var(--border-radius)",
          border: "1px solid var(--border-color)",
          color: "var(--text-color)",
          backgroundColor: "var(--background)",
          transition: "all 0.2s ease",
          
          "&::placeholder": {
            color: "var(--text-light)",
            fontWeight: "400"
          },
          
          "&:focus": {
            outline: "none",
            borderColor: "var(--primary-color)",
            boxShadow: "0 0 0 3px rgb(99 102 241 / 0.1)"
          }
        },
        // Button styles
        button: {
          backgroundColor: "var(--primary-color)",
          color: "#ffffff",
          padding: "0 24px",
          height: "var(--input-height)",
          borderRadius: "var(--border-radius)",
          fontSize: "15px",
          fontWeight: "500",
          border: "none",
          cursor: "pointer",
          transition: "all 0.2s ease",
          
          "&:hover": {
            backgroundColor: "var(--primary-dark)"
          },
          
          "&:focus": {
            outline: "none",
            boxShadow: "0 0 0 3px rgb(99 102 241 / 0.1)"
          }
        },
        // Label styles
        label: {
          fontSize: "14px",
          fontWeight: "500",
          color: "var(--text-color)",
          marginBottom: "6px"
        },
        // Error message styles
        ".error": {
          color: "var(--error-color)",
          fontSize: "13px",
          marginTop: "6px",
          fontWeight: "500"
        },
        // Card field styles
        "[ev-component=card]": {
          gap: "16px",
          
          ".field[ev-name=number]": {
            "& input": {
              paddingLeft: "48px"
            },
            
            "& .icon": {
              left: "16px",
              width: "20px",
              height: "20px"
            }
          }
        }
      }
    };

    const darkTheme = {
      ...lightTheme,
      css: {
        ...lightTheme.css,
        ":root": {
          "--primary-color": "#818cf8",
          "--primary-dark": "#6366f1",
          "--error-color": "#ef4444",
          "--text-color": "#f1f5f9",
          "--text-light": "#94a3b8",
          "--border-color": "#334155",
          "--background": "#1e293b",
          "--input-height": "40px",
          "--border-radius": "8px",
          "--shadow-color": "rgb(0 0 0 / 0.3)"
        }
      }
    };

    // Widget configuration
    const config = {
      productId: YOUR_PRODUCT_ID,
      sessionId: \`session_\${Date.now()}\`,
      amount: 2999,
      environment: 'production',
      threeDSRequired: true,
      theme: lightTheme
    };

    // Initialize widget
    const widget = PaySightSDK.createWidget({
      targetId: 'widget-container',
      config,
      onReady: () => console.log('Widget ready'),
      onError: handleError,
      onMessage: handleMessage
    });

    // Error handler
    function handleError(error) {
      console.error('Widget error:', error);
      showError(error.message);
    }

    // Message handler
    function handleMessage(message) {
      switch (message.type) {
        case 'PAYMENT_SUCCESS':
          handlePaymentSuccess(message.payload);
          break;
        case 'PAYMENT_ERROR':
          handlePaymentError(message.payload);
          break;
      }
    }

    // Payment success handler
    function handlePaymentSuccess(payload) {
      const { transactionId, amount, currency } = payload;
      const formattedAmount = new Intl.NumberFormat('en-US', {
        style: 'currency',
        currency
      }).format(amount / 100);

      showSuccess(
        `Payment successful! Amount: ${formattedAmount}, Transaction ID: ${transactionId}`
      );
    }

    // Payment error handler
    function handlePaymentError(payload) {
      showError(payload.message);
    }

    // UI Helper functions
    function showSuccess(message) {
      const element = document.getElementById('success-message');
      element.textContent = message;
      element.style.display = 'block';
      document.getElementById('error-message').style.display = 'none';
    }

    function showError(message) {
      const element = document.getElementById('error-message');
      element.textContent = message;
      element.style.display = 'block';
      document.getElementById('success-message').style.display = 'none';
    }

    // Theme toggle
    let isDarkTheme = false;
    document.getElementById('theme-toggle').addEventListener('click', () => {
      isDarkTheme = !isDarkTheme;
      widget.update({
        theme: isDarkTheme ? darkTheme : lightTheme
      });
    });
  </script>
</body>
</html>

Key Styling Features

1. Theme Configuration

const theme = {
  // Google Font integration
  font: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap',
  
  // Custom CSS
  css: {
    // CSS variables
    ":root": {
      "--primary-color": "#6366f1",
      "--text-color": "#0f172a"
    },
    
    // Component styles
    input: {
      // Input field styles
    },
    button: {
      // Button styles
    }
  }
};

2. Dark Mode Support

const darkTheme = {
  css: {
    ":root": {
      "--primary-color": "#818cf8",
      "--text-color": "#f1f5f9",
      "--background": "#1e293b"
    }
  }
};

// Update theme
widget.update({ theme: darkTheme });

3. Component-Specific Styles

const theme = {
  css: {
    // Card input styling
    "[ev-component=card]": {
      gap: "16px",
      
      // Card number field
      ".field[ev-name=number]": {
        "& input": {
          paddingLeft: "48px"
        }
      }
    }
  }
};

4. State-Based Styling

const theme = {
  css: {
    // Focus states
    "input:focus": {
      outline: "none",
      borderColor: "var(--primary-color)",
      boxShadow: "0 0 0 3px rgb(99 102 241 / 0.1)"
    },
    
    // Error states
    ".error": {
      color: "var(--error-color)",
      fontSize: "13px",
      fontWeight: "500"
    }
  }
};

Implementation Steps

  1. Define Theme Variables

    • Set up CSS variables for consistent styling
    • Include both light and dark mode values
  2. Create Base Styles

    • Style container elements
    • Set up typography
    • Configure spacing and layout
  3. Style Form Components

    • Customize input fields
    • Style buttons
    • Add field labels and error messages
  4. Add Interactive States

    • Implement hover effects
    • Add focus states
    • Style error states
  5. Implement Dark Mode

    • Create dark theme variables
    • Add theme toggle functionality
    • Test both themes
  6. Add Polish

    • Include transitions
    • Add subtle animations
    • Ensure consistent spacing

Best Practices

  1. Use CSS Variables

    const theme = {
      css: {
        ":root": {
          "--primary": "#6366f1",
          "--text": "#0f172a"
        },
        button: {
          backgroundColor: "var(--primary)",
          color: "var(--text)"
        }
      }
    };
    
  2. Maintain Consistency

    const spacing = {
      sm: "8px",
      md: "16px",
      lg: "24px"
    };
    
    const theme = {
      css: {
        input: {
          padding: spacing.md,
          marginBottom: spacing.md
        }
      }
    };
    
  3. Follow Accessibility Guidelines

    const theme = {
      css: {
        // Ensure sufficient color contrast
        button: {
          backgroundColor: "#6366f1",
          color: "#ffffff",
          
          // Visible focus indicators
          "&:focus": {
            outline: "2px solid #818cf8",
            outlineOffset: "2px"
          }
        }
      }
    };
    

Next Steps