---
/**
* PricingColumns: plans side by side, the featured one inverted onto ink.
* Prices are plain strings, so "$420", "From $9k" or "By quote" all work.
*
* @example
* <PricingColumns title="Repair plans," accent="priced plainly." tiers={[
* { name: "Trail", price: "$180", period: "per pack", features: ["Stitching"], cta: { label: "Book", href: "/book" } },
* ]} />
*/
import Button from "../button/Button.astro";
import Icon from "../icon/Icon.astro";
interface Cta {
label: string;
href: string;
}
interface Tier {
name: string;
price: string;
period?: string;
description?: string;
features: string[];
cta: Cta;
featured?: boolean;
/** Small label on the plan, e.g. "Most chosen". */
badge?: string;
}
interface Props {
title: string;
accent?: string;
intro?: string;
tiers: Tier[];
/** Small print under the plans. */
note?: string;
as?: "h2" | "h3";
class?: string;
}
const { title, accent, intro, tiers, note, as: Heading = "h2", class: className } = Astro.props;
---
<section class:list={["vs-pricing-columns", className]}>
<div class="vs-pricing-columns__inner">
<header class="vs-pricing-columns__head">
<Heading class="vs-display vs-pricing-columns__title">{title}{accent && <> <em>{accent}</em></>}</Heading>
{intro && <p class="vs-pricing-columns__intro">{intro}</p>}
</header>
<ul class="vs-pricing-columns__tiers">
{
tiers.map((t) => (
<li class:list={["vs-pricing-columns__tier", { "is-featured": t.featured }]}>
<div>
<h3 class="vs-pricing-columns__name">
{t.name}
{t.badge && <span class="vs-pricing-columns__badge">{t.badge}</span>}
</h3>
{t.description && <p class="vs-pricing-columns__desc">{t.description}</p>}
</div>
<div class="vs-pricing-columns__buy">
<p class="vs-display vs-pricing-columns__price">{t.price}</p>
{t.period && <p class="vs-pricing-columns__period">{t.period}</p>}
<div class="vs-pricing-columns__action">
<Button href={t.cta.href} variant={t.featured ? "accent" : "secondary"}>{t.cta.label}</Button>
</div>
</div>
<ul class="vs-pricing-columns__features">
{t.features.map((f) => (
<li>
<Icon name="check" size={16} strokeWidth={2.25} />
<span>{f}</span>
</li>
))}
</ul>
</li>
))
}
</ul>
{note && <p class="vs-pricing-columns__note">{note}</p>}
</div>
</section>
<style>
.vs-pricing-columns {
container-type: inline-size;
background: var(--vs-page);
color: var(--vs-ink);
font-family: var(--vs-font-sans);
font-stretch: var(--vs-font-stretch);
}
.vs-pricing-columns__inner {
max-width: var(--vs-container);
margin-inline: auto;
padding: var(--vs-section-y) var(--vs-gutter);
}
.vs-pricing-columns__title {
font-size: clamp(2rem, 6.5cqi, 4.25rem);
}
.vs-pricing-columns__intro {
max-width: 34rem;
margin: 1.25rem 0 0;
font-size: 1.0625rem;
line-height: 1.55;
color: var(--vs-ink-2);
}
.vs-pricing-columns__tiers {
display: grid;
gap: 16px;
margin: clamp(28px, 5cqi, 56px) 0 0;
padding: 0;
list-style: none;
}
@container (min-width: 760px) {
.vs-pricing-columns__tiers {
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}
}
.vs-pricing-columns__tier {
display: flex;
flex-direction: column;
gap: 24px;
padding: 28px;
border: var(--vs-border-w) solid var(--vs-line);
border-radius: var(--vs-radius-card);
background: var(--vs-surface);
box-shadow: var(--vs-shadow-sm);
}
.vs-pricing-columns__name {
display: flex;
align-items: center;
gap: 10px;
margin: 0;
font-size: 1.125rem;
font-weight: 600;
}
.vs-pricing-columns__badge {
padding: 3px 9px;
border-radius: var(--vs-radius-pill);
background: var(--vs-accent);
color: var(--vs-on-accent);
font-size: 0.6875rem;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
}
.vs-pricing-columns__desc {
margin: 8px 0 0;
font-size: 0.9375rem;
line-height: 1.5;
color: var(--vs-ink-2);
}
.vs-pricing-columns__buy {
padding-bottom: 24px;
border-bottom: var(--vs-border-w) solid var(--vs-line);
}
.vs-pricing-columns__price {
font-size: clamp(2.25rem, 5cqi, 3.25rem);
line-height: 1;
}
.vs-pricing-columns__period {
margin: 6px 0 0;
font-size: 0.8125rem;
color: var(--vs-ink-3);
}
.vs-pricing-columns__action {
margin-top: 20px;
}
.vs-pricing-columns__action :global(.vs-button) {
width: 100%;
}
.vs-pricing-columns__features {
display: grid;
gap: 10px;
margin: 0;
padding: 0;
list-style: none;
font-size: 0.9375rem;
}
.vs-pricing-columns__features li {
display: flex;
gap: 10px;
line-height: 1.4;
}
.vs-pricing-columns__features :global(.vs-icon) {
margin-top: 2px;
color: var(--vs-accent-ink);
}
.vs-pricing-columns__note {
margin: 24px 0 0;
font-size: 0.8125rem;
color: var(--vs-ink-3);
}
/* Featured plan: inverted onto ink */
.vs-pricing-columns__tier.is-featured {
border-color: var(--vs-ink);
background: var(--vs-ink);
color: var(--vs-ink-inverse);
box-shadow: var(--vs-pop-shadow, var(--vs-shadow-md));
}
.is-featured .vs-pricing-columns__desc,
.is-featured .vs-pricing-columns__period {
color: color-mix(in srgb, var(--vs-ink-inverse) 70%, transparent);
}
.is-featured .vs-pricing-columns__buy {
border-color: color-mix(in srgb, var(--vs-ink-inverse) 22%, transparent);
}
.is-featured .vs-pricing-columns__features :global(.vs-icon) {
color: var(--vs-accent);
}
</style>