Neste tutorial, você vai criar uma calculadora HTML completa usando três arquivos: index.html, style.css e script.js. O projeto realiza as quatro operações básicas, porcentagem, números decimais, limpeza, correção de dígitos e também aceita comandos pelo teclado.
A versão abaixo não usa eval(). Em vez de executar uma expressão como texto, o JavaScript mantém o número atual, o número anterior e o operador selecionado. Isso deixa o exemplo mais seguro e mais fácil de entender.
Resumo: o HTML cria o visor e os botões, o CSS organiza a interface com Grid e o JavaScript controla o estado da operação e calcula o resultado.
Teste a calculadora pronta
Clique nos botões abaixo. Para usar o teclado, clique uma vez dentro da calculadora e pressione números, operadores, Enter, Backspace ou Escape.
O que você vai praticar
- estrutura semântica com HTML e atributos
data-*; - layout responsivo com CSS Grid;
- seleção de elementos com
querySelector(); - eventos de clique e teclado com
addEventListener(); - funções, condições, estado e conversão de números;
- tratamento de divisão por zero e entradas decimais.
Crie os arquivos do projeto
Crie uma pasta chamada calculadora-html e adicione estes três arquivos dentro dela:
index.htmlstyle.cssscript.js
Abra o arquivo index.html no navegador depois de copiar os três códigos. Você também pode usar o Editor HTML do Código Fácil para testar pequenas alterações.
1. Código HTML da calculadora
O HTML cria o visor, agrupa os botões e usa atributos como data-numero, data-operador e data-acao. Esses atributos permitem que o JavaScript descubra o que cada botão deve fazer sem usar vários atributos onclick.
index.html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calculadora HTML</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="calculadora" data-calculadora tabindex="0">
<div class="visor" aria-live="polite">
<span class="historico" data-historico></span>
<output class="resultado" data-visor>0</output>
</div>
<div class="teclado">
<button type="button" data-acao="limpar">AC</button>
<button type="button" data-acao="apagar">⌫</button>
<button type="button" data-acao="porcentagem">%</button>
<button type="button" class="operador" data-operador="/">÷</button>
<button type="button" data-numero="7">7</button>
<button type="button" data-numero="8">8</button>
<button type="button" data-numero="9">9</button>
<button type="button" class="operador" data-operador="*">×</button>
<button type="button" data-numero="4">4</button>
<button type="button" data-numero="5">5</button>
<button type="button" data-numero="6">6</button>
<button type="button" class="operador" data-operador="-">−</button>
<button type="button" data-numero="1">1</button>
<button type="button" data-numero="2">2</button>
<button type="button" data-numero="3">3</button>
<button type="button" class="operador" data-operador="+">+</button>
<button type="button" class="zero" data-numero="0">0</button>
<button type="button" data-acao="decimal">,</button>
<button type="button" class="igual" data-acao="calcular">=</button>
</div>
</main>
<script src="script.js"></script>
</body>
</html>Partes importantes do HTML
outputexibe o resultado e combina melhor com um valor calculado do que um campo editável;aria-live="polite"ajuda leitores de tela a perceber atualizações no visor;type="button"impede que os botões tentem enviar um formulário;tabindex="0"permite focar a calculadora e usar os atalhos do teclado;- o CSS e o JavaScript são carregados por arquivos separados.
Para revisar os fundamentos, consulte também o que é HTML.
2. CSS para estilizar a calculadora
O CSS centraliza o projeto na página, cria o painel escuro e organiza os botões em quatro colunas com Grid. A propriedade width: min(100%, 340px) evita que a calculadora ultrapasse a largura disponível em telas pequenas.
style.css
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
font-family: system-ui, sans-serif;
background: #eef2f7;
}
.calculadora {
width: min(100%, 340px);
padding: 18px;
border-radius: 20px;
background: #161a22;
box-shadow: 0 18px 45px rgba(0, 0, 0, 0.2);
}
.visor {
min-height: 96px;
margin-bottom: 14px;
padding: 14px;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
overflow: hidden;
border-radius: 14px;
background: #f8fafc;
}
.historico {
min-height: 24px;
color: #64748b;
font-size: 0.95rem;
}
.resultado {
max-width: 100%;
overflow: hidden;
color: #0f172a;
font-size: clamp(2rem, 8vw, 3rem);
line-height: 1.1;
text-overflow: ellipsis;
}
.teclado {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
button {
min-height: 58px;
border: 0;
border-radius: 14px;
color: #0f172a;
background: #e2e8f0;
font: inherit;
font-size: 1.15rem;
font-weight: 700;
cursor: pointer;
}
button:hover {
filter: brightness(0.96);
}
button:focus-visible {
outline: 3px solid #38bdf8;
outline-offset: 2px;
}
.operador,
.igual {
color: #ffffff;
background: #2563eb;
}
.zero {
grid-column: span 2;
}Como o CSS Grid organiza os botões
A regra grid-template-columns: repeat(4, 1fr) cria quatro colunas do mesmo tamanho. O botão zero recebe grid-column: span 2, por isso ocupa o espaço de duas colunas.
O estilo de foco com :focus-visible também melhora a navegação por teclado. Veja mais exemplos em o que é CSS e em botões personalizados com CSS.
3. JavaScript da calculadora
O JavaScript guarda quatro informações principais: o valor atual, o valor anterior, o operador escolhido e se o próximo número deve iniciar uma nova entrada.
script.js
const calculadora = document.querySelector("[data-calculadora]");
const visor = calculadora.querySelector("[data-visor]");
const historico = calculadora.querySelector("[data-historico]");
let valorAtual = "0";
let valorAnterior = "";
let operador = null;
let aguardandoNovoNumero = false;
function atualizarVisor() {
visor.textContent = valorAtual;
historico.textContent =
operador && valorAnterior
? `${valorAnterior} ${simboloDoOperador(operador)}`
: "";
}
function simboloDoOperador(valor) {
return { "/": "÷", "*": "×", "-": "−", "+": "+" }[valor] ?? valor;
}
function numeroParaCalculo(valor) {
return Number(valor.replace(",", "."));
}
function numeroParaVisor(valor) {
if (!Number.isFinite(valor)) {
return "Erro";
}
return String(Number(valor.toFixed(10))).replace(".", ",");
}
function inserirNumero(numero) {
if (valorAtual === "Erro" || aguardandoNovoNumero) {
valorAtual = numero;
aguardandoNovoNumero = false;
} else {
valorAtual = valorAtual === "0" ? numero : valorAtual + numero;
}
atualizarVisor();
}
function inserirDecimal() {
if (valorAtual === "Erro" || aguardandoNovoNumero) {
valorAtual = "0,";
aguardandoNovoNumero = false;
} else if (!valorAtual.includes(",")) {
valorAtual += ",";
}
atualizarVisor();
}
function escolherOperador(novoOperador) {
if (valorAtual === "Erro") {
limpar();
return;
}
if (operador && !aguardandoNovoNumero) {
calcular();
}
valorAnterior = valorAtual;
operador = novoOperador;
aguardandoNovoNumero = true;
atualizarVisor();
}
function calcular() {
if (!operador || aguardandoNovoNumero || valorAtual === "Erro") {
return;
}
const primeiro = numeroParaCalculo(valorAnterior);
const segundo = numeroParaCalculo(valorAtual);
let resultado;
switch (operador) {
case "+":
resultado = primeiro + segundo;
break;
case "-":
resultado = primeiro - segundo;
break;
case "*":
resultado = primeiro * segundo;
break;
case "/":
resultado = segundo === 0 ? NaN : primeiro / segundo;
break;
default:
return;
}
valorAtual = numeroParaVisor(resultado);
valorAnterior = "";
operador = null;
aguardandoNovoNumero = true;
atualizarVisor();
}
function porcentagem() {
if (valorAtual === "Erro") {
return;
}
valorAtual = numeroParaVisor(numeroParaCalculo(valorAtual) / 100);
atualizarVisor();
}
function apagar() {
if (valorAtual === "Erro" || aguardandoNovoNumero) {
return;
}
valorAtual = valorAtual.length > 1 ? valorAtual.slice(0, -1) : "0";
atualizarVisor();
}
function limpar() {
valorAtual = "0";
valorAnterior = "";
operador = null;
aguardandoNovoNumero = false;
atualizarVisor();
}
calculadora.addEventListener("click", (evento) => {
const botao = evento.target.closest("button");
if (!botao) {
return;
}
if (botao.dataset.numero) inserirNumero(botao.dataset.numero);
if (botao.dataset.operador) escolherOperador(botao.dataset.operador);
if (botao.dataset.acao === "decimal") inserirDecimal();
if (botao.dataset.acao === "calcular") calcular();
if (botao.dataset.acao === "porcentagem") porcentagem();
if (botao.dataset.acao === "apagar") apagar();
if (botao.dataset.acao === "limpar") limpar();
});
calculadora.addEventListener("keydown", (evento) => {
const tecla = evento.key;
if (/^[0-9]$/.test(tecla)) inserirNumero(tecla);
else if (["+", "-", "*", "/"].includes(tecla)) escolherOperador(tecla);
else if (tecla === "," || tecla === ".") inserirDecimal();
else if (tecla === "Enter" || tecla === "=") calcular();
else if (tecla === "Backspace") apagar();
else if (tecla === "Escape") limpar();
else return;
evento.preventDefault();
});
atualizarVisor();Como o cálculo funciona
inserirNumero()adiciona dígitos ao visor;inserirDecimal()impede duas vírgulas no mesmo número;escolherOperador()guarda o primeiro número e a operação;calcular()executa soma, subtração, multiplicação ou divisão;numeroParaVisor()limita erros de ponto flutuante e troca ponto por vírgula;porcentagem()divide o valor atual por 100;limpar()restaura todo o estado da calculadora.
A divisão por zero retorna Erro. O código também verifica se o resultado é um número finito antes de mostrá-lo. Para entender melhor os conceitos usados, veja o que é JavaScript.
Atalhos do teclado
| Tecla | Ação |
|---|---|
| 0 a 9 | Insere números |
| + – * / | Seleciona a operação |
| , ou . | Insere decimal |
| Enter ou = | Calcula o resultado |
| Backspace | Apaga o último dígito |
| Escape | Limpa a calculadora |
Os eventos de teclado ficam presos ao elemento da calculadora. Assim, os atalhos só são executados quando ela está em foco e não interferem no restante da página.
Por que evitar eval() neste projeto?
A função eval() executa uma string como código JavaScript. Em um exemplo totalmente controlado ela pode parecer conveniente, mas se a entrada puder ser manipulada, o risco aumenta. Além disso, usar funções específicas para cada operação deixa o fluxo mais claro para quem está aprendendo.
Nesta versão, apenas números e operadores conhecidos entram no cálculo. Nenhum texto é executado como código.
Erros comuns e como corrigir
A calculadora aparece sem estilo
Confirme se o arquivo se chama exatamente style.css e está na mesma pasta do index.html. Verifique também o atributo href="style.css".
Os botões não fazem nada
Confira se o arquivo script.js está na mesma pasta e se a linha <script src="script.js"></script> foi colocada antes do fechamento de body.
O separador decimal não funciona
A interface usa vírgula, mas o JavaScript converte o valor para ponto antes de usar Number(). Não remova as conversões feitas em numeroParaCalculo() e numeroParaVisor().
Aparece Erro ao dividir
O resultado Erro é esperado quando o segundo número de uma divisão é zero. Pressione AC ou Escape para começar uma nova operação.
Ideias para evoluir o projeto
- adicionar memória com botões M+, M- e MR;
- criar um histórico das últimas operações;
- incluir tema claro e tema escuro;
- aceitar parênteses e expressões maiores usando um parser próprio;
- transformar o projeto em uma calculadora científica;
- salvar o histórico com
localStorage; - adicionar testes automatizados para as funções de cálculo.
Perguntas frequentes
É possível criar uma calculadora apenas com HTML?
O HTML consegue montar o visor e os botões, mas não executa os cálculos. Para uma calculadora funcional, você precisa de JavaScript ou de outra linguagem que processe as operações.
Posso colocar HTML, CSS e JavaScript em um único arquivo?
Sim. O CSS pode ficar dentro de uma tag style e o JavaScript dentro de uma tag script. Separar os arquivos, porém, facilita a organização e a manutenção.
Esta calculadora funciona no celular?
Sim. A largura é responsiva e os botões são organizados com CSS Grid. O tamanho máximo de 340 pixels mantém a interface compacta em telas maiores.
A calculadora aceita números negativos?
Ela calcula resultados negativos e permite subtração. Para começar diretamente com um número negativo, seria necessário adicionar uma regra específica para o sinal unário.
Por que o resultado é arredondado?
O método toFixed(10) reduz efeitos comuns da representação de números decimais em ponto flutuante. Depois, Number() remove zeros desnecessários no final.
Conclusão
Você criou uma calculadora HTML funcional com CSS e JavaScript, sem depender de bibliotecas e sem executar expressões com eval(). O projeto já inclui operações básicas, porcentagem, decimais, teclado, acessibilidade e tratamento de divisão por zero.
Depois de testar o código, explore outros projetos práticos de programação e continue aprimorando a interface e a lógica.




