プライシングカード - Vibe Coding Showcase

プライシングカード

料金プランを魅力的に表示するカードコンポーネント。複数のテーマ、サイズ、機能比較表示に対応し、SaaSやECサイトでの料金ページ作成に最適

デザインプレビュー

プランタイプ

テーマ

サイズ

課金サイクル

フリー

個人利用や小規模プロジェクトに最適

無料
  • 月1,000リクエスト
  • コミュニティサポート
  • 基本ダッシュボード
  • APIアクセス
  • 優先サポート
  • カスタム統合
  • SLA保証
人気プラン

プロフェッショナル

ビジネス利用におすすめの人気プラン

¥2,980/月
  • 月100,000リクエスト
  • 優先サポート
  • 高度なダッシュボード
  • APIアクセス
  • カスタム統合(月3回まで)
  • Webhook機能
  • SLA保証

エンタープライズ

大規模組織向けの包括的ソリューション

¥9,800/月
  • 無制限リクエスト
  • 専任サポート
  • カスタムダッシュボード
  • APIアクセス
  • 無制限カスタム統合
  • Webhook機能
  • 99.9% SLA保証

💡 使用例

SaaSサービス

  • • 月額・年額の料金プラン比較
  • • 機能制限の明確な表示
  • • 人気プランの強調表示

Eコマース

  • • 配送オプションの比較
  • • サービス内容の詳細表示
  • • 買い切り価格の表示

プライシングカードの特徴

柔軟な料金表示

月額・年額切り替え、割引価格表示、無料プラン対応など、様々な料金体系に対応。通貨や課金サイクルも自由にカスタマイズ可能

直感的な機能比較

機能の有無を視覚的に表示し、制限や詳細説明も追加可能。ユーザーが各プランの違いを一目で理解できる設計

多彩なデザインテーマ

5つのテーマ(デフォルト、モダン、ミニマル、グラデーション、ダーク)を提供。人気プランの強調表示やホバーエフェクトも内蔵

AI活用ガイド

SaaSサービス料金プラン

プロンプト例:

3つの料金プラン(フリー、プロ、エンタープライズ)を作成してください。月額と年額の切り替え機能付きで、プロプランを人気プランとして強調表示。モダンテーマを使用してください

Eコマース配送プラン

プロンプト例:

オンラインショップ向けの配送プラン比較カードを作成してください。スタンダード、プレミアム、エクスプレスの3プランで、配送日数や保険などの機能比較を含めてください

コンサルティングサービス

プロンプト例:

ビジネスコンサルティング会社向けの料金プランを作成してください。時間制、プロジェクト制、月額制の3つのプランで、グラデーションテーマを使用してプレミアム感を演出してください

実装コード

ファイルサイズ: 18.5KB TypeScript

コンポーネント実装

import React, { useState } from 'react';

export interface PricingFeature {
  /** 機能名 */
  name: string;
  /** 含まれているかどうか */
  included: boolean;
  /** 追加の説明 */
  description?: string;
  /** 制限がある場合(例:「月5回まで」) */
  limit?: string;
}

export interface PricingPlan {
  /** プラン名 */
  name: string;
  /** 説明 */
  description: string;
  /** 価格 */
  price: number;
  /** 通貨 */
  currency?: string;
  /** 課金サイクル */
  billingCycle?: 'monthly' | 'yearly' | 'one-time';
  /** 割引価格(年間プランなど) */
  discountPrice?: number;
  /** 人気プランかどうか */
  popular?: boolean;
  /** 機能リスト */
  features: PricingFeature[];
  /** CTAボタンテキスト */
  buttonText: string;
  /** CTAボタンのリンク */
  buttonLink?: string;
  /** 無料プランかどうか */
  isFree?: boolean;
  /** プランの詳細ページリンク */
  detailsLink?: string;
}

export interface PricingCardProps {
  /** プラン情報 */
  plan: PricingPlan;
  /** テーマ */
  theme?: 'default' | 'modern' | 'minimal' | 'gradient' | 'dark';
  /** サイズ */
  size?: 'sm' | 'md' | 'lg';
  /** 年間/月間切り替え */
  billingToggle?: boolean;
  /** 現在の課金サイクル */
  currentBilling?: 'monthly' | 'yearly';
  /** CTAボタンクリック時のコールバック */
  onButtonClick?: (plan: PricingPlan) => void;
  /** 詳細リンククリック時のコールバック */
  onDetailsClick?: (plan: PricingPlan) => void;
  /** カスタムクラス */
  className?: string;
}

const PricingCard: React.FC<PricingCardProps> = ({
  plan,
  theme = 'default',
  size = 'md',
  billingToggle = false,
  currentBilling = 'monthly',
  onButtonClick,
  onDetailsClick,
  className = ''
}) => {
  const [hovering, setHovering] = useState(false);

  // テーマ設定
  const themes = {
    default: {
      card: 'bg-white border border-gray-200 shadow-lg',
      popularBadge: 'bg-blue-500 text-white',
      header: 'bg-gray-50',
      price: 'text-gray-900',
      originalPrice: 'text-gray-500 line-through',
      description: 'text-gray-600',
      feature: 'text-gray-700',
      featureIcon: 'text-green-500',
      notIncluded: 'text-gray-400',
      button: plan.popular 
        ? 'bg-blue-500 hover:bg-blue-600 text-white' 
        : 'bg-gray-100 hover:bg-gray-200 text-gray-900',
      detailsLink: 'text-blue-500 hover:text-blue-600'
    },
    modern: {
      card: 'bg-white border border-gray-200 shadow-xl rounded-2xl',
      popularBadge: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
      header: 'bg-gradient-to-br from-gray-50 to-gray-100',
      price: 'text-gray-900',
      originalPrice: 'text-gray-500 line-through',
      description: 'text-gray-600',
      feature: 'text-gray-700',
      featureIcon: 'text-green-500',
      notIncluded: 'text-gray-400',
      button: plan.popular 
        ? 'bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white' 
        : 'bg-gray-100 hover:bg-gray-200 text-gray-900',
      detailsLink: 'text-purple-500 hover:text-purple-600'
    },
    minimal: {
      card: 'bg-white border border-gray-100 shadow-sm',
      popularBadge: 'bg-gray-900 text-white',
      header: 'bg-transparent',
      price: 'text-gray-900',
      originalPrice: 'text-gray-400 line-through',
      description: 'text-gray-500',
      feature: 'text-gray-600',
      featureIcon: 'text-gray-900',
      notIncluded: 'text-gray-300',
      button: plan.popular 
        ? 'bg-gray-900 hover:bg-gray-800 text-white' 
        : 'bg-gray-50 hover:bg-gray-100 text-gray-900 border border-gray-200',
      detailsLink: 'text-gray-500 hover:text-gray-700'
    },
    gradient: {
      card: plan.popular 
        ? 'bg-gradient-to-br from-blue-500 to-purple-600 text-white border-0 shadow-2xl'
        : 'bg-white border border-gray-200 shadow-lg',
      popularBadge: 'bg-yellow-400 text-gray-900',
      header: plan.popular ? 'bg-white bg-opacity-10' : 'bg-gray-50',
      price: plan.popular ? 'text-white' : 'text-gray-900',
      originalPrice: plan.popular ? 'text-blue-200 line-through' : 'text-gray-500 line-through',
      description: plan.popular ? 'text-blue-100' : 'text-gray-600',
      feature: plan.popular ? 'text-white' : 'text-gray-700',
      featureIcon: plan.popular ? 'text-blue-200' : 'text-green-500',
      notIncluded: plan.popular ? 'text-blue-300' : 'text-gray-400',
      button: plan.popular 
        ? 'bg-white text-blue-600 hover:bg-gray-100' 
        : 'bg-blue-500 hover:bg-blue-600 text-white',
      detailsLink: plan.popular ? 'text-blue-200 hover:text-white' : 'text-blue-500 hover:text-blue-600'
    },
    dark: {
      card: 'bg-gray-900 border border-gray-700 shadow-xl text-white',
      popularBadge: 'bg-blue-500 text-white',
      header: 'bg-gray-800',
      price: 'text-white',
      originalPrice: 'text-gray-400 line-through',
      description: 'text-gray-300',
      feature: 'text-gray-200',
      featureIcon: 'text-green-400',
      notIncluded: 'text-gray-500',
      button: plan.popular 
        ? 'bg-blue-500 hover:bg-blue-600 text-white' 
        : 'bg-gray-700 hover:bg-gray-600 text-white',
      detailsLink: 'text-blue-400 hover:text-blue-300'
    }
  };

  // サイズ設定
  const sizes = {
    sm: {
      card: 'p-4',
      header: 'p-4 pb-2',
      body: 'p-4 pt-2',
      price: 'text-2xl',
      planName: 'text-lg',
      description: 'text-sm',
      feature: 'text-sm',
      button: 'py-2 px-4 text-sm'
    },
    md: {
      card: 'p-6',
      header: 'p-6 pb-4',
      body: 'p-6 pt-4',
      price: 'text-4xl',
      planName: 'text-xl',
      description: 'text-base',
      feature: 'text-sm',
      button: 'py-3 px-6 text-base'
    },
    lg: {
      card: 'p-8',
      header: 'p-8 pb-6',
      body: 'p-8 pt-6',
      price: 'text-5xl',
      planName: 'text-2xl',
      description: 'text-lg',
      feature: 'text-base',
      button: 'py-4 px-8 text-lg'
    }
  };

  const themeConfig = themes[theme];
  const sizeConfig = sizes[size];

  // 現在の価格を取得
  const getCurrentPrice = () => {
    if (plan.isFree) return 0;
    if (currentBilling === 'yearly' && plan.discountPrice) {
      return plan.discountPrice;
    }
    return plan.price;
  };

  // 元の価格を取得(割引がある場合)
  const getOriginalPrice = () => {
    if (currentBilling === 'yearly' && plan.discountPrice && plan.discountPrice < plan.price) {
      return plan.price;
    }
    return null;
  };

  // 課金サイクルテキスト
  const getBillingText = () => {
    switch (plan.billingCycle || currentBilling) {
      case 'yearly':
        return '/年';
      case 'monthly':
        return '/月';
      case 'one-time':
        return '買い切り';
      default:
        return '/月';
    }
  };

  const currentPrice = getCurrentPrice();
  const originalPrice = getOriginalPrice();

  return (
    <div 
      className={`relative rounded-lg transition-all duration-300 ${themeConfig.card} ${sizeConfig.card} ${className} ${
        hovering && !plan.popular ? 'transform scale-105 shadow-xl' : ''
      } ${plan.popular ? 'ring-2 ring-blue-500 ring-opacity-50' : ''}`}
      onMouseEnter={() => setHovering(true)}
      onMouseLeave={() => setHovering(false)}
    >
      {/* 人気プランバッジ */}
      {plan.popular && (
        <div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
          <div className={`${themeConfig.popularBadge} px-4 py-1 rounded-full text-xs font-semibold`}>
            人気プラン
          </div>
        </div>
      )}

      {/* ヘッダー */}
      <div className={`${themeConfig.header} ${sizeConfig.header} rounded-t-lg`}>
        <h3 className={`${sizeConfig.planName} font-bold ${theme === 'gradient' && plan.popular ? 'text-white' : 'text-gray-900'}`}>
          {plan.name}
        </h3>
        <p className={`${sizeConfig.description} ${themeConfig.description} mt-1`}>
          {plan.description}
        </p>
      </div>

      {/* 価格セクション */}
      <div className={`${sizeConfig.body}`}>
        <div className="flex items-baseline justify-center mb-6">
          {plan.isFree ? (
            <span className={`${sizeConfig.price} font-bold ${themeConfig.price}`}>
              無料
            </span>
          ) : (
            <>
              <span className={`${sizeConfig.price} font-bold ${themeConfig.price}`}>
                {plan.currency || '¥'}{currentPrice.toLocaleString()}
              </span>
              <span className={`ml-1 ${sizeConfig.feature} ${themeConfig.description}`}>
                {getBillingText()}
              </span>
              {originalPrice && (
                <span className={`ml-2 ${sizeConfig.feature} ${themeConfig.originalPrice}`}>
                  {plan.currency || '¥'}{originalPrice.toLocaleString()}
                </span>
              )}
            </>
          )}
        </div>

        {/* 機能リスト */}
        <ul className="space-y-3 mb-8">
          {plan.features.map((feature, index) => (
            <li key={index} className="flex items-start">
              <svg 
                className={`w-5 h-5 mt-0.5 mr-3 flex-shrink-0 ${
                  feature.included ? themeConfig.featureIcon : themeConfig.notIncluded
                }`}
                fill="none" 
                stroke="currentColor" 
                viewBox="0 0 24 24"
              >
                {feature.included ? (
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                ) : (
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                )}
              </svg>
              <div className="flex-1">
                <span className={`${sizeConfig.feature} ${
                  feature.included ? themeConfig.feature : themeConfig.notIncluded
                }`}>
                  {feature.name}
                </span>
                {feature.limit && (
                  <span className={`ml-2 text-xs ${themeConfig.description}`}>
                    ({feature.limit})
                  </span>
                )}
                {feature.description && (
                  <p className={`text-xs ${themeConfig.description} mt-1`}>
                    {feature.description}
                  </p>
                )}
              </div>
            </li>
          ))}
        </ul>

        {/* CTAボタン */}
        <button
          onClick={() => onButtonClick?.(plan)}
          className={`w-full ${sizeConfig.button} font-semibold rounded-lg transition-colors duration-200 ${themeConfig.button}`}
        >
          {plan.buttonText}
        </button>

        {/* 詳細リンク */}
        {plan.detailsLink && (
          <div className="text-center mt-4">
            <button
              onClick={() => onDetailsClick?.(plan)}
              className={`${sizeConfig.feature} ${themeConfig.detailsLink} hover:underline`}
            >
              詳細を見る
            </button>
          </div>
        )}
      </div>
    </div>
  );
};

export default PricingCard;

使用例

// 基本的な使用例
import PricingCard from './pricing-card';

const basicPlan = {
  name: 'ベーシック',
  description: '個人利用に最適なプラン',
  price: 980,
  currency: '¥',
  billingCycle: 'monthly',
  features: [
    { name: '月10,000リクエスト', included: true },
    { name: '基本サポート', included: true },
    { name: 'APIアクセス', included: false },
    { name: '優先サポート', included: false }
  ],
  buttonText: '開始する',
  isFree: false
};

function App() {
  const handlePlanSelect = (plan) => {
    console.log('プラン選択:', plan.name);
    // 決済処理やサインアップフローへ
  };

  return (
    <PricingCard
      plan={basicPlan}
      theme="default"
      size="md"
      onButtonClick={handlePlanSelect}
    />
  );
}

// 人気プランの強調表示
const popularPlan = {
  name: 'プロフェッショナル',
  description: 'ビジネス利用におすすめ',
  price: 2980,
  discountPrice: 2480, // 年額プラン時の割引価格
  popular: true, // 人気プランとして強調
  features: [
    { name: '月100,000リクエスト', included: true },
    { name: '優先サポート', included: true },
    { name: 'APIアクセス', included: true },
    { name: 'カスタム統合', included: true, limit: '月3回まで' }
  ],
  buttonText: '今すぐ始める',
  detailsLink: '/plans/pro'
};

// 年額・月額切り替え機能付き
function PricingSection() {
  const [billing, setBilling] = useState('monthly');
  
  return (
    <div className="grid md:grid-cols-3 gap-8">
      {plans.map(plan => (
        <PricingCard
          key={plan.name}
          plan={plan}
          theme="modern"
          currentBilling={billing}
          billingToggle={true}
          onButtonClick={handlePlanSelect}
        />
      ))}
    </div>
  );
}

// 無料プランの例
const freePlan = {
  name: 'フリー',
  description: '無料で始められる',
  price: 0,
  isFree: true,
  features: [
    { name: '月1,000リクエスト', included: true },
    { name: 'コミュニティサポート', included: true },
    { name: 'APIアクセス', included: false },
    { name: '商用利用', included: false }
  ],
  buttonText: '無料で始める'
};

// グラデーションテーマでプレミアム感を演出
<PricingCard
  plan={premiumPlan}
  theme="gradient"
  size="lg"
  onButtonClick={handlePlanSelect}
/>