Skip to content

Neutral palette · 3.6 KB ZIP Receipt View code

Preview

Fit to the available width. Previews taller than the maximum auto-height scroll inside the frame.

Use this component with your coding agent

Using the PageSugar MCP server, fetch component cmp_pricing_grid_001 version 1.0.0 with variant "neutral", then add its files to this project and follow its usage notes.

Code

Neutral palette, version 1.0.0. Artifact sha256-7e41bda56205cd6856dc7c9f75e6a0558e35caddd725e84c321307c88188dfe3

PricingGrid.svelte · Svelte component · 3.8 KB

<script module lang="ts">
	export interface PricingPlan {
		/** Plan name shown as the card heading. */
		label: string;
		/** Display-only price string, e.g. "$12". Never parsed or charged. */
		price: string;
		/** Billing period shown after the price, e.g. "per month". */
		period?: string;
		description?: string;
		features: string[];
		cta: { label: string; href: string };
		recommended?: boolean;
	}
</script>

<script lang="ts">
	interface Props {
		plans: PricingPlan[];
		title: string;
		description?: string;
		headingLevel?: 2 | 3 | 4 | 5;
		recommendedLabel?: string;
	}

	let {
		plans,
		title,
		description,
		headingLevel = 2,
		recommendedLabel = 'Recommended'
	}: Props = $props();

	const uid = $props.id();
	const sectionHeading = $derived(`h${headingLevel}`);
	const planHeading = $derived(`h${headingLevel + 1}`);
</script>

<section class="pricing-grid px-4 py-16 sm:px-6 lg:px-8" aria-labelledby="{uid}-title">
	<div class="mx-auto max-w-6xl">
		<div class="mx-auto max-w-2xl text-center">
			<svelte:element
				this={sectionHeading}
				id="{uid}-title"
				class="text-3xl font-semibold tracking-tight text-balance text-zinc-900 sm:text-4xl"
			>
				{title}
			</svelte:element>
			{#if description}
				<p class="mt-4 text-base text-pretty text-zinc-600 sm:text-lg">{description}</p>
			{/if}
		</div>

		<ul class="mt-12 flex flex-wrap justify-center gap-6" role="list">
			{#each plans as plan, index (index)}
				<li
					class={[
						'flex max-w-sm min-w-0 grow basis-64 flex-col rounded-2xl bg-white p-6 sm:p-8',
						plan.recommended ? 'shadow-sm ring-2 ring-[var(--_accent)]' : 'ring-1 ring-zinc-200'
					]}
				>
					<div class="flex flex-wrap items-start justify-between gap-x-4 gap-y-2">
						<svelte:element
							this={planHeading}
							id="{uid}-plan-{index}"
							class="text-lg font-semibold break-words text-zinc-900"
						>
							{plan.label}
						</svelte:element>
						{#if plan.recommended}
							<p
								class="rounded-full bg-[var(--_accent)] px-2.5 py-1 text-xs font-medium text-[var(--_on-accent)]"
							>
								{recommendedLabel}
							</p>
						{/if}
					</div>

					{#if plan.description}
						<p class="mt-3 text-sm text-pretty text-zinc-600">{plan.description}</p>
					{/if}

					<p class="mt-6 flex flex-wrap items-baseline gap-x-2">
						<span class="text-4xl font-semibold tracking-tight break-all text-zinc-900"
							>{plan.price}</span
						>
						{#if plan.period}
							<span class="text-sm text-zinc-600">{plan.period}</span>
						{/if}
					</p>

					<ul class="mt-6 flex-1 space-y-3 text-sm text-zinc-700" role="list">
						{#each plan.features as feature, featureIndex (featureIndex)}
							<li class="flex gap-3">
								<svg
									class="mt-0.5 size-4 shrink-0 text-zinc-900"
									viewBox="0 0 16 16"
									fill="none"
									aria-hidden="true"
								>
									<path
										d="M3.5 8.5l3 3 6-7"
										stroke="currentColor"
										stroke-width="1.75"
										stroke-linecap="round"
										stroke-linejoin="round"
									/>
								</svg>
								<span class="min-w-0 break-words">{feature}</span>
							</li>
						{/each}
					</ul>

					<a
						href={plan.cta.href}
						aria-describedby="{uid}-plan-{index}"
						class={[
							'mt-8 block rounded-lg px-4 py-2.5 text-center text-sm font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--_accent)]',
							plan.recommended
								? 'bg-[var(--_accent)] text-[var(--_on-accent)] hover:opacity-90'
								: 'text-zinc-900 ring-1 ring-zinc-300 ring-inset hover:bg-zinc-50'
						]}
					>
						{plan.cta.label}
					</a>
				</li>
			{/each}
		</ul>
	</div>
</section>

<style>
	.pricing-grid {
		--_accent: var(--pricing-grid-accent, #18181b);
		--_on-accent: var(--pricing-grid-on-accent, #ffffff);
	}
</style>

Usage

Presentational only: every label, price and feature is displayed exactly as supplied by your data, and each plan button is an ordinary link. No runtime dependencies and no backend. Point each link at your own checkout, signup or contact flow.

Suggested location: src/lib/components/pricing-grid-01

Required props: plans, title

Example

Svelte
<script lang="ts">
	import PricingGrid, { type PricingPlan } from '$lib/components/pricing-grid-01/PricingGrid.svelte';

	const plans: PricingPlan[] = [
		{
			label: 'Starter',
			price: '$9',
			period: 'per month',
			features: ['One project', 'Email support'],
			cta: { label: 'Choose Starter', href: '/signup?plan=starter' }
		},
		{
			label: 'Team',
			price: '$29',
			period: 'per month',
			features: ['Unlimited projects', 'Shared workspaces'],
			cta: { label: 'Choose Team', href: '/signup?plan=team' },
			recommended: true
		}
	];
</script>

<PricingGrid title="Plans and pricing" {plans} />

Limitations

  • Does not create subscriptions, calculate totals, apply tax or switch currencies; prices are display strings.
  • No billing-period toggle; render a second grid or edit the source if you need one.
  • Light appearance only.

Simple pricing grid #

A responsive grid of plan cards. Everything shown comes from the plans prop; the component has no runtime dependencies, makes no requests and does not handle payment. Each plan button is an ordinary link to a destination you choose.

Install #

Copy PricingGrid.svelte into src/lib/components/pricing-grid-01/. No packages are required.

Example #

Svelte
<script lang="ts">
	import PricingGrid, {
		type PricingPlan
	} from '$lib/components/pricing-grid-01/PricingGrid.svelte';

	const plans: PricingPlan[] = [
		{
			label: 'Starter',
			price: '$9',
			period: 'per month',
			features: ['One project', 'Email support'],
			cta: { label: 'Choose Starter', href: '/signup?plan=starter' }
		},
		{
			label: 'Team',
			price: '$29',
			period: 'per month',
			features: ['Unlimited projects', 'Shared workspaces'],
			cta: { label: 'Choose Team', href: '/signup?plan=team' },
			recommended: true
		}
	];
</script>

<PricingGrid title="Plans and pricing" {plans} />

Props #

Prop Type Default Notes
plans PricingPlan[] required Display order is array order.
title string required Section heading.
description string Intro paragraph.
headingLevel 2 | 3 | 4 | 5 2 Plan names use headingLevel + 1.
recommendedLabel string 'Recommended' Badge text on the recommended plan.

PricingPlan is exported from the component's module script: { label, price, period?, description?, features, cta: { label, href }, recommended? }.

Customization #

  • Colours: override on any ancestor. Keep the pair contrast-safe (4.5:1 or better).

    Svelte
    <div style="--pricing-grid-accent: #1d4ed8; --pricing-grid-on-accent: #ffffff;">
        <PricingGrid title="Plans and pricing" {plans} />
    </div>
  • Links: replace every cta.href with your real checkout, signup or contact route.

  • Layout: card width comes from basis-64 max-w-sm on each card; edit those classes to change columns.

  • Background: the section is transparent; add a bg-* class to the root <section> if needed.

Limitations #

  • Prices are display strings: no currency conversion, tax, totals or subscription creation.
  • No monthly/yearly toggle.
  • Light appearance only. White on-accent text measures about 17.7:1 on the neutral accent (#18181b) and about 6.7:1 on the blue accent (#1d4ed8), computed with the WCAG relative-luminance formula. Re-check contrast whenever you change either colour.

Customization

Change content through the plans prop, change the accent colour through two CSS variables, and edit Tailwind classes in the source for spacing, radius or layout.

  • Colours: set --pricing-grid-accent and --pricing-grid-on-accent on any ancestor; keep the pair at 4.5:1 contrast or better because the on-accent colour is used for button and badge text.
  • Links: replace each cta.href with your real checkout, signup or contact route and make cta.label specific (e.g. "Choose Team").
  • Headings: set headingLevel so the section heading fits the page outline; plan names automatically use the next level.
  • Layout: cards wrap using basis-64 and max-w-sm on each list item; change those classes to alter card width or column count.
  • Recommended plan: mark at most one plan recommended so the emphasis stays meaningful.
  • Surface: the section has no background of its own; place it on a white or light neutral page, or add a bg-* class to the root section.

Public CSS variables

VariableToken
--pricing-grid-accentaccent
--pricing-grid-on-accentonAccent

Props and content inputs

NameTypeRequiredDefaultDescription
plansPricingPlan[]YesNonePlans in display order. Each has label, price (display string), optional period and description, features (string[]), cta { label, href } and an optional recommended flag.
titlestringYesNoneSection heading text.
descriptionstringNoNoneOptional introductory paragraph under the heading.
headingLevel2 | 3 | 4 | 5No2Level of the section heading; plan names use the next level down.
recommendedLabelstringNo'Recommended'Badge text shown on plans with recommended: true.

Dependencies and services

No additional runtime packages beyond Svelte and Tailwind CSS.

Accessibility

  • Plans are a semantic list; each plan name is a heading one level below the section heading, which is set with headingLevel.
  • Each plan link is described by its plan heading via aria-describedby, so repeated labels such as "Get started" remain distinguishable.
  • The recommended plan is identified by visible badge text, not colour alone.
  • White (#ffffff) on-accent text measures about 17.7:1 against the neutral accent (#18181b) and about 6.7:1 against the blue accent (#1d4ed8), computed with the WCAG relative-luminance formula; links show a visible focus outline in the accent colour.
  • Checkmark icons are decorative and hidden from assistive technology.
  • Element IDs come from $props.id(), so multiple grids on one page stay unique and hydrate deterministically.

Known limitations

  • Contrast ratios are computed for the two shipped palettes only; any changed accent or on-accent colour must be re-checked for at least 4.5:1.

License

Source license: MIT.

Version history

Only one release has been published so far; earlier versions will be listed here.

  • 1.0.0 Published current · selected · 16 September 2026

Search components

Describe a section or control, such as “FAQ accordion” or “newsletter signup”.