PricingTable

GitLab
Um componente de tabela de preços responsivo que exibe planos em níveis com comparação de recursos.

Uso

O componente PricingTable oferece uma maneira responsiva e personalizável de exibir planos de preços em formato de tabela, alternando automaticamente entre um layout de tabela horizontal no desktop para facilitar a comparação e um layout de card vertical no mobile para uma melhor legibilidade.

  • Solo
    Most popular
    For indie hackers.
    $249
    billed annually/month
    Features
    Number of developers
    1
    Projects
    GitHub repository access
    Updates
    Patch & minor
    Support
    Community
    Security
    SSO
    Audit logs
    Custom security review
  • Team
    For growing teams.
    $499
    billed annually/month
    Features
    Number of developers
    5
    Projects
    GitHub repository access
    Updates
    All updates
    Support
    Priority
    Security
    SSO
    Audit logs
    Custom security review
  • Enterprise
    For large organizations.
    Custom
    Features
    Number of developers
    Unlimited
    Projects
    GitHub repository access
    Updates
    All updates
    Support
    24/7
    Security
    SSO
    Audit logs
    Custom security review

Níveis

Use a prop tiers como um array de objetos para definir os seus planos de preços. Cada objeto de tier suporta as seguintes propriedades:

  • id: string - Unique identifier for the tier (required)
  • title?: string - Name of the pricing plan
  • description?: string - Short description of the plan
  • price?: string - The current price of the plan (e.g., "$99", "€99", "Free")
  • discount?: string - The discounted price that will display the price with strikethrough (e.g., "$79", "€79")
  • billingCycle?: string - The unit price period that appears next to the price (e.g., "/month", "/seat/month")
  • billingPeriod?: string - Additional billing context that appears above the billing cycle (e.g., "billed monthly")
  • badge?: string | BadgeProps - Display a badge next to the title { color: 'primary', variant: 'subtle' }
  • button?: ButtonProps - Configure the CTA button { size: 'lg', block: true }
  • highlight?: boolean - Whether to visually emphasize this tier as the recommended option
  • Solo
    Most popular
    For indie hackers.
    $249
    billed annually/month
  • Team
    For growing teams.
    $499
    billed annually/month
  • Enterprise
    For large organizations.
    Custom
<script setup lang="ts">
import type { PricingTableTier } from '@nitro/ui'

const tiers = ref<PricingTableTier[]>([
  {
    id: 'solo',
    title: 'Solo',
    description: 'For indie hackers.',
    price: '$249',
    billingCycle: '/month',
    billingPeriod: 'billed annually',
    badge: 'Most popular',
    button: {
      label: 'Buy now',
      variant: 'subtle'
    }
  },
  {
    id: 'team',
    title: 'Team',
    description: 'For growing teams.',
    price: '$499',
    billingCycle: '/month',
    billingPeriod: 'billed annually',
    button: {
      label: 'Buy now'
    },
    highlight: true
  },
  {
    id: 'enterprise',
    title: 'Enterprise',
    description: 'For large organizations.',
    price: 'Custom',
    button: {
      label: 'Contact sales',
      color: 'neutral'
    }
  }
])
</script>

<template>
  <NPricingTable :tiers="tiers" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { PricingTableTier } from '@nitro/ui'

const tiers = ref<PricingTableTier[]>([
  {
    id: 'solo',
    title: 'Solo',
    description: 'For indie hackers.',
    price: '$249',
    billingCycle: '/month',
    billingPeriod: 'billed annually',
    badge: 'Most popular',
    button: {
      label: 'Buy now',
      variant: 'subtle'
    }
  },
  {
    id: 'team',
    title: 'Team',
    description: 'For growing teams.',
    price: '$499',
    billingCycle: '/month',
    billingPeriod: 'billed annually',
    button: {
      label: 'Buy now'
    },
    highlight: true
  },
  {
    id: 'enterprise',
    title: 'Enterprise',
    description: 'For large organizations.',
    price: 'Custom',
    button: {
      label: 'Contact sales',
      color: 'neutral'
    }
  }
])
</script>

<template>
  <NPricingTable :tiers="tiers" />
</template>

Seções

Use a prop sections para organizar os recursos em grupos lógicos. Cada seção representa uma categoria de recursos que você quer comparar entre os diferentes tiers de preço.

  • title: string - The heading for the feature section
  • features: PricingTableSectionFeature[] - An array of features with their availability in each tier:
    • Each feature requires a title and a tiers object mapping tier IDs to values
    • Boolean values (true/false) will display as checkmarks (✓) or minus icons (-)
    • String values will be shown as text (e.g., "Unlimited", "Up to 5 users")
    • Numeric values will be displayed as is (e.g., 10, 100)
  • Solo
    For indie hackers.
    $249
     /month
    Features
    Number of developers
    1
    Projects
    Security
    SSO
  • Team
    For growing teams.
    $499
     /month
    Features
    Number of developers
    5
    Projects
    Security
    SSO
  • Enterprise
    For large organizations.
    Custom
    Features
    Number of developers
    Unlimited
    Projects
    Security
    SSO
<script setup lang="ts">
import type { PricingTableTier, PricingTableSection } from '@nitro/ui'

const tiers = ref<PricingTableTier[]>([
  {
    id: 'solo',
    title: 'Solo',
    price: '$249',
    description: 'For indie hackers.',
    billingCycle: '/month',
    button: {
      label: 'Buy now',
      variant: 'subtle'
    }
  },
  {
    id: 'team',
    title: 'Team',
    price: '$499',
    description: 'For growing teams.',
    billingCycle: '/month',
    button: {
      label: 'Buy now'
    }
  },
  {
    id: 'enterprise',
    title: 'Enterprise',
    price: 'Custom',
    description: 'For large organizations.',
    button: {
      label: 'Contact sales',
      color: 'neutral'
    }
  }
])
const sections = ref<PricingTableSection[]>([
  {
    title: 'Features',
    features: [
      {
        title: 'Number of developers',
        tiers: {
          solo: '1',
          team: '5',
          enterprise: 'Unlimited'
        }
      },
      {
        title: 'Projects',
        tiers: {
          solo: true,
          team: true,
          enterprise: true
        }
      }
    ]
  },
  {
    title: 'Security',
    features: [
      {
        title: 'SSO',
        tiers: {
          solo: false,
          team: true,
          enterprise: true
        }
      }
    ]
  }
])
</script>

<template>
  <NPricingTable :tiers="tiers" :sections="sections" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { PricingTableTier, PricingTableSection } from '@nitro/ui'

const tiers = ref<PricingTableTier[]>([
  {
    id: 'solo',
    title: 'Solo',
    price: '$249',
    description: 'For indie hackers.',
    billingCycle: '/month',
    button: {
      label: 'Buy now',
      variant: 'subtle'
    }
  },
  {
    id: 'team',
    title: 'Team',
    price: '$499',
    description: 'For growing teams.',
    billingCycle: '/month',
    button: {
      label: 'Buy now'
    }
  },
  {
    id: 'enterprise',
    title: 'Enterprise',
    price: 'Custom',
    description: 'For large organizations.',
    button: {
      label: 'Contact sales',
      color: 'neutral'
    }
  }
])
const sections = ref<PricingTableSection[]>([
  {
    title: 'Features',
    features: [
      {
        title: 'Number of developers',
        tiers: {
          solo: '1',
          team: '5',
          enterprise: 'Unlimited'
        }
      },
      {
        title: 'Projects',
        tiers: {
          solo: true,
          team: true,
          enterprise: true
        }
      }
    ]
  },
  {
    title: 'Security',
    features: [
      {
        title: 'SSO',
        tiers: {
          solo: false,
          team: true,
          enterprise: true
        }
      }
    ]
  }
])
</script>

<template>
  <NPricingTable :tiers="tiers" :sections="sections" />
</template>

Exemplos

Com slots

O componente PricingTable oferece poderosas opções de personalização de slots para ajustar a exibição do seu conteúdo. Você pode personalizar elementos individuais usando slots genéricos ou mirar itens específicos usando os IDs deles.

  • Solo
    Para indie hackers.
    $249
     /month
    Recursos
    Número de desenvolvedores
    1
    Projetos
    Segurança
    SSO
  • Equipe
    Para equipes em crescimento.
    $499
     /month
    Recursos
    Número de desenvolvedores
    5
    Projetos
    Segurança
    SSO
  • Enterprise
    Para grandes organizações.
    Custom
    Recursos
    Número de desenvolvedores
    Unlimited
    Projetos
    Segurança
    SSO

O componente suporta vários tipos de slot para máxima flexibilidade de personalização:

Slot TypePatternDescriptionExample
Tier slots#{tier-id}-{element}Target specific tiers#team-title, #solo-price
Section slots#section-{id|formatted-title}-titleTarget specific sections#section-features-title
Feature slots#feature-{id|formatted-title}-{title|value}Target specific features#feature-developers-title
Generic slots#tier-title, #section-title, etc.Apply to all items#feature-value
Quando nenhum id é fornecido, o nome do slot é gerado automaticamente a partir do título (ex.: "Premium Features!" vira #section-premium-features-title).

API

Props

Prop Default Type
as'div'any

The element or component this component should render as.

tiersT[]

The pricing tiers to display in the table. Each tier represents a pricing plan with its own title, description, price, and features.

sectionsPricingTableSection<T>[]

The sections of features to display in the table. Each section contains a title and a list of features with their availability in each tier.

caption string

The caption to display above the table.

ui { root?: SlotClass; table?: SlotClass; list?: SlotClass; item?: SlotClass; caption?: SlotClass; thead?: SlotClass; tbody?: SlotClass; tr?: SlotClass; th?: SlotClass; td?: SlotClass; tier?: SlotClass; tierWrapper?: SlotClass; tierTitleWrapper?: SlotClass; tierTitle?: SlotClass; tierDescription?: SlotClass; tierBadge?: SlotClass; tierPriceWrapper?: SlotClass; tierPrice?: SlotClass; tierDiscount?: SlotClass; tierBilling?: SlotClass; tierBillingPeriod?: SlotClass; tierBillingCycle?: SlotClass; tierButton?: SlotClass; tierFeatureIcon?: SlotClass; section?: SlotClass; sectionTitle?: SlotClass; feature?: SlotClass; featureTitle?: SlotClass; featureValue?: SlotClass; }

Slots

Slot Type
caption{}
tier{ tier: T; }
tier-title{ tier: T; }
tier-description{ tier: T; }
tier-badge{ tier: T; }
tier-button{ tier: T; }
tier-billing{ tier: T; }
tier-discount{ tier: T; }
tier-price{ tier: T; }
section-title{ section: PricingTableSection<T>; }
feature-title{ feature: PricingTableSectionFeature<T>; section: PricingTableSection<T>; }
feature-value{ feature: PricingTableSectionFeature<T>; tier: T; section: PricingTableSection<T>; }

Tema

app.config.ts
export default defineAppConfig({
  ui: {
    pricingTable: {
      slots: {
        root: 'w-full relative',
        table: 'w-full table-fixed border-separate border-spacing-x-0 hidden md:table h-fit',
        list: 'md:hidden flex flex-col gap-6 w-full',
        item: 'p-6 flex flex-col border border-default rounded-lg',
        caption: 'sr-only',
        thead: '',
        tbody: '',
        tr: '',
        th: 'py-4 font-normal text-left rtl:text-right border-b border-default',
        td: 'px-6 py-4 text-center border-b border-default',
        tier: 'p-6 text-left rtl:text-right font-normal align-top h-full',
        tierWrapper: 'flex flex-col md:h-full',
        tierTitleWrapper: 'flex items-center gap-3',
        tierTitle: 'text-lg font-semibold text-highlighted',
        tierDescription: 'text-sm font-normal text-muted mt-1',
        tierBadge: 'truncate',
        tierPriceWrapper: 'flex items-center gap-1 mt-4',
        tierPrice: 'text-highlighted text-3xl sm:text-4xl font-semibold',
        tierDiscount: 'text-muted line-through text-xl sm:text-2xl',
        tierBilling: 'flex flex-col justify-between min-w-0',
        tierBillingPeriod: 'text-toned truncate text-xs font-medium',
        tierBillingCycle: 'text-muted truncate text-xs font-medium',
        tierButton: 'mt-6 md:mt-auto md:pt-6',
        tierFeatureIcon: 'size-5 shrink-0',
        section: 'mt-6 flex flex-col gap-2',
        sectionTitle: 'font-semibold text-sm text-highlighted',
        feature: 'flex items-center justify-between gap-1',
        featureTitle: 'text-sm text-default',
        featureValue: 'text-sm text-muted flex justify-center min-w-5'
      },
      variants: {
        section: {
          true: {
            tr: '*:pt-8'
          }
        },
        active: {
          true: {
            tierFeatureIcon: 'text-primary'
          }
        },
        highlight: {
          true: {
            tier: 'bg-elevated/50 border-x border-t border-default rounded-t-lg',
            td: 'bg-elevated/50 border-x border-default',
            item: 'bg-elevated/50'
          }
        }
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nitro/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        pricingTable: {
          slots: {
            root: 'w-full relative',
            table: 'w-full table-fixed border-separate border-spacing-x-0 hidden md:table h-fit',
            list: 'md:hidden flex flex-col gap-6 w-full',
            item: 'p-6 flex flex-col border border-default rounded-lg',
            caption: 'sr-only',
            thead: '',
            tbody: '',
            tr: '',
            th: 'py-4 font-normal text-left rtl:text-right border-b border-default',
            td: 'px-6 py-4 text-center border-b border-default',
            tier: 'p-6 text-left rtl:text-right font-normal align-top h-full',
            tierWrapper: 'flex flex-col md:h-full',
            tierTitleWrapper: 'flex items-center gap-3',
            tierTitle: 'text-lg font-semibold text-highlighted',
            tierDescription: 'text-sm font-normal text-muted mt-1',
            tierBadge: 'truncate',
            tierPriceWrapper: 'flex items-center gap-1 mt-4',
            tierPrice: 'text-highlighted text-3xl sm:text-4xl font-semibold',
            tierDiscount: 'text-muted line-through text-xl sm:text-2xl',
            tierBilling: 'flex flex-col justify-between min-w-0',
            tierBillingPeriod: 'text-toned truncate text-xs font-medium',
            tierBillingCycle: 'text-muted truncate text-xs font-medium',
            tierButton: 'mt-6 md:mt-auto md:pt-6',
            tierFeatureIcon: 'size-5 shrink-0',
            section: 'mt-6 flex flex-col gap-2',
            sectionTitle: 'font-semibold text-sm text-highlighted',
            feature: 'flex items-center justify-between gap-1',
            featureTitle: 'text-sm text-default',
            featureValue: 'text-sm text-muted flex justify-center min-w-5'
          },
          variants: {
            section: {
              true: {
                tr: '*:pt-8'
              }
            },
            active: {
              true: {
                tierFeatureIcon: 'text-primary'
              }
            },
            highlight: {
              true: {
                tier: 'bg-elevated/50 border-x border-t border-default rounded-t-lg',
                td: 'bg-elevated/50 border-x border-default',
                item: 'bg-elevated/50'
              }
            }
          }
        }
      }
    })
  ]
})

Changelog

No recent changes