Skip to content

Pricing page

The page that sells the plan. A billing-period switch built on md-segmented-button-set drives the prices in three plan md-cards — the popular one carrying an md-badge and highlight md-chips — above a full md-table comparison whose fine-print rows explain themselves through md-tooltip, and a FAQ md-accordion that answers the objections before sales has to.

Live preview — switch to Annual and watch all three prices update
Plans that grow with your team Save 20% with annual billing
Starter
$19 per month, billed monthly
  • 10 projects
  • 3 team members
  • Community support
Start free trial
Pro · Most popular
$49 per month, billed monthly
  • Unlimited projects
  • 25 team members
Choose Pro
Scale
$99 per month, billed monthly
  • Unlimited everything
  • 99.95% uptime SLA
  • Dedicated support
Contact sales
Feature Starter Pro Scale Projects 10 Unlimited Unlimited Team members 3 25 Unlimited API requests / month 10,000 100,000 2,000,000 Single sign-on (SSO) Audit log retention 30 days 365 days Uptime SLA 99.9% 99.95% Support Community Email Dedicated
Frequently asked questions

Yes — upgrades apply immediately and are prorated; downgrades take effect at the next renewal.

Your workspace becomes read-only. Nothing is deleted for 90 days, and picking any plan restores it instantly.

Annual Pro and Scale plans can pay by invoice with net-30 terms. Monthly plans are card-only.

Annual plans can be refunded pro rata within 30 days of purchase. Contact billing@example.com with your invoice number.

Show code for each technology
<!-- index.html <head> — the icon font the components draw from -->
<link rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap">

<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<md-segmented-button-set aria-label="Billing period">
  <md-segmented-button value="monthly" label="Monthly" selected></md-segmented-button>
  <md-segmented-button value="annual" label="Annual"></md-segmented-button>
</md-segmented-button-set>

<md-card variant="outlined" style="position: relative;">
  <md-badge icon="star"
    style="--md-badge-offset-x: -14px; --md-badge-offset-y: 14px;"></md-badge>
    <h3>Pro · Most popular</h3>
    <p><strong>$49</strong> per month, billed monthly</p>
    <div role="group" aria-label="Pro plan highlights">
      <md-chip variant="assist" label="SSO"></md-chip>
      <md-chip variant="assist" label="Priority support"></md-chip>
    </div>
    <md-button variant="filled" full-width>Choose Pro</md-button>
  </md-card>

  <md-table-container variant="outlined">
    <md-table column-template="1.5fr 1fr 1fr 1fr" label="Plan comparison" striped>
      <md-table-head>
        <md-table-row rowgroup="head">
          <md-table-cell head scope="col">Feature</md-table-cell>
          <md-table-cell head scope="col" align="center">Pro</md-table-cell>
        </md-table-row>
      </md-table-head>
      <md-table-body>
        <md-table-row value="sla">
          <md-table-cell head scope="row">
            <md-tooltip text="Measured per calendar month; credits apply automatically.">
              <span tabindex="0">Uptime SLA</span>
            </md-tooltip>
          </md-table-cell>
          <md-table-cell align="center">99.9%</md-table-cell>
        </md-table-row>
      </md-table-body>
    </md-table>
  </md-table-container>

  <md-accordion exclusive heading-level="3" variant="outlined">
    <md-accordion-item headline="Can I change plans later?">
      <p>Yes — upgrades apply immediately and are prorated.</p>
    </md-accordion-item>
  </md-accordion>

<script type="module">
  const billing = document.querySelector('[data-billing]');
  const prices = document.querySelectorAll('[data-price]');
  const terms = document.querySelectorAll('[data-term]');
  const savings = document.querySelector('[data-savings]');

  // Single-select mdChange always reports exactly one value, and it fires on
  // every activation (including a re-click of the selected segment), so the
  // handler is written to be idempotent.
  billing.addEventListener('mdChange', (e) => {
    const annual = e.detail[0] === 'annual';
    prices.forEach((el) => {
      el.textContent = '$' + (annual ? el.dataset.annual : el.dataset.monthly);
    });
    terms.forEach((el) => {
      el.textContent = annual
        ? ' per month, billed annually'
        : ' per month, billed monthly';
    });
    savings.textContent = annual
      ? 'You are saving 20% with annual billing'
      : 'Save 20% with annual billing';
  });
</script>
ComponentRole in this screen
md-segmented-button-setThe billing toggle. Single-select radiogroup over two segments; its mdChange carries the full selection snapshot (['annual']), and the initial state is expressed with selected on the Monthly child — the set has no value prop.
md-cardOne outlined card per plan. They hold their own md-buttons, so they are deliberately not interactive — the buttons are the controls. The popular card just overrides --md-card-outline-color and --md-card-outline-width.
md-badgeThe “popular” marker on the Pro card. It anchors to the nearest positioned ancestor, so the card gets position: relative, and the offset properties pull it inside the card’s overflow: hidden boundary.
md-chipPlan highlights on the Pro card, as a set inside a labelled role="group" — chips never appear alone.
md-tableThe full comparison. column-template declares the four tracks, head scope="col" / head scope="row" make every value announce with its plan and feature, and label names the grid for screen readers.
md-tooltipFine print on the metered rows — the trigger is wrapped in the default slot, and each span gets tabindex="0" so keyboard users get the same disclosure.
md-accordionThe FAQ. exclusive keeps one answer open at a time; each item renders a real heading wrapping a button with aria-expanded.
  • The price update is idempotent by design. The set’s mdChange fires on every activation — including re-clicking the already-selected segment — and single-select can never report an empty selection, so the handler just rewrites the DOM from e.detail[0] with no state to diff.
  • The badge is a glyph, not the word “Popular”. md-badge shows at most 4 characters of value, so the card uses icon="star" plus visible text in the plan title; the badge itself is role="status", takes no pointer events, and never blocks the card underneath.
  • Cards keep the keyboard path on the buttons. A card holding an md-button must not be interactive: the component would detect the focusable child and drop its own role="button", leaving a mouse-only card action. Here each card is a plain container and the CTA is the single control.
  • Tooltip fine print is keyboard-reachable and WCAG 1.4.13 compliant. The plain-text triggers carry tabindex="0" (a bare span gets hover only), the popup opens on focus as well as hover, and the default hide-delay keeps the grace period that lets a pointer travel into the popup. The tooltip sets aria-description on the trigger — a description, never a name.
  • The comparison table is a real ARIA grid. md-table lays out with CSS Grid but stamps role="table" / row / columnheader / rowheader from your head + scope attributes, so “99.9%” announces as Uptime SLA, Pro rather than as loose text.
  • Localized currency: format prices with Intl.NumberFormat before writing textContent, and keep the raw amounts in the data-monthly / data-annual attributes.
  • Always-open FAQ: add keep-one-expanded next to exclusive so exactly one answer is open at all times — the open header advertises aria-disabled="true" instead of collapsing.
  • A third billing term: segmented buttons scale to five segments, so a “2-year” option is one more md-segmented-button child plus a data-biennial price on each card.
  • A very long feature matrix: wrap the table in md-table-container max-height="60vh" and set frozen-header so the plan names stay visible while the body scrolls.