PricingPlan

A customizable pricing plan to display in a pricing page.

Usage

The PricingPlan component provides a flexible way to display a pricing plan with customizable content including title, description, price, features, etc.

Solo
Most popular
For bootstrappers and indie hackers.
$249
$199
 /month
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Unlimited patch & minor updates
  • Lifetime access
Use the PricingPlans component to display multiple pricing plans in a responsive grid layout.

Title

Use the title prop to set the title of the PricingPlan.

Solo
<template>
  <UPricingPlan title="Solo" class="w-96" />
</template>

Description

Use the description prop to set the description of the PricingPlan.

Solo
For bootstrappers and indie hackers.
<template>
  <UPricingPlan title="Solo" description="For bootstrappers and indie hackers." />
</template>

Badge

Use the badge prop to display a Badge next to the title of the PricingPlan.

Solo
Most popular
For bootstrappers and indie hackers.
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    badge="Most popular"
  />
</template>

You can pass any property from the Badge component to customize it.

Solo
Most popular
For bootstrappers and indie hackers.
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    :badge="{
      label: 'Most popular',
      color: 'neutral',
      variant: 'solid'
    }"
  />
</template>

Price

Use the price prop to set the price of the PricingPlan.

Solo
For bootstrappers and indie hackers.
$249
<template>
  <UPricingPlan title="Solo" description="For bootstrappers and indie hackers." price="$249" />
</template>

Discount

Use the discount prop to set a discounted price that will be displayed alongside the original price (which will be shown with a strikethrough).

Solo
For bootstrappers and indie hackers.
$249
$199
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    discount="$199"
  />
</template>

Billing

Use the billing-cycle and/or billing-period props to display the billing information of the PricingPlan.

Solo
For bootstrappers and indie hackers.
$9
billed annually/month
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$9"
    billing-cycle="/month"
    billing-period="billed annually"
  />
</template>

Features

Use the features prop as an array of string to display a list of features on the PricingPlan:

Solo
For bootstrappers and indie hackers.
$249
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Unlimited patch & minor updates
  • Lifetime access
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="[
      'One developer',
      'Unlimited projects',
      'Access to GitHub repository',
      'Unlimited patch & minor updates',
      'Lifetime access'
    ]"
  />
</template>
You can customize this icon globally in your app.config.ts under ui.icons.success key.
You can customize this icon globally in your vite.config.ts under ui.icons.success key.

You can also pass an array of objects with the following properties:

  • title: string
  • icon?: string
Solo
For bootstrappers and indie hackers.
$249
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Unlimited patch & minor updates
  • Lifetime access
<script setup lang="ts">
const features = ref([
  {
    title: 'One developer',
    icon: 'i-lucide-user'
  },
  {
    title: 'Unlimited projects',
    icon: 'i-lucide-infinity'
  },
  {
    title: 'Access to GitHub repository',
    icon: 'i-lucide-github'
  },
  {
    title: 'Unlimited patch & minor updates',
    icon: 'i-lucide-refresh-cw'
  },
  {
    title: 'Lifetime access',
    icon: 'i-lucide-clock'
  }
])
</script>

<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="features"
  />
</template>

Button

Use the button prop with any property from the Button component to display a button at the bottom of the PricingPlan.

Solo
For bootstrappers and indie hackers.
$249
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Unlimited patch & minor updates
  • Lifetime access
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="[
      'One developer',
      'Unlimited projects',
      'Access to GitHub repository',
      'Unlimited patch & minor updates',
      'Lifetime access'
    ]"
    :button="{
      label: 'Buy now'
    }"
  />
</template>
Use the onClick field to add a click handler to trigger the plan purchase.

Variant

Use the variant prop to change the variant of the PricingPlan.

Solo
For bootstrappers and indie hackers.
$249
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Unlimited patch & minor updates
  • Lifetime access
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="[
      'One developer',
      'Unlimited projects',
      'Access to GitHub repository',
      'Unlimited patch & minor updates',
      'Lifetime access'
    ]"
    :button="{
      label: 'Buy now'
    }"
    variant="subtle"
  />
</template>

Orientation

Use the orientation prop to change the orientation of the PricingPlan. Defaults to vertical.

Solo
For bootstrappers and indie hackers.
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Lifetime access
$249
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="[
      'One developer',
      'Unlimited projects',
      'Access to GitHub repository',
      'Lifetime access'
    ]"
    :button="{
      label: 'Buy now'
    }"
    orientation="horizontal"
  />
</template>

Tagline

Use the tagline prop to display a tagline text above the price.

Solo
For bootstrappers and indie hackers.
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Lifetime access

Pay once, own it forever

$249
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="[
      'One developer',
      'Unlimited projects',
      'Access to GitHub repository',
      'Lifetime access'
    ]"
    :button="{
      label: 'Buy now'
    }"
    orientation="horizontal"
    tagline="Pay once, own it forever"
  />
</template>

Terms

Use the terms prop to display terms below the price.

Solo
For bootstrappers and indie hackers.
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Lifetime access

Pay once, own it forever

$249

Invoices and receipts available.

<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="[
      'One developer',
      'Unlimited projects',
      'Access to GitHub repository',
      'Lifetime access'
    ]"
    :button="{
      label: 'Buy now'
    }"
    orientation="horizontal"
    tagline="Pay once, own it forever"
    terms="Invoices and receipts available."
  />
</template>

Highlight

Use the highlight prop to display a highlighted border around the PricingPlan.

Solo
For bootstrappers and indie hackers.
$249
  • One developer
  • Unlimited projects
  • Access to GitHub repository
  • Unlimited patch & minor updates
  • Lifetime access
<template>
  <UPricingPlan
    title="Solo"
    description="For bootstrappers and indie hackers."
    price="$249"
    :features="[
      'One developer',
      'Unlimited projects',
      'Access to GitHub repository',
      'Unlimited patch & minor updates',
      'Lifetime access'
    ]"
    :button="{
      label: 'Buy now'
    }"
    highlight
  />
</template>

Scale

Use the scale prop to make a PricingPlan bigger than the others.

Check out the PricingPlans's scale example to see how it works as it's hard to demonstrate by itself.

API

Props

Prop Default Type
as

'div'

any

The element or component this component should render as.

title

string

description

string

badge

string | BadgeProps

Display a badge on the pricing plan next to the title. Can be a string or an object. { color: 'primary', variant: 'subtle' }

billingCycle

string

The unit price period that appears next to the price. Typically used to show the recurring interval.

billingPeriod

string

Additional billing context that appears above the billing cycle. Typically used to show the actual billing frequency.

price

string

The current price of the plan. When used with discount, this becomes the original price.

discount

string

The discounted price of the plan. When provided, the price prop will be displayed as strikethrough.

features

string[] | PricingPlanFeature[]

Display a list of features under the price. Can be an array of strings or an array of objects.

button

ButtonProps

Display a buy button at the bottom of the pricing plan. { size: 'lg', block: true } Use the onClick field to add a click handler.

tagline

string

Display a tagline highlighting the pricing value proposition.

terms

string

Display terms at the bottom of the pricing plan.

orientation

'vertical'

"vertical" | "horizontal"

The orientation of the pricing plan.

variant

'outline'

"solid" | "outline" | "soft" | "subtle"

highlight

boolean

Display a ring around the pricing plan to highlight it.

scale

boolean

Enlarge the plan to make it more prominent.

ui

Partial<{ root: string; header: string; body: string; footer: string; titleWrapper: string; title: string; description: string; priceWrapper: string; price: string; discount: string; billing: string; billingPeriod: string; ... 8 more ...; terms: string; }>

Slots

Slot Type
badge

{}

title

{}

description

{}

price

{}

discount

{}

billing

{}

features

{}

button

{}

header

{}

body

{}

footer

{}

Theme

app.config.ts
export default defineAppConfig({
  uiPro: {
    pricingPlan: {
      slots: {
        root: 'relative grid rounded-[calc(var(--ui-radius)*2.5)] p-6 lg:p-8 xl:p-10 gap-6',
        header: '',
        body: 'flex flex-col min-w-0',
        footer: 'flex flex-col gap-6 items-center',
        titleWrapper: 'flex items-center gap-3',
        title: 'text-[var(--ui-text-highlighted)] text-2xl sm:text-3xl text-pretty font-semibold',
        description: 'text-base text-pretty mt-2',
        priceWrapper: 'flex items-center gap-1',
        price: 'text-[var(--ui-text-highlighted)] text-3xl sm:text-4xl font-semibold',
        discount: 'text-[var(--ui-text-muted)] line-through text-xl sm:text-2xl',
        billing: 'flex flex-col justify-between min-w-0',
        billingPeriod: 'text-[var(--ui-text-toned)] truncate text-xs font-medium',
        billingCycle: 'text-[var(--ui-text-muted)] truncate text-xs font-medium',
        features: 'flex flex-col gap-3 flex-1 mt-6 grow-0',
        feature: 'flex items-center gap-2 min-w-0',
        featureIcon: 'size-5 shrink-0 text-[var(--ui-primary)]',
        featureTitle: 'text-sm truncate',
        badge: '',
        button: '',
        tagline: 'text-base font-semibold text-[var(--ui-text)]',
        terms: 'text-xs/5 text-[var(--ui-text-muted)] text-center text-balance'
      },
      variants: {
        orientation: {
          horizontal: {
            root: 'grid-cols-1 lg:grid-cols-3 justify-between divide-y lg:divide-y-0 lg:divide-x divide-[var(--ui-border)]',
            body: 'lg:col-span-2 pb-6 lg:pb-0 lg:pr-6 justify-center',
            footer: 'lg:justify-center lg:items-center lg:p-6 lg:max-w-xs lg:w-full lg:mx-auto',
            features: 'lg:grid lg:grid-cols-2 lg:mt-12'
          },
          vertical: {
            footer: 'justify-end',
            priceWrapper: 'mt-6'
          }
        },
        variant: {
          solid: {
            root: 'bg-[var(--ui-bg-inverted)]',
            title: 'text-[var(--ui-bg)]',
            description: 'text-[var(--ui-text-dimmed)]',
            price: 'text-[var(--ui-bg)]',
            discount: 'text-[var(--ui-text-dimmed)]',
            billingCycle: 'text-[var(--ui-text-dimmed)]',
            billingPeriod: 'text-[var(--ui-text-dimmed)]',
            featureTitle: 'text-[var(--ui-text-dimmed)]'
          },
          outline: {
            root: 'bg-[var(--ui-bg)] ring ring-inset ring-[var(--ui-border)]',
            description: 'text-[var(--ui-text-muted)]',
            featureTitle: 'text-[var(--ui-text-muted)]'
          },
          soft: {
            root: 'bg-[var(--ui-bg-elevated)]/50',
            description: 'text-[var(--ui-text-toned)]',
            featureTitle: 'text-[var(--ui-text-toned)]'
          },
          subtle: {
            root: 'bg-[var(--ui-bg-elevated)] ring ring-inset ring-[var(--ui-border-accented)]',
            description: 'text-[var(--ui-text)]',
            featureTitle: 'text-[var(--ui-text)]'
          }
        },
        highlight: {
          true: {
            root: 'ring-2 ring-inset ring-[var(--ui-primary)]'
          }
        },
        scale: {
          true: {
            root: 'lg:scale-[1.1] lg:z-[1]'
          }
        }
      },
      compoundVariants: [
        {
          orientation: 'horizontal',
          variant: 'soft',
          class: {
            root: 'divide-[var(--ui-border-accented)]'
          }
        },
        {
          orientation: 'horizontal',
          variant: 'subtle',
          class: {
            root: 'divide-[var(--ui-border-accented)]'
          }
        }
      ],
      defaultVariants: {
        color: 'primary',
        variant: 'outline'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      uiPro: {
        pricingPlan: {
          slots: {
            root: 'relative grid rounded-[calc(var(--ui-radius)*2.5)] p-6 lg:p-8 xl:p-10 gap-6',
            header: '',
            body: 'flex flex-col min-w-0',
            footer: 'flex flex-col gap-6 items-center',
            titleWrapper: 'flex items-center gap-3',
            title: 'text-[var(--ui-text-highlighted)] text-2xl sm:text-3xl text-pretty font-semibold',
            description: 'text-base text-pretty mt-2',
            priceWrapper: 'flex items-center gap-1',
            price: 'text-[var(--ui-text-highlighted)] text-3xl sm:text-4xl font-semibold',
            discount: 'text-[var(--ui-text-muted)] line-through text-xl sm:text-2xl',
            billing: 'flex flex-col justify-between min-w-0',
            billingPeriod: 'text-[var(--ui-text-toned)] truncate text-xs font-medium',
            billingCycle: 'text-[var(--ui-text-muted)] truncate text-xs font-medium',
            features: 'flex flex-col gap-3 flex-1 mt-6 grow-0',
            feature: 'flex items-center gap-2 min-w-0',
            featureIcon: 'size-5 shrink-0 text-[var(--ui-primary)]',
            featureTitle: 'text-sm truncate',
            badge: '',
            button: '',
            tagline: 'text-base font-semibold text-[var(--ui-text)]',
            terms: 'text-xs/5 text-[var(--ui-text-muted)] text-center text-balance'
          },
          variants: {
            orientation: {
              horizontal: {
                root: 'grid-cols-1 lg:grid-cols-3 justify-between divide-y lg:divide-y-0 lg:divide-x divide-[var(--ui-border)]',
                body: 'lg:col-span-2 pb-6 lg:pb-0 lg:pr-6 justify-center',
                footer: 'lg:justify-center lg:items-center lg:p-6 lg:max-w-xs lg:w-full lg:mx-auto',
                features: 'lg:grid lg:grid-cols-2 lg:mt-12'
              },
              vertical: {
                footer: 'justify-end',
                priceWrapper: 'mt-6'
              }
            },
            variant: {
              solid: {
                root: 'bg-[var(--ui-bg-inverted)]',
                title: 'text-[var(--ui-bg)]',
                description: 'text-[var(--ui-text-dimmed)]',
                price: 'text-[var(--ui-bg)]',
                discount: 'text-[var(--ui-text-dimmed)]',
                billingCycle: 'text-[var(--ui-text-dimmed)]',
                billingPeriod: 'text-[var(--ui-text-dimmed)]',
                featureTitle: 'text-[var(--ui-text-dimmed)]'
              },
              outline: {
                root: 'bg-[var(--ui-bg)] ring ring-inset ring-[var(--ui-border)]',
                description: 'text-[var(--ui-text-muted)]',
                featureTitle: 'text-[var(--ui-text-muted)]'
              },
              soft: {
                root: 'bg-[var(--ui-bg-elevated)]/50',
                description: 'text-[var(--ui-text-toned)]',
                featureTitle: 'text-[var(--ui-text-toned)]'
              },
              subtle: {
                root: 'bg-[var(--ui-bg-elevated)] ring ring-inset ring-[var(--ui-border-accented)]',
                description: 'text-[var(--ui-text)]',
                featureTitle: 'text-[var(--ui-text)]'
              }
            },
            highlight: {
              true: {
                root: 'ring-2 ring-inset ring-[var(--ui-primary)]'
              }
            },
            scale: {
              true: {
                root: 'lg:scale-[1.1] lg:z-[1]'
              }
            }
          },
          compoundVariants: [
            {
              orientation: 'horizontal',
              variant: 'soft',
              class: {
                root: 'divide-[var(--ui-border-accented)]'
              }
            },
            {
              orientation: 'horizontal',
              variant: 'subtle',
              class: {
                root: 'divide-[var(--ui-border-accented)]'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            variant: 'outline'
          }
        }
      }
    })
  ]
})