# Introduction billui is an open collection of billing components for React, built on top of shadcn/ui and distributed via the shadcn registry. The goal is simple: **help you ship billing UI faster**. This is my small contribution to the open source community that taught me so much. {'<3'} ## Not a library—an open component distribution Like shadcn/ui, billui is not a typical install-from-NPM library. It's an open collection you can copy, modify, and customize directly in your codebase. You own the code. No wrappers, no overrides, no fighting with the library. Billing UI is deceptively complex. Pricing tables, plan groups, invoice history—each one has edge cases and design decisions that take time to get right. billui gives you battle-tested starting points so you can focus on what makes your product unique. ## Why billui? * **Open source & copy-first**: Direct access to source so you can adapt components to your design system * **Ship faster**: Stop rebuilding the same billing components from scratch * **shadcn compatible**: Install via the shadcn CLI, works with your existing setup * **Composable**: Build complex billing UIs from simple, reusable primitives * **Accessible**: Built with semantic HTML and accessibility in mind * **Type-safe**: Full TypeScript support with exported types # Animated Usage Card import { AnimatedUsageCard, AnimatedUsageCardHeader, AnimatedUsageCardPeriod, AnimatedUsageCardAction, AnimatedUsageCardSummary, AnimatedUsageCardLabels, AnimatedUsageCardLabel, AnimatedUsageCardProgress, AnimatedUsageCardList, AnimatedUsageCardItem, AnimatedUsageCardItemLabel, AnimatedUsageCardItemValue, AnimatedUsageCardMeter, AnimatedUsageCardTotal, } from "@/registry/animated"; Buy Credits Image Generation 120 credits Video Generation 85 credits Upscaling (4x) 62 credits Background Removal 45 credits Style Transfer 35 credits `} >
Buy Credits Image Generation 120 credits Video Generation 85 credits Upscaling (4x) 62 credits Background Removal 45 credits Style Transfer 35 credits
## Installation ```bash npx shadcn@latest add "https://billui.com/r/animated-usage-card.json" ``` ```tsx title="components/ui/animated-usage-card.tsx" "use client"; import { cva, type VariantProps } from "class-variance-authority"; import { ChevronDown } from "lucide-react"; import { motion, useMotionValue, useSpring, useTransform } from "motion/react"; import * as React from "react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; // ... component code ``` ## Examples ### Basic Usage Simple usage card with animated progress bar and itemized list. API Calls Storage `} >
API Calls Storage
### With Animated Meters Use `AnimatedUsageCardMeter` to show individual resource usage with animated progress bars and number interpolation. Manage
`} >
Manage
### Collapsible List Enable `collapsible` on `AnimatedUsageCardList` to show a preview with bouncy expand/collapse animation. Build Minutes Edge Invocations Function Calls Bandwidth `} >
Build Minutes Edge Invocations Function Calls Bandwidth
### With Animated Total The `AnimatedUsageCardTotal` row animates in with a bounce effect and the amount value interpolates smoothly. Pro Plan Extra Seats (3) Storage Add-on `} >
Pro Plan Extra Seats (3) Storage Add-on
### Elevated Variant Use `variant="elevated"` to add a shadow for more visual prominence. Upgrade `} >
Upgrade
## API Reference ### AnimatedUsageCard The root container component with entrance animation. | Prop | Type | Default | | --------- | ------------------------- | ----------- | | `variant` | `"default" \| "elevated"` | `"default"` | ### AnimatedUsageCardPeriod Displays the billing period or days remaining. | Prop | Type | Default | | --------------- | -------- | ------- | | `daysRemaining` | `number` | — | ### AnimatedUsageCardAction Action button in the header. Extends shadcn Button props. | Prop | Type | Default | | --------- | ----------------------------------- | ----------- | | `variant` | `"default" \| "outline" \| "ghost"` | `"outline"` | ### AnimatedUsageCardLabel Displays a labeled amount with optional limit. | Prop | Type | Default | | ---------- | ------------------- | -------- | | `label` | `string` | — | | `amount` | `number` | — | | `limit` | `number` | — | | `currency` | `string` | `"$"` | | `align` | `"left" \| "right"` | `"left"` | ### AnimatedUsageCardProgress Animated progress bar showing usage against a limit with spring physics. | Prop | Type | Default | | ------------- | --------- | ------- | | `value` | `number` | — | | `max` | `number` | `100` | | `showOverage` | `boolean` | `false` | ### AnimatedUsageCardList Container for usage items with optional bouncy expand/collapse animation. | Prop | Type | Default | | ----------------- | --------- | ------- | | `collapsible` | `boolean` | `false` | | `defaultExpanded` | `boolean` | `false` | | `visibleItems` | `number` | `1.5` | | `dividers` | `boolean` | `true` | ### AnimatedUsageCardItem Individual usage line item. | Prop | Type | Default | | ------------- | --------- | ------- | | `highlighted` | `boolean` | `false` | ### AnimatedUsageCardItemValue Value display for a usage item. | Prop | Type | Default | | ---------- | -------- | ------- | | `amount` | `number` | — | | `currency` | `string` | `"$"` | | `unit` | `string` | — | ### AnimatedUsageCardMeter Visual meter showing resource usage with animated progress and number interpolation. | Prop | Type | Default | | ---------------- | --------- | ------- | | `used` | `number` | — | | `limit` | `number` | — | | `label` | `string` | — | | `unit` | `string` | — | | `showPercentage` | `boolean` | `false` | ### AnimatedUsageCardTotal Animated total row with entrance animation and number interpolation. | Prop | Type | Default | | ---------- | -------- | --------- | | `label` | `string` | `"Total"` | | `amount` | `number` | — | | `currency` | `string` | `"$"` | ### useAnimatedUsageCardList Hook to access the list expansion state from child components. ```tsx const { expanded, setExpanded, itemCount, collapsedHeight, expandedHeight } = useAnimatedUsageCardList(); ``` ## Comparison with UsageCard This component is API-compatible with the non-animated `UsageCard`. You can swap between them by changing the import: ```tsx // Without animations import { UsageCard, UsageCardList, ... } from "@/components/ui/usage-card" // With animations import { AnimatedUsageCard, AnimatedUsageCardList, ... } from "@/components/ui/animated-usage-card" ``` The only API difference is: * `AnimatedUsageCardList` uses `visibleItems` (number of items) instead of `collapsedHeight` (pixels) # Billing Address import { Label } from "@/components/ui/label"; import { BillingAddress, BillingAddressInput, BillingAddressCountry, BillingAddressState, } from "@/registry/ui";
`} >
## Installation ```bash npx shadcn@latest add "https://billui.com/r/billing-address.json" ``` ```tsx title="components/ui/billing-address.tsx" "use client"; import * as React from "react"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { COUNTRIES, getStatesForCountry, type IState } from "./billing-address-data"; // ... component code ``` ## Examples ### Minimal Form A compact version with only essential fields.
`} >
### With Server Actions The `name` prop enables native form submission—works with `useActionState` and Server Actions. ```tsx title="billing-form.tsx" "use client"; import { useActionState } from "react"; function BillingForm() { const [state, formAction] = useActionState(updateBilling, null); return (
); } ``` ### Controlled State For real-time validation or conditional UI. ```tsx function ControlledBillingForm() { const [country, setCountry] = useState("US"); const [state, setState] = useState(""); const showTaxMessage = state === "CA"; return ( {showTaxMessage &&

California sales tax applies

}
); } ``` ## API Reference ### BillingAddress Root component that provides country/state synchronization context. | Prop | Type | Default | | ----------------- | ------------------------ | ------- | | `defaultCountry` | `string` | `"US"` | | `country` | `string` | — | | `onCountryChange` | `(code: string) => void` | — | ### BillingAddressInput Input with correct `autocomplete`, `inputMode`, and `spellCheck` based on field type. | Prop | Type | Default | | --------------- | -------------------------------------------------------- | ------- | | `field` | `"name" \| "line1" \| "line2" \| "city" \| "postalCode"` | — | | `name` | `string` | — | | `value` | `string` | — | | `defaultValue` | `string` | `""` | | `onValueChange` | `(value: string) => void` | — | | `trimOnBlur` | `boolean` | `true` | ### BillingAddressCountry Select with all countries. Syncs with root context. | Prop | Type | Default | | --------------- | ------------------------- | ------------------- | | `name` | `string` | — | | `onValueChange` | `(value: string) => void` | — | | `placeholder` | `string` | `"Select country…"` | | `disabled` | `boolean` | `false` | ### BillingAddressState Select when states exist for the country, Input otherwise. Resets when country changes. | Prop | Type | Default | | --------------- | ------------------------- | --------------------- | | `name` | `string` | — | | `value` | `string` | — | | `defaultValue` | `string` | `""` | | `onValueChange` | `(value: string) => void` | — | | `placeholder` | `string` | `"State / Province…"` | | `trimOnBlur` | `boolean` | `true` | | `disabled` | `boolean` | `false` | # Card Input import { CardInputGroup, CardNumberInput, CardExpiryInput, CardCvcInput, } from "@/registry/ui"; `} >
## Installation ```bash npx shadcn@latest add "https://billui.com/r/card-input.json" ``` ```tsx title="components/ui/card-input.tsx" "use client"; import * as React from "react"; import { cn } from "@/lib/utils"; import { CardBrandIcons, type CardBrand } from "./card-icons"; // ... component code ``` ## Examples ### Basic Grouped Inputs Use `CardInputGroup` to combine all card inputs into a single grouped component with shared focus states. `} >
### Individual Inputs Use each input component separately for custom layouts with individual labels and styling.
`} >
## API Reference ### CardInputGroup Container that groups card inputs together with shared focus states and border styling. | Prop | Type | Default | | ------- | --------- | ------- | | `error` | `boolean` | `false` | ### CardNumberInput Card number input with automatic formatting, brand detection, and Luhn validation. | Prop | Type | Default | | -------------------- | ----------------------------------------------------------------- | ------- | | `value` | `string` | — | | `defaultValue` | `string` | `""` | | `onValueChange` | `(value: string) => void` | — | | `onBrandChange` | `(brand: CardBrand) => void` | — | | `onValidationChange` | `(validation: { isValid: boolean; isComplete: boolean }) => void` | — | | `disabled` | `boolean` | `false` | ### CardExpiryInput Expiry date input with MM/YY formatting and expiration validation. | Prop | Type | Default | | -------------------- | ------------------------------------------------------------------------------------- | ------- | | `value` | `string` | — | | `defaultValue` | `string` | `""` | | `onValueChange` | `(value: string) => void` | — | | `onValidationChange` | `(validation: { isValid: boolean; isComplete: boolean; isExpired: boolean }) => void` | — | | `disabled` | `boolean` | `false` | ### CardCvcInput CVC/CVV input that adapts to card brand (3 digits for most cards, 4 for Amex). When used inside `CardInputGroup`, automatically gets the brand from context. | Prop | Type | Default | | --------------- | ------------------------- | ------- | | `value` | `string` | — | | `defaultValue` | `string` | `""` | | `onValueChange` | `(value: string) => void` | — | | `disabled` | `boolean` | `false` | ### CardBrand Type representing supported card brands. ```tsx type CardBrand = | "visa" | "mastercard" | "amex" | "discover" | "diners" | "jcb" | "unionpay" | "unknown"; ``` # Components
Billing Address Card Input Invoice Card Payment Method Plan Card Plan Group Pricing Table Usage Card Animated Usage Card
*** Can't find what you need? Check our [GitHub repository](https://github.com/commet-labs/billui) and open an issue to request a new component. # Invoice Card import { InvoiceCard, InvoiceCardIcon, InvoiceCardContent, InvoiceCardHeader, InvoiceCardNumber, InvoiceCardStatus, InvoiceCardDate, InvoiceCardAmount, InvoiceCardActions, InvoiceCardAction, } from "@/registry/ui"; INV-2024-001 INV-2024-002 INV-2024-003 `} >
INV-2024-001 INV-2024-002 INV-2024-003
## Installation ```bash npx shadcn@latest add "https://billui.com/r/invoice-card.json" ``` ```tsx title="components/ui/invoice-card.tsx" import { cva, type VariantProps } from "class-variance-authority"; import { Download, ExternalLink, FileText } from "lucide-react"; import * as React from "react"; import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; const invoiceCardVariants = cva( "relative flex items-center gap-4 rounded-xl border bg-card p-4 text-card-foreground transition-colors hover:bg-accent/50", { variants: { variant: { default: "border-border", compact: "gap-3 p-3", }, }, defaultVariants: { variant: "default", }, }, ); interface InvoiceCardProps extends React.HTMLAttributes, VariantProps {} const InvoiceCard = React.forwardRef( ({ className, variant, ...props }, ref) => (
), ); InvoiceCard.displayName = "InvoiceCard"; // ... rest of component code ``` ## Examples ### Pending Status INV-2024-002
`} >
INV-2024-002
### Failed Status INV-2024-003 `} >
INV-2024-003
### Refunded Status INV-2024-004 `} >
INV-2024-004
### Draft Status INV-2024-005 `} >
INV-2024-005
### Compact Variant Use the `compact` variant for denser layouts. INV-2024-001 `} >
INV-2024-001
### Long Date Format Use the `format="long"` prop for more detailed date display. INV-2024-001 `} >
INV-2024-001
### With Actions Include download or view action buttons. INV-2024-001 `} >
INV-2024-001
### Custom Currency Display amounts in different currencies. INV-2024-001 `} >
INV-2024-001
## API Reference ### InvoiceCard The root container component. | Prop | Type | Default | | --------- | ------------------------ | ----------- | | `variant` | `"default" \| "compact"` | `"default"` | ### InvoiceCardStatus Badge showing the invoice status. | Prop | Type | Default | | -------- | ---------------------------------------------------------- | ------- | | `status` | `"paid" \| "pending" \| "failed" \| "refunded" \| "draft"` | — | ### InvoiceCardDate Displays the invoice date. | Prop | Type | Default | | -------- | ------------------- | --------- | | `date` | `Date \| string` | — | | `format` | `"short" \| "long"` | `"short"` | ### InvoiceCardAmount Displays the invoice amount. | Prop | Type | Default | | ---------- | -------- | ------- | | `amount` | `number` | — | | `currency` | `string` | `"$"` | ### InvoiceCardAction Action button for download or view. | Prop | Type | Default | | -------- | ---------------------- | ------------ | | `action` | `"download" \| "view"` | `"download"` | # Payment Method import { PaymentMethod, PaymentMethodIcon, PaymentMethodDetails, PaymentMethodNumber, PaymentMethodExpiry, PaymentMethodBadge, PaymentMethodActions, PaymentMethodAction, } from "@/registry/ui"; Default `} >
Default
## Installation ```bash npx shadcn@latest add "https://billui.com/r/payment-method.json" ``` ```tsx title="components/ui/payment-method.tsx" import { cva, type VariantProps } from "class-variance-authority"; import { CreditCard, MoreHorizontal, Pencil, Trash2 } from "lucide-react"; import * as React from "react"; import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; // ... component code ``` ## Examples ### Card Brands Each card brand displays with its official logo. `} >
### Selected State Use the `selected` variant to highlight the active payment method. Default `} >
Default
### Expired Card Show expired cards with visual indication. `} >
### With Actions Include edit, delete, or more options buttons. `} >
## API Reference ### PaymentMethod The root container for the payment method card. | Prop | Type | Default | Description | | --------- | ------------------------- | ----------- | -------------------- | | `variant` | `"default" \| "selected"` | `"default"` | Visual style variant | ### PaymentMethodIcon Displays the card brand logo (SVGs embedded inline for easy distribution). | Prop | Type | Default | Description | | ------- | ----------------------------------------------- | ----------- | ----------- | | `brand` | `"visa" \| "mastercard" \| "amex" \| "generic"` | `"generic"` | Card brand | ### PaymentMethodNumber Displays the masked card number. | Prop | Type | Default | Description | | ------- | -------- | ------- | ------------------------- | | `last4` | `string` | - | Last 4 digits of the card | ### PaymentMethodExpiry Displays the card expiration date. | Prop | Type | Default | Description | | --------- | --------- | ------- | ----------------------- | | `month` | `number` | - | Expiration month (1-12) | | `year` | `number` | - | Expiration year | | `expired` | `boolean` | `false` | Shows expired indicator | ### PaymentMethodAction Action button for the payment method. | Prop | Type | Default | Description | | -------- | ------------------------------ | -------- | -------------- | | `action` | `"edit" \| "delete" \| "more"` | `"more"` | Type of action | # Plan Card import { PlanCard, PlanCardHeader, PlanCardBadge, PlanCardTitle, PlanCardDescription, PlanCardPrice, PlanCardFeatures, PlanCardFeature, PlanCardAction, } from "@/registry/ui"; Most Popular Pro For growing teams Unlimited projects Advanced analytics Priority support Custom integrations Get Started `} > Most Popular Pro For growing teams Unlimited projects Advanced analytics Priority support Custom integrations Get Started ## Installation ```bash npx shadcn@latest add "https://billui.com/r/plan-card.json" ``` ```tsx title="components/ui/plan-card.tsx" import { cva, type VariantProps } from "class-variance-authority"; import { Check, X } from "lucide-react"; import * as React from "react"; import { cn } from "@/lib/utils"; import { Badge } from "@/registry/shadcn/badge"; import { Button } from "@/registry/shadcn/button"; const planCardVariants = cva( "relative flex flex-col rounded-2xl border bg-card text-card-foreground transition-all duration-200 min-w-[280px]", { variants: { variant: { default: "border-border", highlighted: "border-primary shadow-lg shadow-primary/10 ring-1 ring-primary", compact: "border-border p-4", }, size: { default: "p-6", lg: "p-8", }, }, defaultVariants: { variant: "default", size: "default", }, }, ); interface PlanCardProps extends React.HTMLAttributes, VariantProps {} const PlanCard = React.forwardRef( ({ className, variant, size, ...props }, ref) => (
), ); PlanCard.displayName = "PlanCard"; const PlanCardHeader = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
)); PlanCardHeader.displayName = "PlanCardHeader"; interface PlanCardBadgeProps extends React.ComponentPropsWithoutRef {} const PlanCardBadge = React.forwardRef( ({ className, ...props }, ref) => ( ), ); PlanCardBadge.displayName = "PlanCardBadge"; const PlanCardTitle = React.forwardRef< HTMLHeadingElement, React.HTMLAttributes >(({ className, ...props }, ref) => (

)); PlanCardTitle.displayName = "PlanCardTitle"; const PlanCardDescription = React.forwardRef< HTMLParagraphElement, React.HTMLAttributes >(({ className, ...props }, ref) => (

)); PlanCardDescription.displayName = "PlanCardDescription"; interface PlanCardPriceProps extends React.HTMLAttributes { amount: number; currency?: string; period?: "month" | "year" | "once"; originalAmount?: number; } const PlanCardPrice = React.forwardRef( ( { className, amount, currency = "$", period = "month", originalAmount, ...props }, ref, ) => { const periodLabel = { month: "/mo", year: "/yr", once: "", }; return (

{originalAmount && ( {currency} {originalAmount} )} {currency} {amount} {period !== "once" && ( {periodLabel[period]} )}
); }, ); PlanCardPrice.displayName = "PlanCardPrice"; const PlanCardFeatures = React.forwardRef< HTMLUListElement, React.HTMLAttributes >(({ className, ...props }, ref) => (
    )); PlanCardFeatures.displayName = "PlanCardFeatures"; interface PlanCardFeatureProps extends React.HTMLAttributes { included?: boolean; } const PlanCardFeature = React.forwardRef( ({ className, included = true, children, ...props }, ref) => (
  • {included ? ( ) : ( )} {children}
  • ), ); PlanCardFeature.displayName = "PlanCardFeature"; interface PlanCardActionProps extends React.ComponentPropsWithoutRef {} const PlanCardAction = React.forwardRef( ({ className, ...props }, ref) => (

`} >
Buy Credits Image Generation 120 credits Video Generation 85 credits Upscaling (4x) 62 credits Background Removal 45 credits Style Transfer 35 credits
## Installation ```bash npx shadcn@latest add "https://billui.com/r/usage-card.json" ``` ```tsx title="components/ui/usage-card.tsx" "use client"; import { cva, type VariantProps } from "class-variance-authority"; import { ChevronDown, ChevronUp } from "lucide-react"; import * as React from "react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; // ... component code ``` ## Examples ### Basic Usage Simple usage card with progress bar and itemized list. API Calls Storage
`} >
API Calls Storage
### Elevated Variant Use `variant="elevated"` to add a shadow for more visual prominence. Upgrade `} >
Upgrade
### With Usage Meters Use `UsageCardMeter` to show individual resource usage with visual progress bars. Manage
`} >
Manage
### Collapsible List Enable `collapsible` on `UsageCardList` to show a preview with expand/collapse functionality. Build Minutes Edge Invocations Function Calls Bandwidth `} >
Build Minutes Edge Invocations Function Calls Bandwidth
### With Highlighted Items Use `highlighted` prop on items to create zebra striping. Compute Storage Bandwidth Functions `} >
Compute Storage Bandwidth Functions
### With Total Row Add a `UsageCardTotal` at the bottom to show the sum of all charges. Pro Plan Extra Seats (3) Storage Add-on `} >
Pro Plan Extra Seats (3) Storage Add-on
### Overage Warning The progress bar turns red when usage exceeds the limit. Billing cycle exceeded Add Credits `} >
Billing cycle exceeded Add Credits
### Custom Currency Display amounts in different currencies. Almacenamiento Transferencia `} >
Almacenamiento Transferencia
### Custom Period Text Pass custom children to `UsageCardPeriod` for custom text. Jan 1 - Jan 31, 2024 View History `} >
Jan 1 - Jan 31, 2024 View History
## API Reference ### UsageCard The root container component. | Prop | Type | Default | | --------- | ------------------------- | ----------- | | `variant` | `"default" \| "elevated"` | `"default"` | ### UsageCardPeriod Displays the billing period or days remaining. | Prop | Type | Default | | --------------- | -------- | ------- | | `daysRemaining` | `number` | — | ### UsageCardAction Action button in the header. | Prop | Type | Default | | --------- | ----------------------------------- | ----------- | | `variant` | `"default" \| "outline" \| "ghost"` | `"outline"` | ### UsageCardLabel Displays a labeled amount with optional limit. | Prop | Type | Default | | ---------- | ------------------- | -------- | | `label` | `string` | — | | `amount` | `number` | — | | `limit` | `number` | — | | `currency` | `string` | `"$"` | | `align` | `"left" \| "right"` | `"left"` | ### UsageCardProgress Progress bar showing usage against a limit. | Prop | Type | Default | | ------------- | --------- | ------- | | `value` | `number` | — | | `max` | `number` | `100` | | `showOverage` | `boolean` | `false` | ### UsageCardList Container for usage items with optional collapsing. | Prop | Type | Default | | ----------------- | --------- | ------- | | `collapsible` | `boolean` | `false` | | `defaultExpanded` | `boolean` | `false` | | `collapsedHeight` | `number` | `88` | | `dividers` | `boolean` | `true` | ### UsageCardItem Individual usage line item. | Prop | Type | Default | | ------------- | --------- | ------- | | `highlighted` | `boolean` | `false` | ### UsageCardItemValue Value display for a usage item. | Prop | Type | Default | | ---------- | -------- | ------- | | `amount` | `number` | — | | `currency` | `string` | `"$"` | | `unit` | `string` | — | ### UsageCardMeter Visual meter showing resource usage with warning states. | Prop | Type | Default | | ---------------- | --------- | ------- | | `used` | `number` | — | | `limit` | `number` | — | | `label` | `string` | — | | `unit` | `string` | — | | `showPercentage` | `boolean` | `false` | ### UsageCardTotal Total row at the bottom of the card. | Prop | Type | Default | | ---------- | -------- | --------- | | `label` | `string` | `"Total"` | | `amount` | `number` | — | | `currency` | `string` | `"$"` | ### useUsageCardList Hook to access the list expansion state from child components. ```tsx const { expanded, setExpanded, itemCount, collapsedHeight } = useUsageCardList(); ```