Omnibase

UI Components

Pre-built shadcn components for pricing tables and billing UI

UI Components

OmniBase provides pre-built React components via the @omnibase/shadcn package for displaying pricing information and handling billing interactions.

Installation

bun add @omnibase/shadcn @omnibase/core-js

PricingTable

The PricingTable component displays your Stripe products with pricing, features, and a selection interface.

Basic Usage

import { PricingTable } from '@omnibase/shadcn';
import { V1StripeApi } from '@omnibase/core-js';

export default async function PricingPage() {
  const stripeApi = new V1StripeApi(config);
  const { data: stripeConfig } = await stripeApi.getStripeConfig();

  return (
    <PricingTable
      products={stripeConfig.products}
      onPriceSelect={(priceId, productId) => {
        console.log(`Selected: ${priceId} from ${productId}`);
      }}
    />
  );
}

Props

PropTypeDefaultDescription
productsProduct[]RequiredArray of products from Stripe config
selectedPriceIdstringCurrently selected price (shows visual indicator)
onPriceSelect(priceId: string, productId: string) => voidCallback when a price is selected
classNamestringAdditional CSS classes
showPricingTogglebooleanfalseShow month/year interval toggle
defaultInterval"month" | "year""month"Default billing interval

Features

Responsive Design

The component automatically adapts to screen size:

Screen SizeBehavior
MobileSingle-card carousel with swipe navigation
TabletSingle-card carousel
Desktop3-card carousel (or grid if ≤3 products)

Billing Period Toggle

When showPricingToggle={true}, displays a month/year toggle:

<PricingTable
  products={products}
  showPricingToggle={true}
  defaultInterval="month"
/>

The toggle only appears if products have prices with different intervals.

Selected Price Indicator

Highlight the current subscription:

<PricingTable
  products={products}
  selectedPriceId="pro_monthly"
  onPriceSelect={handleSelect}
/>

The selected product displays with a visual ring indicator.

Product Highlighting

Products with ui.highlighted: true display a "Most Popular" badge:

{
  "id": "pro",
  "name": "Pro Plan",
  "ui": {
    "highlighted": true,
    "badge": "Most Popular"
  }
}

Complete Example

app/(dashboard)/subscriptions/page.tsx
import { V1StripeApi, V1TenantsApi, V1PaymentsApi, Product } from '@omnibase/core-js';
import { PricingTable } from '@omnibase/shadcn';
import { redirect } from 'next/navigation';

export default async function SubscriptionsPage() {
  const stripeApi = new V1StripeApi(config);
  const tenantsApi = new V1TenantsApi(config);

  const [{ data: stripeConfig }, { data: subscriptions }] = await Promise.all([
    stripeApi.getStripeConfig(),
    tenantsApi.listTenantSubscriptions(),
  ]);

  const currentPriceId = subscriptions?.[0]?.configPriceId;

  async function handleCheckout(priceId: string) {
    'use server';
    const paymentsApi = new V1PaymentsApi(config);
    const { data } = await paymentsApi.createCheckout({
      createCheckoutRequest: {
        priceId,
        successUrl: `${process.env.APP_URL}/subscriptions?success=true`,
        cancelUrl: `${process.env.APP_URL}/subscriptions`,
        trialPeriodDays: 14,
        allowPromotionCodes: true,
      },
    });
    redirect(data.url!);
  }

  return (
    <div className="container py-12">
      <div className="text-center mb-12">
        <h1 className="text-4xl font-bold mb-4">Choose Your Plan</h1>
        <p className="text-muted-foreground">
          Select the perfect plan for your needs
        </p>
      </div>

      <PricingTable
        products={stripeConfig.products as Product[]}
        selectedPriceId={currentPriceId}
        onPriceSelect={handleCheckout}
        showPricingToggle
        defaultInterval="month"
      />
    </div>
  );
}

Data Types

The PricingTable component uses types from @omnibase/core-js:

Product

interface Product {
  id: string;                    // Config ID (e.g., "pro")
  stripeId?: string;             // Stripe product ID
  name: string;                  // Display name
  description?: string;          // Product description
  type?: "service" | "good";     // Product type
  prices: Price[];               // Array of prices
  ui?: ProductUI;                // UI customization
}

ProductUI

interface ProductUI {
  displayName?: string;          // Override display name
  tagline?: string;              // Short tagline (e.g., "Perfect for teams")
  features?: string[];           // List of features
  badge?: string;                // Badge text (e.g., "Most Popular")
  ctaText?: string;              // Button text (e.g., "Upgrade to Pro")
  highlighted?: boolean;         // Highlight this product
  sortOrder?: number;            // Display order (lower = first)
}

Price

interface Price {
  id: string;                    // Config ID (e.g., "pro_monthly")
  stripeId?: string;             // Stripe price ID
  amount: number;                // Amount in cents (4900 = $49.00)
  currency: string;              // Currency code (e.g., "usd")
  interval?: string;             // "month", "year", "week", "day"
  intervalCount?: number;        // Billing frequency (default: 1)
  usageType?: string;            // "licensed" or "metered"
  billingScheme?: string;        // "per_unit" or "tiered"
  public?: boolean;              // Visible in public API
  default?: boolean;             // Default price for product
  ui?: PriceUI;                  // UI customization
}

PriceUI

interface PriceUI {
  displayName?: string;          // Override display name
  billingPeriod?: string;        // Human-readable period (e.g., "per month")
  priceDisplay?: PriceDisplay;   // Custom price display
  features?: string[];           // Price-specific features
  limits?: PriceLimit[];         // Usage limits
}

PriceDisplay

interface PriceDisplay {
  customText?: string;           // Custom text (e.g., "Free", "Custom")
  showCurrency?: boolean;        // Show currency symbol
  suffix?: string;               // Text after price (e.g., " (Save 17%)")
}

PriceLimit

interface PriceLimit {
  text: string;                  // Limit description
  value?: number;                // Numeric value
  unit?: string;                 // Unit (e.g., "instances", "percent")
}

Customizing Display

Custom Price Text

For free tiers or custom pricing:

{
  "id": "free",
  "amount": 0,
  "currency": "usd",
  "interval": "month",
  "ui": {
    "priceDisplay": {
      "customText": "Free",
      "showCurrency": false
    }
  }
}

Renders as: Free (instead of $0.00)

Enterprise/Contact Sales

{
  "id": "enterprise",
  "amount": 0,
  "currency": "usd",
  "ui": {
    "priceDisplay": {
      "customText": "Custom"
    }
  }
}

Savings Suffix

{
  "id": "pro_yearly",
  "amount": 49000,
  "currency": "usd",
  "interval": "year",
  "ui": {
    "priceDisplay": {
      "suffix": " (Save 17%)"
    }
  }
}

Renders as: $490.00/year (Save 17%)

Feature Lists

Define features at the product or price level:

{
  "id": "pro",
  "name": "Pro Plan",
  "ui": {
    "features": [
      "Unlimited projects",
      "Priority support",
      "Advanced analytics"
    ]
  },
  "prices": [
    {
      "id": "pro_monthly",
      "amount": 4900,
      "currency": "usd",
      "interval": "month",
      "ui": {
        "features": [
          "Everything in Starter",
          "API access"
        ]
      }
    }
  ]
}

Usage Limits

Display limits with values:

{
  "id": "starter_monthly",
  "amount": 1900,
  "ui": {
    "limits": [
      { "text": "5 projects", "value": 5, "unit": "projects" },
      { "text": "10 GB storage", "value": 10, "unit": "GB" },
      { "text": "1.5% transaction fee", "value": 1.5, "unit": "percent" }
    ]
  }
}

Currency Support

The PricingTable supports these currencies with proper symbol formatting:

CurrencySymbolCode
US Dollar$usd
Euroeur
British Pound£gbp
Japanese Yen¥jpy
Canadian DollarCA$cad
Australian DollarA$aud

Other currencies display the currency code.

Sorting Products

Products are sorted by ui.sortOrder:

[
  { "id": "free", "ui": { "sortOrder": 1 } },
  { "id": "starter", "ui": { "sortOrder": 2 } },
  { "id": "pro", "ui": { "sortOrder": 3 } },
  { "id": "enterprise", "ui": { "sortOrder": 4 } }
]

Lower numbers appear first. Products without sortOrder appear at the end.

TenantCreator

The TenantCreator component includes billing-related functionality for initial organization setup.

Usage

import { TenantCreator } from '@omnibase/shadcn';

export function OnboardingPage() {
  return (
    <TenantCreator
      config={{
        defaultMode: 'create',
        createForm: {
          organizationName: {
            label: 'Organization Name',
            placeholder: 'Acme Corp',
          },
          billingEmail: {
            label: 'Billing Email',
            placeholder: 'billing@acme.com',
          },
        },
      }}
      actions={{
        createOrganizationAction: async (formData) => {
          'use server';
          // Create tenant with billing email
          await tenantsApi.createTenant({
            createTenantRequest: {
              name: formData.get('name') as string,
              billingEmail: formData.get('billing_email') as string,
            },
          });
        },
      }}
    />
  );
}

The billingEmail field triggers automatic Stripe customer creation when the tenant is created.

Styling

Components are built with Tailwind CSS and shadcn/ui. Customize via:

CSS Variables

Override shadcn/ui CSS variables in your global styles:

app/globals.css
:root {
  --primary: 222.2 84% 4.9%;
  --primary-foreground: 210 40% 98%;
  /* ... other variables */
}

className Prop

Add custom classes:

<PricingTable
  products={products}
  className="max-w-5xl mx-auto"
/>

Component Customization

For deeper customization, you can import and modify the component source from @omnibase/shadcn.

Storybook

View component variations in Storybook:

cd public/sdk/component/shadcn
bun run storybook

Available stories:

  • Default — Standard pricing table
  • WithSelectedPrice — Pre-selected subscription
  • YearlyDefault — Annual pricing selected
  • WithoutToggle — No interval toggle
  • SingleProduct — Single product display
  • CustomStyling — Full page example
  • CarouselExample — Multiple products with carousel

On this page