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-jsPricingTable
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
| Prop | Type | Default | Description |
|---|---|---|---|
products | Product[] | Required | Array of products from Stripe config |
selectedPriceId | string | — | Currently selected price (shows visual indicator) |
onPriceSelect | (priceId: string, productId: string) => void | — | Callback when a price is selected |
className | string | — | Additional CSS classes |
showPricingToggle | boolean | false | Show month/year interval toggle |
defaultInterval | "month" | "year" | "month" | Default billing interval |
Features
Responsive Design
The component automatically adapts to screen size:
| Screen Size | Behavior |
|---|---|
| Mobile | Single-card carousel with swipe navigation |
| Tablet | Single-card carousel |
| Desktop | 3-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
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:
| Currency | Symbol | Code |
|---|---|---|
| US Dollar | $ | usd |
| Euro | € | eur |
| British Pound | £ | gbp |
| Japanese Yen | ¥ | jpy |
| Canadian Dollar | CA$ | cad |
| Australian Dollar | A$ | 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:
: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 storybookAvailable stories:
Default— Standard pricing tableWithSelectedPrice— Pre-selected subscriptionYearlyDefault— Annual pricing selectedWithoutToggle— No interval toggleSingleProduct— Single product displayCustomStyling— Full page exampleCarouselExample— Multiple products with carousel
Related
- Products & Prices — Configure product UI
- Checkout & Portal — Handle price selection
- Subscriptions — Display current subscription