Black Friday: 50% off all products for the first 50 customers. Only 10 left. Use code
40/50 claimed
See pricing →
November 7, 2026

Quasar Pricing Page: Build One From Components

Build a Quasar pricing page in four parts: a monthly/yearly toggle, plan cards, an FAQ accordion and a closing CTA, then theme it to match your brand.
Quasar
Vue
SaaS
Components

A Quasar pricing page is four sections stacked in a fixed order: a header with a billing toggle, the plan cards, an FAQ, and one last call to action. Each one maps to a handful of Quasar components you already know: q-btn-toggle, q-card, q-expansion-item and q-btn. This post builds all four with the Quasar 2.35.2 API, explains why the order matters, and then shows how to skip the hand-building by assembling the page from ready-made Components and theming it.

The plan names and numbers below are example data for a made-up product. Swap in your own.

What goes on a SaaS pricing page

Visitors land on a pricing page with one question: which plan is mine, and what does it cost? The order of the sections follows that question:

  1. Header and billing toggle. One line on who the plans are for, and a monthly/yearly switch above the cards, so the prices below already show the right period.
  2. Plan cards. Three is the common count. One is marked as recommended. Each card has a price, a one-line summary, a button, and a short feature list.
  3. FAQ. The objections that stop a purchase: cancellation, refunds, what happens at the limit, whether a card is needed for a trial.
  4. Closing CTA. Someone who read the whole FAQ is close to deciding. Give them one button without scrolling back up.

A long feature comparison table can sit between the cards and the FAQ if your plans differ in many small ways. For three plans with five differences each, the card lists are enough.

The monthly/yearly toggle with QBtnToggle

q-btn-toggle renders a group of buttons where exactly one is active, and its v-model holds the active option's value:

<script setup lang="ts">
import { computed, ref } from 'vue'

type Billing = 'monthly' | 'yearly'

const billing = ref<Billing>('monthly')
const billingOptions = [
  { label: 'Monthly', value: 'monthly' },
  { label: 'Yearly', value: 'yearly' },
]
const yearly = computed(() => billing.value === 'yearly')
</script>

<template>
  <div class="text-center q-mb-xl">
    <div class="text-overline text-primary">Pricing</div>
    <h2 class="text-h4 q-my-sm">Plans that grow with your team</h2>
    <q-btn-toggle
      v-model="billing"
      :options="billingOptions"
      unelevated
      no-caps
      toggle-color="primary"
      class="q-mt-md"
    />
    <div class="text-caption q-mt-sm">Yearly billing: two months free</div>
  </div>
</template>

toggle-color colors the active button, and every other QBtn prop (unelevated, no-caps, rounded, padding) applies to all the buttons at once. An option object can carry its own QBtn props too, such as an icon or an attrs: { 'aria-label': ... }.

A q-toggle with false-value="monthly" and true-value="yearly" works as well, if you prefer a switch between two labels. The segmented button group makes both choices visible, which is why most pricing pages use it.

Plan cards with q-card and the grid

Three cards in a row on desktop, stacked on phones, is row plus col-12 col-md-4. q-col-gutter-lg adds the space between them:

<script setup lang="ts">
// ...the toggle code from above, plus:
const plans = [
  { name: 'Starter', monthly: 9, yearly: 90, summary: 'For one person shipping a side project.', cta: 'Start free', features: ['1 project', '1,000 events a month', 'Email support'] },
  { name: 'Team', monthly: 29, yearly: 290, summary: 'For small teams that ship every week.', cta: 'Start a trial', featured: true, features: ['10 projects', '50,000 events a month', 'Shared dashboards', 'Priority support'] },
  { name: 'Scale', monthly: 99, yearly: 990, summary: 'For companies with many products.', cta: 'Talk to us', features: ['Unlimited projects', '1M events a month', 'SSO', 'A named contact'] },
]
</script>

<template>
  <div class="row q-col-gutter-lg items-stretch">
    <div v-for="plan in plans" :key="plan.name" class="col-12 col-md-4">
      <q-card flat bordered class="full-height column" :class="{ 'plan--featured': plan.featured }">
        <q-card-section>
          <div class="row items-center justify-between">
            <div class="text-h6">{{ plan.name }}</div>
            <q-badge v-if="plan.featured" color="primary" label="Most popular" />
          </div>
          <p class="text-body2 q-mt-sm">{{ plan.summary }}</p>
          <div class="text-h3">${{ yearly ? plan.yearly : plan.monthly }}</div>
          <div class="text-caption">{{ yearly ? 'per year' : 'per month' }}</div>
          <q-btn
            unelevated
            no-caps
            class="full-width q-mt-md"
            :color="plan.featured ? 'primary' : 'grey-3'"
            :text-color="plan.featured ? 'white' : 'dark'"
            :label="plan.cta"
          />
        </q-card-section>

        <q-separator />

        <q-card-section class="col">
          <div v-for="feature in plan.features" :key="feature" class="row items-center no-wrap q-mb-sm">
            <q-icon name="check" color="primary" size="18px" class="q-mr-sm" />
            <span class="text-body2">{{ feature }}</span>
          </div>
        </q-card-section>
      </q-card>
    </div>
  </div>
</template>

<style scoped>
.plan--featured {
  border-color: var(--q-primary);
  border-width: 2px;
}
</style>

The details that matter:

  • items-stretch on the row and full-height column on the card give all three cards the same height. The feature section gets col, so it takes the leftover space and the buttons line up.
  • The price comes from the toggle's computed, so switching the period updates every card at once.
  • The featured card gets a colored border from var(--q-primary), the CSS variable Quasar sets for the brand color. It follows your theme without hard-coding a hex value.
  • Keep the feature lists short and parallel: the same order on every card, so readers can compare line by line.

The FAQ as an accordion

q-expansion-item is a collapsible row. Give every item the same group, and opening one closes the others:

<script setup lang="ts">
const faqs = [
  { q: 'Can I cancel at any time?', a: 'Yes. Your plan stays active until the end of the period you paid for.' },
  { q: 'Do I need a card for the trial?', a: 'No. Add one only when you pick a paid plan.' },
  { q: 'What happens when I hit my event limit?', a: 'Nothing stops. We email you and you can upgrade or wait for the next month.' },
]
</script>

<template>
  <q-list bordered separator class="rounded-borders" style="max-width: 720px; margin: 0 auto">
    <q-expansion-item
      v-for="(item, i) in faqs"
      :key="item.q"
      group="pricing-faq"
      :label="item.q"
      :default-opened="i === 0"
      header-class="text-weight-medium"
    >
      <q-card>
        <q-card-section class="text-body2">{{ item.a }}</q-card-section>
      </q-card>
    </q-expansion-item>
  </q-list>
</template>

Opening the first answer by default with default-opened tells the reader the rows are clickable. For more than eight or so questions, a two-column grid of always-visible answers reads faster than an accordion.

The closing CTA

The last section repeats the main action. Keep it short: a heading, one sentence, one primary button, and at most one secondary link.

<template>
  <div class="bg-primary text-white text-center q-pa-xl rounded-borders">
    <h2 class="text-h5 q-mt-none q-mb-sm">Try it on your next project</h2>
    <p class="q-mb-lg">Free for 14 days. No card needed.</p>
    <q-btn unelevated no-caps color="white" text-color="primary" label="Start free" />
  </div>
</template>

Assemble a Quasar pricing page from Components

Building the four sections by hand takes an afternoon. Assembling them from QuasarUI Components takes a few minutes, because each section exists as a Block:

  • Pricing Sections: 11 Components. Five have a monthly/yearly switch, built with q-btn-toggle, q-toggle or a pair of buttons. Three are free, including a three-card layout that shows the yearly price under the monthly one.
  • FAQ Sections: 5 Components, as accordions built on q-expansion-item or as open grids. Two are free: one of each kind.
  • CTA Sections: 8 Components for the closing section. One is free.

Each Component is a single .vue file with the plan data in its <script setup>. Copy the three you picked into one page, stack them in the order from the first section of this post, and replace the example data with your own. The plan array is the only part you have to edit.

Theme it

A pricing page shows your brand colors more than most pages do: the toggle, the featured card, every button. Every QuasarUI Component uses Quasar's brand color names (primary, secondary and so on), so setting those colors recolors the whole page.

The free Theme Builder goes further. Pick one of its 12 presets or build your own palette, fonts and radius, then export the CSS and config for Quasar CLI or Nuxt. Two Components on this page are Themeable Components: the last Pricing Section and the last CTA Section read the Theme Builder's surface, text, border, radius and font tokens. Their previews can switch between presets, so you can see the page in your theme before you copy anything.

Common questions about a Quasar pricing page

How do I show yearly prices as a monthly amount? Store both numbers in the plan data and pick one with the toggle's computed, as above. Showing "per month, billed yearly" under the yearly figure avoids confusion about what gets charged.

How do I keep the plan cards the same height? Use items-stretch on the row, full-height column on the q-card, and col on the section that holds the feature list.

Should the toggle default to monthly or yearly? Both are common. Defaulting to yearly shows the lower monthly figure first. Defaulting to monthly matches what most visitors expect to pay first. Pick one and say clearly which period the price is for.

Can a pricing page be static in Nuxt? Yes. Nothing in these sections needs the server. Prerender the route if your plans only change on deploys.

Next step

Open the Pricing Sections Block, pick a layout with or without the billing switch, and copy the code. The free ones work without an account. Then add an FAQ and a CTA below it and you have the page.