Capítulo 13
Projeto: checkout do café
5 min de leitura 869 palavras 2 gravuras
A Casa da Restinga precisa de uma tela de pedido: itens à esquerda, nome e pagamento à direita, total embaixo. Você escreve o HTML e o CSS. Nada é cobrado. Nada sai do computador.
O exemplo está em exemplos/13-checkout/. Natal, cafeteria fictícia. Este volume tem 23 capítulos.
Não digite nome real, nem número de cartão verdadeiro. Use Iara Costa e 0000 0000 0000 0000.
Trabalhe na cópia inicio da etapa. Leia e digite o código; mude uma coisa. Antes de recarregar, anote sua previsão. Depois confira com a referência completa. Se nada mudar, confira o arquivo salvo e o caminho aberto. Blocos adicionais do capítulo podem ser digitados no console; o exemplo completo mostra a aplicação daquela etapa.
O que você vai aprender
- Montar a página de checkout com
header,maine seções. - Ligar cada
labelao campo (foreid). - Arredondar com
border-radiuse levantar combox-shadow. - Grid na página, Flex (ou Grid simples) na linha do item.
- Impedir o envio: botão
type="button".
O que vamos montar
Três faixas: pedido, dados, total. A marca no topo é Casa da Restinga, não outra cafeteria.
Itens fictícios:
- 1× Café coado da Restinga — R$ 8,00
- 1× Cappuccino de coco — R$ 12,00 (leite de coco)
- 1× Pão de queijo de mandioca — R$ 7,00
Subtotal R$ 27,00. “Taxa” de demonstração R$ 1,35. Total R$ 28,35. Os números são texto. Não há caixa registradora.
Há muitos jeitos certos. Grid + Flex cabem. Não force a página inteira num Grid de vinte células.
Mude uma coisa. Troque o cappuccino por Café gelado da duna no HTML. Preveja só o nome mudar. Recarregue e confira.
Erro comum: copiar preços em dólar e o nome de outra marca. Este pedido é da Restinga, em real.
HTML do pedido
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Checkout — Casa da Restinga</title>
<link rel="stylesheet" href="estilos.css">
</head>
<body>
<header>
<p class="marca">Casa da Restinga</p>
</header>
<main class="pagina">
<section class="pedido" aria-labelledby="tit-pedido">
<h1 id="tit-pedido">Itens</h1>
<ul class="itens">
<li class="item">
<span>1×</span>
<span>Café coado da Restinga</span>
<span>R$ 8,00</span>
</li>
</ul>
</section>
</main>
</body>
</html>
Complete os três itens no exemplo. Lista: ul / li. Preço visível no texto, não só na cor.
Mude uma coisa. Troque o h1 para Seu pedido na Restinga. Preveja o título da seção mudar, a aba continuar Checkout — Casa da Restinga. Recarregue e confira.
Erro comum: div no lugar de section sem título. A região some para o leitor de tela.
Formulário que não envia
Cada campo tem label com for igual ao id. O botão é type="button": clicar não navega, não abre e-mail, não chama PHP.
<section class="dados" aria-labelledby="tit-dados">
<h2 id="tit-dados">Nome e pagamento</h2>
<form>
<p class="campo">
<label for="nome">Nome no pedido</label>
<input id="nome" name="nome" type="text" autocomplete="off">
</p>
<p class="campo">
<label for="cartao">Número fictício do cartão</label>
<input id="cartao" name="cartao" type="text" inputmode="numeric" autocomplete="off">
</p>
<p class="campo">
<label for="cvv">CVV fictício</label>
<input id="cvv" name="cvv" type="text" inputmode="numeric" autocomplete="off">
</p>
<button type="button">Confirmar pedido fictício</button>
</form>
</section>
Clique no texto “Nome no pedido”: o cursor vai ao campo. Isso só funciona se for e id forem iguais, inclusive minúsculas.
O rótulo pode sentar na borda do campo com position: relative no grupo e position: absolute no label. O for continua obrigatório. placeholder não substitui o nome.
Mude uma coisa. Troque type="button" por type="submit". Clique. Preveja a página recarregar com lixo na URL. Recarregue. Volte para button.
Erro comum: action apontando para um site ou para mailto:. O exercício deixa de ser local.

Grid na página, linha do item
.pagina {
display: grid;
gap: 1.5rem;
padding: 1.5rem;
grid-template-areas:
"pedido dados"
"total total";
grid-template-columns: 1fr 1fr;
}
.pedido { grid-area: pedido; }
.dados { grid-area: dados; }
.total { grid-area: total; }
.item {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 0.5rem;
}
.marca {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0.75rem;
background: #0d5c63;
color: #f4efe6;
}
@media (max-width: 40rem) {
.pagina {
grid-template-areas:
"pedido"
"dados"
"total";
grid-template-columns: 1fr;
}
}
Cabeçalho em Flex (componente). Página em Grid. Item em Flex.
Mude uma coisa. Encolha a janela abaixo de 40rem. Preveja uma coluna: pedido, dados, total. Recarregue se precisar e confira.
Erro comum: grid-template-columns: 1fr 1fr sem media query. No telefone as duas colunas esmagam os campos.
border-radius e box-shadow
Ainda não tínhamos falado disso. border-radius arredonda. box-shadow desenha uma sombra.
.caixa {
background: #eee1d5;
border-radius: 8px;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.2);
padding: 1rem;
}
input {
border: 1px solid #0d5c63;
border-radius: 4px;
padding: 0.5rem;
}
input:focus-visible,
button:focus-visible {
outline: 3px solid #0d5c63;
outline-offset: 2px;
}
Quatro valores em border-radius arredondam cada canto. Um valor só arredonda todos. A sombra é deslocamento-x deslocamento-y desfoque cor.
Não precisa copiar o desenho ao milímetro. Chegue perto da cara da casa.
Mude uma coisa. Suba border-radius da caixa para 24px. Preveja um cartão bem redondo. Recarregue e confira.
Erro comum: box-shadow tão escura que parece um segundo fundo. Sombra é dica de elevação, não um muro.
Fonte local e conferência
Prefira fonte do sistema. Google Fonts é opcional e pede rede.
body {
margin: 0;
font-family: system-ui, "Segoe UI", sans-serif;
background: #efefef;
}
system-ui usa a fonte da máquina. Sem link para fonts.google.com.
Confira: Tab em todos os campos; rótulo clicável; botão não envia; zoom 200%; tela estreita em uma coluna. Servidor só em 127.0.0.1. Publicação não é pedida.
Mude uma coisa. Não acrescente Google Fonts. Preveja o texto na fonte do sistema. Recarregue e confira.
Erro comum: font-family só com uma fonte da web, sem genérica. Sem internet, o checkout cai no Times padrão sem plano B.

Atividades
- Por que o botão é
type="button"e nãotype="submit"? - O que acontece se
for="nome"eid="Nome"não baterem? - Grid ou Flex: a página inteira do checkout? A linha “1× Café coado”?
border-radiusebox-shadowmudam o HTML ou só o CSS?
Respostas comentadas
submitenvia o formulário (recarrega ou vai para umaction).buttonsó existe na tela. Nada é transmitido.- Clicar no rótulo não foca o campo. Maiúscula conta. Leitor de tela também perde a ligação.
- Página: Grid (pedido, dados, total). Linha do item: Flex (quantidade, nome, preço).
- Só o CSS. O HTML dos itens e dos campos permanece o mesmo.
Referências
O arquivo completo
Referência do exemplo 13-checkout, reproduzida aqui para que você possa conferir sem sair da página.
checkout.js
document.querySelector("form").addEventListener("submit", (evento) => {
evento.preventDefault();
document.getElementById("ok").textContent = "Pedido simulado. Nada foi enviado.";
});