Um guia claro e passo a passo para criar um pequeno app de tarefas: planeje recursos, crie as telas, adicione lógica, salve dados, teste e publique.

Quando eu digo “app” neste guia, quero dizer um pequeno web app: uma única página que você abre no navegador e que responde ao que você clica e digita. Sem instalações, sem contas e sem configurações pesadas — apenas um projeto simples que você pode executar localmente.
No final, você terá um app de tarefas que pode:
localStorage (para que fechar a aba não apague tudo)Não será perfeito nem “pronto para empresas”, e esse é o objetivo. Este é um projeto para iniciantes projetado para ensinar o básico sem empilhar muitas ferramentas.
Você vai construir o app passo a passo e aprender as peças principais de como apps front-end funcionam:
Mantenha simples. Você só precisa de:
Se você sabe criar uma pasta e editar alguns arquivos, está pronto.
Antes de escrever qualquer código, decida como será o “sucesso”. Este tutorial constrói um app pequeno com uma tarefa clara: ajudar você a acompanhar tarefas que precisa fazer.
Escreva uma frase-alvo para manter em mente enquanto constrói:
“Este app me permite adicionar tarefas a uma lista para não esquecê-las.”
Isso é tudo. Se surgir a tentação de adicionar calendários, lembretes, tags ou contas, deixe essas ideias para depois.
Faça duas listas rápidas:
Obrigatório (para este projeto):
Desejável (não obrigatório hoje): datas de vencimento, prioridades, categorias, pesquisa, arrastar-e-soltar, sincronização na nuvem.
Manter o “obrigatório” pequeno ajuda a terminar de fato.
Este app pode ser uma única página com:
Seja específico para não travar:
Com isso decidido, você está pronto para preparar os arquivos do projeto.
Antes de escrever código, vamos criar uma “casa” limpa para o app. Manter os arquivos organizados desde o início facilita os próximos passos.
Faça uma nova pasta no seu computador e chame algo como todo-app. Essa pasta conterá tudo do projeto.
Dentro dela, crie três arquivos:
index.html (a estrutura da página)styles.css (a aparência e layout)app.js (o comportamento e interatividade)Se seu sistema esconde extensões, confirme que você tem arquivos reais (um erro comum é acabar com index.html.txt).
Abra a pasta todo-app no seu editor (VS Code, Sublime Text etc.). Depois abra index.html no navegador.
Nesse ponto a página pode estar em branco — e tudo bem. Vamos adicionar conteúdo no próximo passo.
Quando você editar os arquivos, o navegador não atualiza automaticamente (a menos que use uma ferramenta que faça isso).
O ciclo básico é:
Se algo “não funciona”, atualizar a página é a primeira ação a tentar.
Você pode abrir o index.html diretamente, mas um servidor local evita problemas estranhos depois (especialmente ao salvar dados ou carregar arquivos).
Opções simples para iniciantes:
python -m http.server
Depois abra o endereço que ele informar (geralmente http://localhost:8000) no navegador.
Agora vamos criar um esqueleto limpo para o app. Esse HTML não fará nada interativo ainda (isso vem depois), mas fornece pontos claros para o JavaScript ler e escrever.
Incluiremos:
Nomes simples e legíveis ajudam. IDs/classes claros facilitam o JavaScript buscar elementos sem confusão.
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>To‑Do App</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="app" id="app">
<h1 class="app__title" id="appTitle">My To‑Do List</h1>
<form class="task-form" id="taskForm">
<label class="task-form__label" for="taskInput">New task</label>
<div =>
Add
Isso é tudo para a estrutura. Observe que usamos id="taskInput" e id="taskList" — esses serão os elementos que você mais manipulará no JavaScript.
Agora a página existe, mas provavelmente parece um documento simples. Um pouco de CSS torna o uso mais fácil: espaçamento claro, texto legível e botões que parecem clicáveis.
Uma caixa centralizada mantém o app focado e evita que o conteúdo se estique demais em telas largas.
/* Basic page setup */
body {
font-family: Arial, sans-serif;
background: #f6f7fb;
margin: 0;
padding: 24px;
}
/* Centered app container */
.container {
max-width: 520px;
margin: 0 auto;
background: #ffffff;
padding: 16px;
border-radius: 10px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
}
Cada tarefa deve parecer uma “linha” separada, com espaçamento confortável.
ul { list-style: none; padding: 0; margin: 16px 0 0; }
li {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border: 1px solid #e7e7ee;
border-radius: 8px;
margin-bottom: 10px;
}
.task-text { flex: 1; }
Quando uma tarefa estiver completa, ela deve mudar visualmente para ser reconhecida rapidamente.
.done .task-text {
text-decoration: line-through;
color: #777;
opacity: 0.85;
}
Mantenha os botões do mesmo tamanho e estilo para que pareçam parte de um único app.
button {
border: none;
border-radius: 8px;
padding: 8px 10px;
cursor: pointer;
}
button:hover { filter: brightness(0.95); }
Isso é suficiente para uma UI limpa e amigável — sem truques avançados. A seguir, vamos ligar o comportamento com JavaScript.
Agora que você tem o input, o botão e a lista na página, vamos fazê-los funcionar. O objetivo: quando alguém digitar uma tarefa e clicar Adicionar (ou apertar Enter), um novo item aparece na lista.
No seu arquivo JavaScript, primeiramente capture os elementos necessários e depois escute duas ações: o clique no botão e a tecla Enter dentro do input.
const taskInput = document.querySelector('#taskInput');
const addBtn = document.querySelector('#addBtn');
const taskList = document.querySelector('#taskList');
function addTask() {
const text = taskInput.value.trim();
// Block empty tasks (including ones that are just spaces)
if (text === '') return;
const li = document.createElement('li');
li.textContent = text;
taskList.appendChild(li);
// Clear the input and put the cursor back
taskInput.value = '';
taskInput.focus();
}
addBtn.addEventListener('click', addTask);
taskInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
addTask();
}
});
trim() para remover espaços extras no início/fim.li, define seu texto e adiciona à lista.Se nada acontecer, verifique se os IDs no HTML batem exatamente com os seletores do JavaScript (um erro comum de iniciante).
Agora que você pode adicionar tarefas, vamos torná-las acionáveis: você deve poder marcar como concluída e remover uma tarefa.
Em vez de usar strings simples, represente cada tarefa como um objeto. Assim cada tarefa tem identidade estável e espaço para marcar se está concluída:
text: o texto da tarefadone: true ou falseid: um número único para encontrar/excluir a tarefa corretaExemplo simples:
let tasks = [
{ id: 1, text: "Buy milk", done: false },
{ id: 2, text: "Email Sam", done: true }
];
Ao renderizar cada tarefa, inclua uma checkbox ou um botão “Concluído”, além de um botão “Excluir”.
Um padrão amigável para iniciantes é delegação de eventos: coloque um único listener de clique no contêiner da lista e verifique onde o clique aconteceu.
function toggleDone(id) {
tasks = tasks.map(t => t.id === id ? { ...t, done: !t.done } : t);
renderTasks();
}
function deleteTask(id) {
tasks = tasks.filter(t => t.id !== id);
renderTasks();
}
document.querySelector("#taskList").addEventListener("click", (e) => {
const id = Number(e.target.dataset.id);
if (e.target.matches(".toggle")) toggleDone(id);
if (e.target.matches(".delete")) deleteTask(id);
});
Na sua função renderTasks():
data-id="${task.id}" em cada botão..done).Agora sua lista tem um problema chato: se você atualizar a página (ou fechar a aba), tudo desaparece.
Isso acontece porque as tarefas só existem na memória do JavaScript. Ao recarregar, essa memória é zerada.
localStorage já faz parte do navegador. Pense nele como uma caixinha onde você guarda texto sob um nome (uma “chave”). É perfeito para projetos de iniciante porque não precisa de servidor nem conta.
Vamos salvar a lista inteira de tarefas como texto JSON e carregá-la quando a página abrir.
Sempre que você adicionar, marcar ou excluir uma tarefa, chame saveTasks().
const STORAGE_KEY = "todo.tasks";
function saveTasks(tasks) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
Onde seu app atualiza o array tasks, faça isso em seguida:
saveTasks(tasks);
renderTasks(tasks);
Ao carregar a página, leia o valor salvo. Se não houver nada salvo, use uma lista vazia.
function loadTasks() {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
}
let tasks = loadTasks();
renderTasks(tasks);
Pronto: seu app agora lembra das tarefas após um refresh.
Dica: localStorage só guarda strings, então JSON.stringify() transforma seu array em texto, e JSON.parse() transforma de volta em um array real ao carregar.
Testar soa chato, mas é a forma mais rápida de transformar seu app de “funciona no meu computador” para “funciona sempre”. Faça um rápido teste após cada pequena mudança.
Execute o fluxo principal nesta ordem:
Se alguma etapa falhar, conserte antes de adicionar novas funcionalidades. Apps pequenos ficam bagunçados quando você empilha problemas.
Casos extremos são entradas que você não planejou, mas pessoas reais farão:
Uma correção comum é bloquear tarefas em branco:
const text = input.value.trim();
addButton.disabled = text === "";
(Rode isso no evento input e novamente antes de adicionar.)
Quando cliques não fazem nada, geralmente é uma destas:
id ou classe difere entre HTML e JS).app.js não encontrado).Quando algo parece aleatório, registre valores:
console.log("Adding:", text);
console.log("Current tasks:", tasks);
Verifique o Console do navegador por erros (texto em vermelho). Depois de corrigir, remova os logs para não poluir o projeto.
Um app de tarefas só está “pronto” quando é confortável para pessoas reais — em celulares, com teclado e com ferramentas assistivas como leitores de tela.
Em telas pequenas, botões minúsculos são frustrantes. Dê espaço aos elementos clicáveis:
Aumentar padding, font-size e gap no CSS normalmente traz o maior benefício.
Leitores de tela precisam de nomes claros para controles.
label real (melhor opção). Se não quiser exibir, esconda visualmente com CSS, mas mantenha no HTML.aria-label="Delete task" para que o leitor de tela saiba do que se trata.Isso ajuda pessoas a entenderem cada controle sem adivinhar.
Assegure que o app funcione sem mouse:
<form> faz o Enter funcionar naturalmente).Use tamanho de fonte legível (16px como base) e contraste forte (texto escuro em fundo claro, ou o oposto). Evite depender apenas de cor para indicar “concluído” — adicione um estilo claro como riscar o texto.
Agora que tudo funciona, dedique 10–15 minutos para arrumar. Isso facilita correções futuras e ajuda você a entender o projeto quando voltar.
Mantenha pequeno e previsível:
/index.html — estrutura da página (input, botão, lista)/styles.css — aparência (espaçamentos, fontes, estilo “concluído”)/app.js — comportamento (adicionar, marcar, excluir, salvar/carregar)/README.md — notas rápidas para o “você do futuro”Se preferir subpastas, tudo bem:
/css/styles.css/js/app.jsApenas garanta que os caminhos em <link> e <script> batam.
Pequenas melhorias:
taskInput, taskList, saveTasks()Por exemplo, fica fácil ler:
renderTasks(tasks)addTask(text)toggleTask(id)deleteTask(id)Seu README.md pode ser simples:
index.html no navegador)Ao menos compacte a pasta após concluir uma etapa importante (por exemplo: “localStorage funcionando”). Se quiser histórico, use Git — mas não é obrigatório. Mesmo um backup pode salvar de exclusões acidentais.
Publicar significa colocar os arquivos (HTML, CSS, JS) em algum lugar público para que outras pessoas possam abrir um link e usar. Como este app é um “site estático” (roda no navegador sem servidor), você pode hospedá-lo gratuitamente em vários serviços.
Passos gerais:
/ root).Se usar arquivos separados, confira nomes exatos (ex.: styles.css vs style.css).
Para o caminho mais fácil:
localStorage ok).Quando passar, mande o link para um amigo e peça um teste — olhos frescos acham problemas rápido.
Você construiu um app funcional. Se quiser continuar aprendendo sem um salto enorme, essas melhorias adicionam valor e ensinam padrões úteis.
Adicione um botão “Editar” ao lado de cada tarefa. Ao clicar, troque o rótulo por um pequeno input (pré-preenchido), mais “Salvar” e “Cancelar”.
Dica: mantenha os dados como um array de objetos (com id e text). Editar então vira: encontrar a tarefa por id, atualizar text, re-renderizar e salvar.
Adicione três botões no topo: Todas, Ativas, Concluídas.
Armazene o filtro atual em uma variável, por exemplo currentFilter = 'all'. Ao renderizar, mostre:
Mantenha leve:
YYYY-MM-DD) e mostre ao lado da tarefaMesmo um campo extra ensina como atualizar modelo de dados e UI juntos.
Quando estiver pronto para avançar, a ideia é: em vez de salvar no localStorage, envie as tarefas para uma API usando fetch(). O servidor guarda em um banco de dados e você pode sincronizar entre dispositivos.
Se quiser experimentar esse salto sem refazer tudo, plataformas de prototipagem podem ajudar a gerar backends e exemplos para aprender (descrever endpoints, tabelas e mudanças na UI, iterar e exportar código).
Tente fazer um app de notas (com pesquisa) ou um rastreado de hábitos (checagens diárias). Reaproveitam as mesmas habilidades: renderizar listas, editar, salvar e design simples de UI.