Pricing — Per-Plan Comparison
blockmarketingPlan-first comparison that renders each plan as its own card of included and excluded capabilities, the layout a wide matrix should degrade to on a phone.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/pricing-matrix-plan-cards.json$npx shadcn@latest add https://uipkge.dev/r/lit/pricing-matrix-plan-cards.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/pricing-matrix-plan-cards.json$bunx shadcn@latest add https://uipkge.dev/r/lit/pricing-matrix-plan-cards.jsonManifest:
https://uipkge.dev/r/lit/pricing-matrix-plan-cards.jsonInstalls to:src/uip/blocks/pricing-matrix-plan-cards/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-pricing-matrix-plan-cards': UipPricingMatrixPlanCards
}
}Files installed (1)
src/uip/blocks/pricing-matrix-plan-cards/pricing-matrix-plan-cards.ts4.9 kB
import { LitElement, css, html } from 'lit' import { Check, Minus } from 'lucide' import { tailwind } from '../../lib/styles' import { ThemeController } from '../../lib/theme' import { icon } from '../../lib/icon' // Side-effect imports: register the composed elements for standalone use // (consumers who install only this block still get working primitives). import '../../components/badge/badge' import '../../components/button/button' import '../../components/card/card' import '../../components/separator/separator' const capabilities = [ 'Certified metric definitions', 'Connected warehouses', 'Query budget per team', 'Row-level access control', 'SCIM provisioning', 'Audit log export', 'Embedded dashboards', 'Data residency choice', 'Customer-managed keys', 'Named support engineer', ] const plans = [ { name: 'Team', price: '$0', cadence: 'while in beta', included: [0, 1], note: 'For one team proving it out.' }, { name: 'Business', price: '$1,400', cadence: 'per month', included: [0, 1, 2, 3, 4, 5, 6], note: 'For finance and analytics running together.', featured: true, }, { name: 'Enterprise', price: 'Custom', cadence: 'annual', included: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], note: 'For regulated estates and multi-region.', }, ] /** * <uip-pricing-matrix-plan-cards> — the registry PricingMatrixPlanCards block * as a web component. * * Class strings are React's PricingMatrixPlanCards verbatim. The section is * static (React holds no state); inclusion is derived from the shared * capability list exactly like React. */ export class UipPricingMatrixPlanCards extends LitElement { static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true } // :host display can't be a utility class (the host has no template of its own). static styles = [tailwind, css`:host { display: block; }`] constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'pricing-matrix-plan-cards') } render() { return html` <section data-slot="pricing-matrix-plan-cards" class="bg-background"> <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div class="max-w-2xl"> <uip-badge variant="secondary">Plans</uip-badge> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">What each plan actually includes</h2> <p class="text-muted-foreground mt-3 text-lg"> The same list on every card, with what is missing shown rather than omitted. </p> </div> <div class="mt-10 grid gap-4 lg:grid-cols-3"> ${plans.map( (plan) => html` <uip-card class=${plan.featured ? 'border-primary' : ''}> <uip-card-content class="flex h-full flex-col p-6"> <div class="flex items-center justify-between gap-3"> <p class="text-sm font-semibold">${plan.name}</p> ${plan.featured ? html`<uip-badge variant="secondary">Most chosen</uip-badge>` : ''} </div> <p class="font-display mt-4 text-3xl font-bold tracking-tight">${plan.price}</p> <p class="text-muted-foreground mt-1 text-xs">${plan.cadence}</p> <p class="text-muted-foreground mt-3 text-sm leading-relaxed">${plan.note}</p> <uip-separator class="[&::part(base)]:my-5"></uip-separator> <ul class="space-y-2.5"> ${capabilities.map((capability, index) => { const included = plan.included.includes(index) return html` <li class=${`flex items-start gap-2.5 text-sm ${included ? '' : 'text-muted-foreground/60'}`} > ${included ? icon(Check, 'check', 'text-success mt-0.5 size-4 shrink-0') : icon(Minus, 'minus', 'text-muted-foreground/40 mt-0.5 size-4 shrink-0')} <span>${capability}</span> </li> ` })} </ul> <uip-button variant=${plan.featured ? 'default' : 'outline'} class="mt-auto pt-0 [&]:mt-8"> ${plan.name === 'Enterprise' ? 'Talk to us' : `Start on ${plan.name}`} </uip-button> </uip-card-content> </uip-card> `, )} </div> </div> </section> ` } } customElements.get('uip-pricing-matrix-plan-cards') || customElements.define('uip-pricing-matrix-plan-cards', UipPricingMatrixPlanCards) declare global { interface HTMLElementTagNameMap { 'uip-pricing-matrix-plan-cards': UipPricingMatrixPlanCards } }
Raw manifest:https://uipkge.dev/r/lit/pricing-matrix-plan-cards.json