Table of Contents
Introdução: Por que uma tabela de preços dinâmicos importa
As tabelas de preços são uma pedra angular de qualquer produto ou site de serviços. Uma tabela estática, de tamanho único, pode apresentar os seus pontos de preço, mas uma tabela de preços dinâmicos ] — uma tabela que atualiza em tempo real com base nas interações do usuário — pode aumentar significativamente as taxas de conversão. Ao permitir que os visitantes escolham entre faturamento mensal e anual, veja o custo total com complementos, ou compare recursos interativamente, você cria uma experiência transparente e envolvente. Neste guia, construiremos uma tabela de preços dinâmicos pronta para produção usando JavaScript simples e CSS Grid. Sem frameworks, sem bibliotecas — código apenas sólido e mantendível que funciona em navegadores modernos.
Configurar a Estrutura HTML para uma Grade de Preços
A base de qualquer tabela de preços dinâmica é uma estrutura HTML limpa e semântica. Começaremos com um recipiente que se tornará nossa grade CSS, e depois aninharemos cartões de preços individuais dentro dele. Cada cartão deve conter o nome do plano, preço, uma lista de recursos e um botão call-to-action. Para ativar atualizações dinâmicas, incluiremos um atributo em cada cartão e estruturaremos o recipiente de preços com uma classe que podemos direcionar com JavaScript.
<div class="pricing-grid" role="group" aria-label="Pricing plans">
<div class="pricing-card" data-plan="basic">
<h3 class="plan-name">Basic</h3>
<p class="price">
<span class="amount">$10</span>
<span class="period">/mo</span>
</p>
<ul class="features">
<li>5 projects</li>
<li>10 GB storage</li>
<li>Basic support</li>
</ul>
<a href="#" class="btn">Get Started</a>
</div>
<div class="pricing-card featured" data-plan="pro">
<h3 class="plan-name">Pro</h3>
<p class="price">
<span class="amount">$25</span>
<span class="period">/mo</span>
</p>
<ul class="features">
<li>Unlimited projects</li>
<li>100 GB storage</li>
<li>Priority support</li>
</ul>
<a href="#" class="btn">Get Started</a>
</div>
<div class="pricing-card" data-plan="enterprise">
<h3 class="plan-name">Enterprise</h3>
<p class="price">
<span class="amount">$50</span>
<span class="period">/mo</span>
</p>
<ul class="features">
<li>Unlimited projects</li>
<li>1 TB storage</li>
<li>24/7 support</li>
</ul>
<a href="#" class="btn">Contact Sales</a>
</div>
</div>
Observe a e para melhorar a acessibilidade. A classe no cartão Pro permite destacar a opção mais popular sem JavaScript extra.
Construindo o layout da grade CSS
CSS Grid torna simples criar uma grade responsiva que ajusta o número de colunas com base na viewport. Nós definimos o recipiente como uma grade, definimos um número flexível de colunas com e , e adicionamos espaçamento com . Dentro de cada placa, usamos CSS Grid internamente para alinhar o preço, características e botão.
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.5rem;
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.pricing-card {
display: grid;
grid-template-rows: auto auto 1fr auto;
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 2rem;
text-align: center;
background: #fff;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.pricing-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.pricing-card.featured {
border: 2px solid #007bff;
position: relative;
}
.pricing-card.featured::before {
content: "Most Popular";
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
background: #007bff;
color: #fff;
padding: 4px 16px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
}
.plan-name {
font-size: 1.5rem;
margin: 0 0 0.5rem;
}
.price {
margin: 0 0 1.5rem;
}
.amount {
font-size: 2.5rem;
font-weight: 700;
}
.period {
font-size: 1rem;
color: #666;
}
.features {
list-style: none;
padding: 0;
margin: 0 0 1.5rem;
}
.features li {
padding: 0.5rem 0;
border-bottom: 1px solid #eee;
}
.features li:last-child {
border-bottom: none;
}
.btn {
display: inline-block;
padding: 0.75rem 1.5rem;
background: #007bff;
color: #fff;
text-decoration: none;
border-radius: 8px;
transition: background 0.3s;
}
.btn:hover {
background: #0056b3;
}
O garante que o botão sempre permanece na parte inferior, mesmo que as listas de recursos diferem em comprimento. O crachá “Mais Popular” do cartão apresentado é criado com um pseudoelemento para evitar marcação extra.
Adicionando a Interatividade JavaScript
Agora vem a parte dinâmica. Vamos adicionar um interruptor que permite aos visitantes alternar entre os preços mensais e anuais. Quando a opção mudar, o JavaScript atualiza os preços de todas as cartas. Também incluimos um auxiliar para formatar preços e uma transição suave para a mudança numérica.
<div class="pricing-toggle">
<label class="toggle-label">
<span>Monthly</span>
<input type="checkbox" id="billing-toggle" role="switch" aria-checked="false">
<span class="toggle-slider"></span>
<span>Yearly (save up to 20%)</span>
</label>
</div>
CSS para a opção:
.pricing-toggle {
text-align: center;
margin-bottom: 2rem;
}
.toggle-label {
display: inline-flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
user-select: none;
}
.toggle-label input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
width: 50px;
height: 26px;
background: #ccc;
border-radius: 13px;
position: relative;
transition: background 0.3s;
}
.toggle-slider::after {
content: "";
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.3s;
}
input:checked + .toggle-slider {
background: #007bff;
}
input:checked + .toggle-slider::after {
transform: translateX(24px);
}
JavaScript que alimenta a atualização:
document.addEventListener('DOMContentLoaded', () => {
const toggle = document.getElementById('billing-toggle');
const prices = {
basic: { monthly: 10, yearly: 8 }, // per month when billed yearly
pro: { monthly: 25, yearly: 20 },
enterprise: { monthly: 50, yearly: 40 }
};
function updatePrices(isYearly) {
document.querySelectorAll('.pricing-card').forEach(card => {
const plan = card.dataset.plan;
if (!plan || !prices[plan]) return;
const amountElement = card.querySelector('.amount');
const periodElement = card.querySelector('.period');
let amount, period;
if (isYearly) {
amount = prices[plan].yearly;
period = ' /mo (billed yearly)';
} else {
amount = prices[plan].monthly;
period = ' /mo';
}
amountElement.textContent = `$${amount}`;
periodElement.textContent = period;
});
}
toggle.addEventListener('change', () => {
const isYearly = toggle.checked;
updatePrices(isYearly);
toggle.setAttribute('aria-checked', isYearly);
});
// Initialize with monthly
updatePrices(false);
});
O objeto armazena as taxas mensais e anuais. Os preços anuais representam o custo anual ao pagar anualmente — um padrão comum. A função itera sobre cada cartão, lê o seu , e atualiza o valor e o período exibidos. Também atualizamos o atributo para manter a opção acessível.
Melhorar a transição
Para que a mudança de preço se sinta suave, podemos adicionar uma breve transição CSS nos elementos e . No entanto, uma vez que muda instantaneamente, podemos simular um efeito de contagem com um pequeno intervalo, ou simplesmente usar um fade-in. Para um site de produção, uma rápida transição de opacidade no recipiente de preço funciona bem. Adicione isso ao seu CSS:
.price {
transition: opacity 0.2s ease;
}
.updating .price {
opacity: 0;
}
Em seguida, no JavaScript, adicione e remova brevemente a classe em cada cartão. Mas, para a maioria dos casos de uso, a atualização instantânea é boa.
Tornar a tabela responsiva e acessível
O CSS Grid já lida com a capacidade de resposta, mas assegura que em telas muito pequenas as cartas empilham em uma única coluna. O valor faz isso automaticamente. Para acessibilidade, temos que garantir que a opção seja operavel pelo teclado e anuncia seu estado. O e já adicionamos leitores de tela de ajuda. Além disso, adicione ao rótulo pai do botão para clareza.
Além disso, forneça um indicador de foco visível para usuários de teclado. O contorno padrão funciona, mas você pode styleá- lo para corresponder à sua marca:
.toggle-label input:focus-visible + .toggle-slider {
outline: 2px solid #007bff;
outline-offset: 4px;
}
Teste a tabela de preços com um leitor de tela para verificar se as mudanças de preços são anunciadas. Para melhorar isso, após atualizar o preço, você pode usar uma região ao vivo:
<div aria-live="polite" aria-atomic="true" class="sr-only"></div>
Em JavaScript, configure o seu texto para algo como “Preços atualizados para faturamento anual” após cada comutador. Este elemento somente para leitura de tela irá anunciar a mudança sem perturbação visual.
Adicionando mais recursos dinâmicos
Uma vez que o núcleo desativado funcione, você pode estender a tabela de preços com interatividade adicional:
Alternações de Caracteres
Adicione caixas de seleção para complementos (por exemplo, armazenamento extra, membros da equipe) que atualizem o preço total. Guarde os preços base e custos adicionais em um objeto. Quando uma caixa de seleção muda, recalcule o total para cada cartão de acordo.
Seletor de Moedas
Se o seu público for global, adicione uma lista para alternar entre USD, EUR, GBP, etc. Armazene as taxas de conversão e multiplique o preço base. Atualize o texto com formatação de moeda adequada usando .
Contador de preços animado
Para um efeito polido, anime o número do valor antigo ao novo valor de algumas centenas de milissegundos. Bibliotecas como animate.css[ não são necessárias — um loop simples funciona. No entanto, mantenha-o performante: apenas anima as cartas visíveis.
Distintivo de Poupança
Quando a cobrança anual for comutada, mostre um crachá “Salvar 20%” próximo ao preço. Calcule a economia como e a mostre dinamicamente. Adicione um atributo a cada cartão no HTML e deixe o JavaScript computá-lo ou computá-lo em tempo real.
Considerações sobre o desempenho
Com apenas algumas placas de preços, o desempenho não é uma preocupação. Mas se você tem dezenas de planos (por exemplo, SaaS que gera planos de um banco de dados), considere essas otimizações:
- Use a delegação de eventos: Anexar um ouvinte de eventos a um recipiente pai em vez de cada cartão. Para a opção, isso já está feito. Para caixas de seleção dentro de cartões, delegue.
- Cache DOM referências: Na inicialização, armazenar referências para os elementos e em um array ou Map by plan key. Então, ao atualizar, você não precisa consultar o DOM novamente.
- Use para animações: Programe atualizações visuais para o próximo ciclo de pintura para evitar o uso de layout.
- Minimizar fluxos: O DOM em lote muda ao atualizar várias cartas. Na nossa função , lemos e escrevemos para o DOM em um único loop — tudo bem. Se você adicionar classes ou estilos em linha, considere ler todos os valores primeiro, depois escreva todas as alterações.
Casos e exemplos de uso do mundo real
As tabelas dinâmicas de preços são usadas em toda parte desde os serviços de assinatura como Dropbox e Spotify[ a provedores de hospedagem como DigitalOcean. Um padrão comum é oferecer uma alternância mensal/anual com um rótulo “Melhor Valor” na opção anual. Algumas empresas também mostram uma taxa anual total próxima às economias mensais. Por exemplo, um plano de $10/mo com faturamento anual pode exibir “$8/mo – faturado $96 anualmente”. Nossa estrutura já suporta isso com o span.
Outra variação é escalas deslizantes para quantidade. Uma tabela de preços para um produto SaaS pode ter barras deslizantes para número de usuários ou armazenamento. À medida que o usuário move a barra deslizante, o preço atualiza ao vivo. A grade CSS lida com o layout e o JavaScript atualiza os valores de preço. Isto pode ser construído com os mesmos princípios: armazenar níveis de preços, ouvir as alterações de entrada e atualizar o DOM.
Para uma abordagem mais avançada, você pode carregar dados de preços de um arquivo JSON ou API, tornando a tabela verdadeiramente orientada por dados. Se o seu site for construído com um CMS sem cabeça como Directus, os dados de preços viriam de uma coleção, e seu JavaScript front-end iria buscá-lo e renderizar a grade dinamicamente. Esta abordagem mantém os preços em sincronia com sua infraestrutura.
Suporte ao Teste e Navegador
O CSS Grid é suportado em todos os navegadores modernos, incluindo o Internet Explorer 11 com o prefixo — embora recomendamos usar o Autoprefixer ou ignorar o IE11 para novos projetos. O JavaScript usado aqui (ES6 , , funções de seta, , literais de modelo) funciona no Chrome, Firefox, Safari e Edge. Se você precisar de suporte a navegadores antigos, transpile com Babel ou use e funções tradicionais. O ] é bem suportado nos leitores de tela modernos, mas você também pode usar uma caixa de verificação padrão com uma região oculta como um retorno.
Abordagem Alternativa: Grelha CSS com Propriedades Personalizadas CSS
Um método interessante para evitar o JavaScript para alternar preços é usar as Propriedades Personalizadas CSS e (quando o suporte do navegador amadurece). Você definiria os preços como etc., e alterá- los- á, alternando uma classe no corpo. Contudo, para uma verdadeira lógica dinâmica (por exemplo, adicionando dois complementos), o JavaScript ainda é necessário. A combinação de ambos — CSS para layout e transições, JavaScript para dados dinâmicos — é a mais robusta.
Conclusão
Construindo uma tabela de preços dinâmica com JavaScript e CSS Grid é tanto alcançável quanto se estende. Ao começar com HTML semântico, estruturando a grade com CSS Grid e adicionando JavaScript direto para interatividade, você cria uma ferramenta que pode se adaptar a modelos complexos de preços sem dependências inchadas. Os mesmos princípios — atualizações orientadas por dados, layout responsivo, controles acessíveis — escala para aplicações empresariais. Como um próximo passo, considere ] ler a documentação do CSS Grid do MDN[] para mais técnicas, ou explorar A acessibilidade de Sara Soueidan – primeiro projeto de switch . Para lidar com ligações complexas de dados sem uma estrutura, a Vanilla JS Toolkit oferece ajuda leve.
Agora implemente esses padrões em seu próprio projeto. Seus usuários apreciarão a clareza, e suas taxas de conversão poderão agradecer.