Billing Card
Current-plan summary with price, a usage meter, billing detail rows and footer actions. Compound API for billing settings.
Example
Installation
npx shadcn@latest add https://hirael.com/r/billing-card.jsonAPI
<BillingCard />
+ native element propsNo props of its own — forwards everything to the underlying element.
<BillingCardHeader />
+ native element propsNo props of its own — forwards everything to the underlying element.
<BillingCardEyebrow />
+ native element propsNo props of its own — forwards everything to the underlying element.
<BillingCardPlan />
+ native element propsNo props of its own — forwards everything to the underlying element.
<BillingCardPrice />
+ native element props| Prop | Type | Default |
|---|---|---|
cycle | React.ReactNode | — |
<BillingCardMeter />
+ native element props| Prop | Type | Default |
|---|---|---|
value* | number | — |
max* | number | — |
label | React.ReactNode | — |
<BillingCardRow />
+ native element props| Prop | Type | Default |
|---|---|---|
label* | React.ReactNode | — |
<BillingCardFooter />
+ native element propsNo props of its own — forwards everything to the underlying element.
Component source
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
type BillingCardProps = React.ComponentProps<"div">;
function BillingCard({ className, ...props }: BillingCardProps) {
return (
<div
data-slot="billing-card"
className={cn(
"flex flex-col gap-4 rounded-lg border border-border bg-card p-5 text-card-foreground",
className,
)}
{...props}
/>
);
}
type BillingCardHeaderProps = React.ComponentProps<"div">;
function BillingCardHeader({ className, ...props }: BillingCardHeaderProps) {
return (
<div
data-slot="billing-card-header"
className={cn("flex items-start justify-between gap-3", className)}
{...props}
/>
);
}
type BillingCardEyebrowProps = React.ComponentProps<"p">;
function BillingCardEyebrow({ className, ...props }: BillingCardEyebrowProps) {
return (
<p
data-slot="billing-card-eyebrow"
className={cn(
"font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground",
className,
)}
{...props}
/>
);
}
type BillingCardPlanProps = React.ComponentProps<"p">;
function BillingCardPlan({ className, ...props }: BillingCardPlanProps) {
return (
<p
data-slot="billing-card-plan"
className={cn(
"text-xl font-semibold tracking-tight text-foreground",
className,
)}
{...props}
/>
);
}
type BillingCardPriceProps = React.ComponentProps<"p"> & {
cycle?: React.ReactNode;
};
function BillingCardPrice({
cycle,
className,
children,
...props
}: BillingCardPriceProps) {
return (
<p
data-slot="billing-card-price"
className={cn("text-end text-sm text-foreground", className)}
{...props}
>
<span className="text-lg font-semibold tracking-tight">{children}</span>
{cycle ? <span className="text-muted-foreground"> / {cycle}</span> : null}
</p>
);
}
type BillingCardMeterProps = React.ComponentProps<"div"> & {
value: number;
max: number;
label?: React.ReactNode;
};
function BillingCardMeter({
value,
max,
label,
className,
...props
}: BillingCardMeterProps) {
const pct = Math.max(0, Math.min(100, max ? (value / max) * 100 : 0));
return (
<div
data-slot="billing-card-meter"
className={cn("flex flex-col gap-1.5", className)}
{...props}
>
{label ? (
<div className="flex items-center justify-between text-xs">
<span className="text-muted-foreground">{label}</span>
<span className="font-mono tabular-nums text-foreground">
{value} / {max}
</span>
</div>
) : null}
<div
role="progressbar"
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={max}
className="h-1.5 w-full overflow-hidden rounded-full bg-muted"
>
<div
className="h-full rounded-full bg-foreground transition-[width] duration-300"
style={{ width: `${pct}%` }}
/>
</div>
</div>
);
}
type BillingCardRowProps = React.ComponentProps<"div"> & {
label: React.ReactNode;
};
function BillingCardRow({
label,
className,
children,
...props
}: BillingCardRowProps) {
return (
<div
data-slot="billing-card-row"
className={cn(
"flex items-center justify-between gap-3 text-sm",
className,
)}
{...props}
>
<span className="text-muted-foreground">{label}</span>
<span className="text-foreground">{children}</span>
</div>
);
}
type BillingCardFooterProps = React.ComponentProps<"div">;
function BillingCardFooter({ className, ...props }: BillingCardFooterProps) {
return (
<div
data-slot="billing-card-footer"
className={cn(
"flex items-center gap-2 border-t border-border pt-4",
className,
)}
{...props}
/>
);
}
export {
BillingCard,
BillingCardHeader,
BillingCardEyebrow,
BillingCardPlan,
BillingCardPrice,
BillingCardMeter,
BillingCardRow,
BillingCardFooter,
};