# Simple cards 04

> A centred heading over two wide plan cards with features in two columns.

URL: https://apogee.avaui.dev/docs/pricing-sections/simple-cards-04
Last updated: 2026-10-20

A centred heading over two wide plan cards with features in two columns.

Last updated October 20, 2026

Live example: https://apogee.avaui.dev/examples/pricing/simple-cards-04

```astro
---
/**
 * Simple cards 04: A centred heading over two wide plan cards with features in two columns.
 *
 * @block pricing
 * @order 4
 */
import { Check } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import SectionHeader from '@/components/apogee/SectionHeader/SectionHeader.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Badge from '@/components/apogee/Badge/Badge.astro'
import { accentRuns, plain } from '@/components/apogee/sections/_shared/spine.ts'
import type { PricingContent } from '@/components/apogee/sections/_shared/pricing.ts'

/** @composable */
interface Props extends PricingContent {
  /** The small label over each features list. @category content */
  featuresLabel?: string | null
  /** The line under that label, one per plan in order; *asterisks* bold a part. @category content */
  featuresIntros?: string[]
}

const {
  eyebrow = 'Pricing',
  heading = 'Plans that fit your scale',
  description = 'Simple, transparent pricing that grows with you. Try any plan free for 30 days.',
  items = [
    { name: 'Basic plan', price: '$10', period: 'month', description: 'Our most popular plan for small teams.', features: ['Access to basic features', 'Attend events', 'Basic reporting and analytics', 'Automatic updates', 'Up to 10 individual users', 'Back up your account', '20 GB individual data', 'Audit log and notes', 'Basic chat support', 'Feature requests'], action: { label: 'Get started', href: '#' } },
    { name: 'Business plan', price: '$20', period: 'month', description: 'Advanced features and reporting.', features: ['200+ integrations', 'Advanced custom fields', 'Advanced reporting', 'Audit log and data history', 'Up to 20 individual users', 'Back up your account', '40 GB individual data', 'Personalised service', 'Priority chat support', 'And much more'], action: { label: 'Get started', href: '#' }, badge: 'Popular' },
  ],
  featuresLabel = 'Features',
  featuresIntros = ['Everything in our *free* plan, plus:', 'Everything in our *Basic* plan, plus:'],
} = Astro.props
// '$10' → '$' (set small) and '10'.
const split = (price: string) => price.match(/^(\D*)(.*)$/)!.slice(1) as [string, string]
---

<Section spacing="large">
  <div class="flex flex-col gap-12 md:gap-16">
    <SectionHeader eyebrow={eyebrow ?? undefined} heading={plain(heading)} description={description ?? undefined} size="medium" align="center" />
    <div class="mx-auto grid w-full max-w-xl gap-8 xl:max-w-none xl:grid-cols-2">
    {items.map((plan, i) => {
      const intro = featuresIntros[i]
      const [currency, amount] = split(plan.price)
      return (
    <div class="flex flex-col overflow-hidden rounded-2xl bg-elevation-raised shadow-raised ring-1 ring-neutral">
      <div class="flex flex-col-reverse gap-6 p-6 md:flex-row md:justify-between md:p-8">
        <div class="flex flex-col gap-2">
          <div class="flex items-center gap-2"><Heading as="h3" size="h5">{plan.name}</Heading>{(plan.badge || plan.featured) && <Badge color="primary" rounded>{plan.badge ?? 'Popular'}</Badge>}</div>
          <p class="text-neutral-faded">{plan.description}</p>
        </div>
        <p class="flex items-start gap-1">{currency && <span class="mt-2 text-2xl font-semibold">{currency}</span>}<span class="text-6xl font-semibold tracking-tight tabular-nums">{amount}</span>{plan.period && <span class="self-end pb-2 text-neutral-faded">per {plan.period}</span>}</p>
      </div>
      <div class="flex flex-col gap-6 border-t border-neutral p-6 md:p-8">
        {(featuresLabel || intro) && (
        <div class="flex flex-col gap-1">
          {featuresLabel && <p class="font-semibold">{featuresLabel}</p>}
          {intro && <p class="text-neutral-faded">{accentRuns(intro).map((r) => (r.accent ? <strong class="font-semibold text-neutral">{r.text}</strong> : r.text))}</p>}
        </div>
        )}
      <ul class="flex list-none flex-col gap-4 p-0 text-neutral-faded md:grid md:grid-cols-2 md:gap-x-8">
        {plan.features.map((f) => <li class="flex gap-3"><span class="flex size-6 shrink-0 items-center justify-center rounded-full bg-positive-faded text-positive"><Check class="size-4" aria-hidden="true" /></span><span>{f}</span></li>)}
      </ul>
      </div>
      <div class="border-t border-neutral p-6 md:px-8">
        <Button href={plan.action.href} size="large" fullWidth>{plan.action.label}</Button>
      </div>
    </div>
      )
    })}
    </div>
  </div>
</Section>
```

## Add

```sh
npx @ava-ui/apogee add pricing/simple-cards-04
```

This copies the block to `src/components/apogee/blocks/pricing/` with everything it uses: Badge, Button, Heading, Section, SectionHeader. Every piece of copy is a prop, with the sample copy as its default. Pass yours, or `null` to hide a part; see [copy is props](https://apogee.avaui.dev/docs/pricing-sections#copy-is-props).

## Accessibility

-   The heading is an `h2` by default, so the block slots into a page under its `h1`; item titles are one level down.

## Related

[

PricingPlan tiers side by side, with an optional monthly / annual switch.

](https://apogee.avaui.dev/docs/sections/pricing)[

SegmentedControlA row of connected, mutually exclusive options backed by native radios — pick one from a small, always-visible set.

](https://apogee.avaui.dev/docs/segmented-control)[

TableA styled, horizontally-scrollable table. Write plain thead/tbody markup — Table only styles it.

](https://apogee.avaui.dev/docs/table)
