Pricing — Sticky Header Matrix
blockmarketingLong feature matrix whose plan header row pins to the top while the body scrolls, so the column a checkmark belongs to stays identifiable at any scroll depth.
Installation
$pnpm dlx uipkge-ng@latest add pricing-matrix-sticky-header$npx uipkge-ng@latest add pricing-matrix-sticky-header$yarn dlx uipkge-ng@latest add pricing-matrix-sticky-header$bunx uipkge-ng@latest add pricing-matrix-sticky-headerManifest:
https://uipkge.dev/r/angular/pricing-matrix-sticky-header.jsonInstalls to:src/app/components/blocks/pricing-matrix-sticky-header/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.
PricingStickyRowinterface PricingStickyRow {
feature: string
values: (boolean | string)[]
}npm dependencies
Used by
Files installed (2)
src/app/components/blocks/pricing-matrix-sticky-header/pricing-matrix-sticky-header.component.ts5.4 kB
// Long feature matrix whose plan header row pins to the top while the body scrolls, // so the column a checkmark belongs to stays identifiable at any scroll depth. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { Check, LucideAngularModule, Minus } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export interface PricingStickyRow { feature: string values: (boolean | string)[] } const plans = ['Team', 'Business', 'Enterprise'] // Cells are true / false / a string, so a plan can state a limit rather than // only claiming a feature exists. const rows: PricingStickyRow[] = [ { feature: 'Certified metric definitions', values: ['50', '500', 'Unlimited'] }, { feature: 'Connected warehouses', values: ['1', '3', 'Unlimited'] }, { feature: 'Query budget per team', values: [false, true, true] }, { feature: 'Row-level access control', values: [false, true, true] }, { feature: 'SCIM provisioning', values: [false, true, true] }, { feature: 'Audit log export', values: [false, '90 days', 'Unlimited'] }, { feature: 'Period locking', values: [false, true, true] }, { feature: 'Embedded dashboards', values: [false, true, true] }, { feature: 'Data residency choice', values: [false, false, true] }, { feature: 'Customer-managed keys', values: [false, false, true] }, { feature: 'Private link', values: [false, false, true] }, { feature: 'Named support engineer', values: [false, false, true] }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-pricing-matrix-sticky-header, [ui-pricing-matrix-sticky-header]', 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, UiCardContentComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <section data-slot="pricing-matrix-sticky-header" [class]="rootClass"> <div class="mx-auto max-w-4xl px-6 py-20 lg:py-28"> <span ui-badge variant="secondary">Compare plans</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Every line, no asterisks</h2> <p class="text-muted-foreground mt-3 text-lg"> Limits differ between plans. Where a plan caps something, the cap is written in the cell. </p> <div ui-card class="mt-8"> <div ui-card-content class="p-0"> <!-- The header pins with CSS alone. Thirty rows down, a checkmark is still attributable to a column without scrolling back up. --> <div class="max-h-[28rem] overflow-auto"> <table ui-table> <thead ui-table-header class="bg-card sticky top-0 z-10"> <tr ui-table-row> <th ui-table-head class="bg-card">Capability</th> @for (plan of plans; track plan) { <th ui-table-head class="bg-card text-center">{{ plan }}</th> } </tr> </thead> <tbody ui-table-body> @for (row of rows; track row.feature) { <tr ui-table-row> <td ui-table-cell class="font-medium">{{ row.feature }}</td> @for (value of row.values; track $index) { <td ui-table-cell class="text-center"> @if (value === true) { <lucide-icon [img]="Check" class="text-success mx-auto size-4" aria-hidden="true" /> <span class="sr-only">Included</span> } @else if (value === false) { <lucide-icon [img]="Minus" class="text-muted-foreground/50 mx-auto size-4" aria-hidden="true" /> <span class="sr-only">Not included</span> } @else { <span class="font-mono text-xs">{{ value }}</span> } </td> } </tr> } </tbody> </table> </div> </div> </div> <div class="mt-6 flex flex-wrap gap-3"> <button ui-button>Start on Team</button> <button ui-button variant="outline">Talk about Enterprise</button> </div> </div> </section> `, }) export class UiPricingMatrixStickyHeaderComponent { protected readonly Check = Check protected readonly Minus = Minus readonly plans = plans readonly rows = rows @Input('class') className?: string get rootClass(): string { return cn('bg-background', this.className) } }src/app/components/blocks/pricing-matrix-sticky-header/index.ts0.1 kB
export { UiPricingMatrixStickyHeaderComponent, type PricingStickyRow } from './pricing-matrix-sticky-header.component'
Raw manifest:https://uipkge.dev/r/angular/pricing-matrix-sticky-header.json