Pricing Tier Comparison Matrix
blockmarketingComprehensive pricing tier comparison matrix with billing cycle discount toggles, prominent recommended plan, and granular capability matrix table.
Installation
$pnpm dlx uipkge-ng@latest add pricing-tier-comparison-matrix$npx uipkge-ng@latest add pricing-tier-comparison-matrix$yarn dlx uipkge-ng@latest add pricing-tier-comparison-matrix$bunx uipkge-ng@latest add pricing-tier-comparison-matrixManifest:
https://uipkge.dev/r/angular/pricing-tier-comparison-matrix.jsonInstalls to:src/app/components/blocks/pricing-tier-comparison-matrix/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.
PricingTierPlaninterface PricingTierPlan {
id: string
name: string
description: string
monthlyPrice: number
annualPrice: number
highlight: boolean
badge?: string
ctaText: string
ctaVariant: ButtonVariant
}TierFeatureCellinterface TierFeatureCell {
name: string
tooltip: string
community: boolean | string
pro: boolean | string
enterprise: boolean | string
}TierComparisonSectioninterface TierComparisonSection {
category: string
features: TierFeatureCell[]
}npm dependencies
Used by
Files installed (2)
src/app/components/blocks/pricing-tier-comparison-matrix/pricing-tier-comparison-matrix.component.ts13.1 kB
// Comprehensive pricing tier comparison matrix with billing cycle discount toggles, // prominent recommended plan, and granular capability matrix table. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core' import { ArrowRight, Check, LucideAngularModule, Minus, ShieldCheck } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent } from '@/ui/card/card.component' import type { ButtonVariant } from '@/ui/button/button.component' export type BillingCycle = 'monthly' | 'annual' export interface PricingTierPlan { id: string name: string description: string monthlyPrice: number annualPrice: number highlight: boolean badge?: string ctaText: string ctaVariant: ButtonVariant } const plans: PricingTierPlan[] = [ { id: 'community', name: 'Community OSS', description: 'For indie hackers and developers building open source products.', monthlyPrice: 0, annualPrice: 0, highlight: false, ctaText: 'Start Building Free', ctaVariant: 'outline', }, { id: 'pro', name: 'Pro Team', description: 'For growing startup engineering teams that require high velocity.', monthlyPrice: 29, annualPrice: 24, highlight: true, badge: 'Most Popular', ctaText: 'Claim Pro License', ctaVariant: 'default', }, { id: 'enterprise', name: 'Enterprise Scale', description: 'Dedicated registry syncing, SSO, SOC2 compliance audits, and SLA.', monthlyPrice: 119, annualPrice: 99, highlight: false, ctaText: 'Talk to Sales', ctaVariant: 'outline', }, ] export interface TierFeatureCell { name: string tooltip: string community: boolean | string pro: boolean | string enterprise: boolean | string } export interface TierComparisonSection { category: string features: TierFeatureCell[] } const comparisonData: TierComparisonSection[] = [ { category: 'Registry & Core Primitives', features: [ { name: 'Full AST Component Source Access', tooltip: 'Raw SFC and TSX source files copied directly into your repository.', community: true, pro: true, enterprise: true, }, { name: 'Dual-Framework Parity (Vue + React)', tooltip: 'Identical DOM semantics and CVA tokens across both ecosystems.', community: true, pro: true, enterprise: true, }, { name: 'Curated Marketing & SaaS Blocks', tooltip: 'Access to 450+ production-grade unbundled layout blocks.', community: '100+ Blocks', pro: 'All 450+ Blocks', enterprise: 'All Blocks + Custom', }, { name: 'Tailwind CSS v4 OKLCH Token System', tooltip: 'Hardware-calibrated color spaces and spring curves.', community: true, pro: true, enterprise: true, }, ], }, { category: 'Enterprise & Security Compliance', features: [ { name: 'Private Registry Mirroring', tooltip: 'Host your organization’s customized internal registry behind a firewall.', community: false, pro: '1 Private Repo', enterprise: 'Unlimited Private Hubs', }, { name: 'SOC2 & ISO 27001 Audit Packs', tooltip: 'Pre-certified security documentation and architecture proofs.', community: false, pro: false, enterprise: true, }, { name: 'Guaranteed 99.99% Registry CDN SLA', tooltip: 'Global multi-region edge distribution uptime guarantee.', community: false, pro: '99.9% SLA', enterprise: '99.99% High Availability', }, { name: 'Dedicated Design Engineering Support', tooltip: 'Direct Slack / Discord channel with core design system maintainers.', community: false, pro: 'Priority Email', enterprise: 'Dedicated Slack Channel', }, ], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-pricing-tier-comparison-matrix, [ui-pricing-tier-comparison-matrix]', standalone: true, // React renders the root <section> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent], template: ` <section data-slot="pricing-tier-comparison-matrix" [class]="rootClass"> <div class="mx-auto max-w-7xl space-y-16"> <!-- Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <span ui-badge variant="secondary" class="gap-1.5 px-3 py-1 font-mono text-xs shadow-xs"> <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5" /> Predictable Pricing </span> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl"> Zero seat taxes. Own your source code forever. </h2> <p class="text-muted-foreground text-base"> Choose the tier that fits your engineering team's delivery scale and compliance needs. </p> <!-- Billing Toggle --> <div class="flex items-center justify-center gap-3 pt-4"> <span class="font-mono text-xs" [class]="cycleLabelClass('monthly')">Monthly</span> <div class="border-border bg-card relative flex items-center rounded-full border p-1"> <button type="button" [class]="toggleButtonClass('monthly')" (click)="setBilling('monthly')"> Monthly </button> <button type="button" [class]="toggleButtonClass('annual')" (click)="setBilling('annual')"> <span>Annual</span> <span class="bg-success rounded-full px-1.5 py-0.5 text-xs font-bold text-white">Save 20%</span> </button> </div> </div> </div> <!-- Pricing Plan Cards Grid (3 Columns) --> <div class="grid grid-cols-1 items-stretch gap-6 md:grid-cols-3"> @for (plan of plans; track plan.id) { <div ui-card [class]="planCardClass(plan)"> <!-- Top Badge --> @if (plan.badge) { <div class="absolute -top-3 left-1/2 -translate-x-1/2"> <span ui-badge class="px-3 py-0.5 font-mono text-xs tracking-wider uppercase shadow-md"> {{ plan.badge }} </span> </div> } <div class="space-y-4"> <div> <h3 class="text-foreground font-mono text-xl font-bold">{{ plan.name }}</h3> <p class="text-muted-foreground mt-1 min-h-[36px] text-xs">{{ plan.description }}</p> </div> <!-- Price display --> <div class="flex items-baseline gap-1.5 font-mono"> <span class="text-foreground text-4xl font-bold">{{ priceLabel(plan) }}</span> <span class="text-muted-foreground text-xs">/ month</span> </div> <p class="text-muted-foreground font-mono text-xs">{{ billedNote(plan) }}</p> </div> <button ui-button [variant]="plan.ctaVariant" class="h-10 w-full gap-1.5 font-mono text-xs shadow-xs"> <span>{{ plan.ctaText }}</span> <lucide-icon [img]="ArrowRight" class="size-3.5" /> </button> </div> } </div> <!-- Deep Feature Comparison Matrix Table --> <div class="space-y-6"> <div class="text-center"> <h3 class="text-foreground font-mono text-xl font-bold">Detailed Feature Comparison</h3> <p class="text-muted-foreground mt-1 text-xs">Full granular matrix of capabilities and entitlements.</p> </div> <div ui-card class="border-border bg-card overflow-hidden rounded-2xl text-left shadow-sm"> <div class="overflow-x-auto"> <table class="w-full border-collapse text-left text-xs"> <thead> <tr class="border-border bg-muted/40 text-muted-foreground border-b font-mono"> <th class="w-1/2 p-4 font-semibold">Capability</th> <th class="p-4 text-center font-semibold">Community</th> <th class="text-primary p-4 text-center font-bold font-semibold">Pro Team</th> <th class="p-4 text-center font-semibold">Enterprise</th> </tr> </thead> <tbody> @for (section of comparisonData; track section.category) { <tr class="bg-muted/20 border-border/80 border-b"> <td colspan="4" class="text-muted-foreground p-3 px-4 font-mono text-xs font-bold tracking-wider uppercase" > {{ section.category }} </td> </tr> @for (row of section.features; track row.name) { <tr class="border-border/60 hover:bg-muted/10 border-b transition-colors"> <td class="p-4"> <div class="text-foreground font-medium">{{ row.name }}</div> <div class="text-muted-foreground mt-0.5 text-xs">{{ row.tooltip }}</div> </td> <td class="p-4 text-center font-mono"> @if (row.community === true) { <lucide-icon [img]="Check" class="text-success mx-auto size-4" /> } @else if (row.community === false) { <lucide-icon [img]="Minus" class="text-muted-foreground/40 mx-auto size-4" /> } @else { <span class="text-muted-foreground">{{ row.community }}</span> } </td> <td class="p-4 text-center font-mono font-semibold"> @if (row.pro === true) { <lucide-icon [img]="Check" class="text-success mx-auto size-4" /> } @else if (row.pro === false) { <lucide-icon [img]="Minus" class="text-muted-foreground/40 mx-auto size-4" /> } @else { <span class="text-primary">{{ row.pro }}</span> } </td> <td class="p-4 text-center font-mono"> @if (row.enterprise === true) { <lucide-icon [img]="Check" class="text-success mx-auto size-4" /> } @else if (row.enterprise === false) { <lucide-icon [img]="Minus" class="text-muted-foreground/40 mx-auto size-4" /> } @else { <span class="text-foreground font-semibold">{{ row.enterprise }}</span> } </td> </tr> } } </tbody> </table> </div> </div> </div> </div> </section> `, }) export class UiPricingTierComparisonMatrixComponent { protected readonly ArrowRight = ArrowRight protected readonly Check = Check protected readonly Minus = Minus protected readonly ShieldCheck = ShieldCheck readonly plans = plans readonly comparisonData = comparisonData @Input() set defaultBilling(v: BillingCycle | undefined) { if (v) this.billing.set(v) } @Input('class') className?: string readonly billing = signal<BillingCycle>('annual') get rootClass(): string { return cn('bg-background relative overflow-hidden px-4 py-16 sm:px-6 sm:py-24 lg:px-8', this.className) } cycleLabelClass(cycle: BillingCycle): string { return cn(this.billing() === cycle ? 'text-foreground font-bold' : 'text-muted-foreground') } toggleButtonClass(cycle: BillingCycle): string { return cn( 'relative z-10 rounded-full px-3 py-1 font-mono text-xs transition-colors', cycle === 'annual' && 'flex items-center gap-1.5', this.billing() === cycle ? 'bg-primary text-primary-foreground font-semibold shadow-xs' : 'text-muted-foreground', ) } planCardClass(plan: PricingTierPlan): string { return cn( 'border-border bg-card relative flex flex-col justify-between space-y-6 rounded-2xl p-6 text-left shadow-xl transition-colors sm:p-8', plan.highlight ? 'border-primary/80 ring-primary/20 scale-[1.02] shadow-sm ring-2' : 'hover:border-border/80', ) } billedNote(plan: PricingTierPlan): string { if (this.billing() === 'annual' && plan.annualPrice > 0) return `Billed annually ($${plan.annualPrice * 12}/yr)` return 'Billed monthly' } priceLabel(plan: PricingTierPlan): string { return `$${this.billing() === 'annual' ? plan.annualPrice : plan.monthlyPrice}` } setBilling(cycle: BillingCycle): void { this.billing.set(cycle) } }src/app/components/blocks/pricing-tier-comparison-matrix/index.ts0.2 kB
export { UiPricingTierComparisonMatrixComponent, type BillingCycle, type PricingTierPlan, type TierComparisonSection, type TierFeatureCell, } from './pricing-tier-comparison-matrix.component'
Raw manifest:https://uipkge.dev/r/angular/pricing-tier-comparison-matrix.json