Jasny przewodnik krok po kroku: zaplanuj funkcje, stwórz ekrany, dodaj logikę, zapisuj dane, testuj i opublikuj małą aplikację to‑do.

Gdy w tym przewodniku mówię „aplikacja”, mam na myśli małą aplikację webową: jedną stronę, którą otwierasz w przeglądarce, reagującą na kliknięcia i wpisywanie. Bez instalacji, kont i ciężkiej konfiguracji — po prostu prosty projekt, który możesz uruchomić lokalnie.
Na końcu będziesz mieć aplikację to‑do, która potrafi:
localStorage (żeby zamknięcie karty nie kasowało wszystkiego)Nie będzie to rozwiązanie idealne ani „enterprise‑grade” — i o to chodzi. To projekt dla początkujących, zaprojektowany tak, żeby uczyć podstaw bez wrzucania na raz wielu narzędzi.
Zbudujesz aplikację krok po kroku i poznasz podstawowe elementy działania aplikacji frontendowych:
Prosto. Potrzebujesz tylko:
Jeśli potrafisz utworzyć folder i edytować kilka plików, jesteś gotowy.
Zanim napiszesz kod, zdecyduj, jak wygląd sukcesu. W tym samouczku budujemy jedną małą aplikację o jasnym zadaniu: pomaga śledzić zadania, które chcesz wykonać.
Napisz jedno zdanie‑cel, które będziesz mieć przed sobą podczas budowy:
„Ta aplikacja pozwala mi dodawać zadania do listy, żeby ich nie zapomnieć.”
To wszystko. Jeśli poczujesz pokusę dodawać kalendarze, przypomnienia, tagi czy konta — odłóż to na później.
Zrób dwie krótkie listy:
Konieczne (na ten projekt):
localStorage później)Miłe do mieć (nie dziś): terminy, priorytety, kategorie, wyszukiwanie, drag-and-drop, synchronizacja w chmurze.
Utrzymanie listy „koniecznych” małych pomaga naprawdę skończyć projekt.
Aplikacja może mieć jedną stronę z:
Bądź konkretny, żeby nie utknąć:
Gdy to ustalisz, możesz przygotować pliki projektu.
Zanim napiszemy kod, stwórzmy porządek. Trzymanie plików od początku w schludzie ułatwia kolejne kroki.
Stwórz nowy folder na komputerze i nazwij go np. todo-app. W tym folderze będą wszystkie pliki projektu.
W środku utwórz trzy pliki:
index.html (struktura strony)styles.css (wygląd i układ)app.js (zachowanie i interaktywność)Jeśli system ukrywa rozszerzenia plików, upewnij się, że tworzysz prawdziwe pliki — częstym błędem początkujących jest index.html.txt.
Otwórz folder todo-app w edytorze (VS Code, Sublime Text itp.). Następnie otwórz index.html w przeglądarce.
Na tym etapie strona może być pusta — i to w porządku. Dodamy zawartość w następnym kroku.
Po edycji plików przeglądarka nie odświeży się automatycznie (chyba że używasz narzędzia, które to robi).
Podstawowy cykl to:
Jeśli coś „nie działa”, odświeżenie to pierwszy krok.
Możesz otwierać index.html dwuklikiem, ale lokalny serwer zapobiega dziwnym problemom (szczególnie gdy zaczniesz zapisywać dane lub ładować pliki).
Proste opcje dla początkujących:
python -m http.server
Następnie otwórz adres, który zostanie wyświetlony (często http://localhost:8000) w przeglądarce.
Teraz stworzymy szkielet aplikacji. Ten HTML nie sprawi, że aplikacja będzie interaktywna (to zadanie JavaScriptu), ale da mu jasne miejsca do odczytu i zapisu.
Powinien zawierać:
Proste, czytelne nazwy i dobre id/klasy ułatwią później pracę JavaScriptowi.
index.html\u003c!doctype html\u003e
\u003chtml lang=\"en\"\u003e
\u003chead\u003e
\u003cmeta charset=\"utf-8\" /\u003e
\u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e
\u003ctitle\u003eTo‑Do App\u003c/title\u003e
\u003clink rel=\"stylesheet\" href=\"styles.css\" /\u003e
\u003c/head\u003e
\u003cbody\u003e
\u003cmain class=\"app\" id=\"app\"\u003e
\u003ch1 class=\"app__title\" id=\"appTitle\"\u003eMy To‑Do List\u003c/h1\u003e
\u003cform class=\"task-form\" id=\"taskForm\"\u003e
\u003clabel class=\"task-form__label\" for=\"taskInput\"\u003eNew task\u003c/label\u003e
\u003cdiv class=\"task-form__row\"\u003e
\u003cinput
id=\"taskInput\"
class=\"task-form__input\"
type=\"text\"
placeholder=\"e.g., Buy milk\"
autocomplete=\"off\"
/\u003e
\u003cbutton id=\"addButton\" class=\"task-form__button\" type=\"submit\"\u003e
Add
\u003c/button\u003e
\u003c/div\u003e
\u003c/form\u003e
\u003cul class=\"task-list\" id=\"taskList\" aria-label=\"Task list\"\u003e\u003c/ul\u003e
\u003c/main\u003e
\u003cscript src=\"app.js\"\u003e\u003c/script\u003e
\u003c/body\u003e
\u003c/html\u003e
To wszystko jeśli chodzi o strukturę. Zauważ, że użyliśmy id="taskInput" i id="taskList" — to te elementy, z którymi najczęściej będzie rozmawiał JavaScript.
Strona działa, ale prawdopodobnie wygląda jak zwykły dokument. Trochę CSS sprawi, że będzie czytelniej: odstępy, rozmiar tekstu i przyciski, które zachęcają do kliknięcia.
Wyśrodkowane pole skupia uwagę i zapobiega rozciąganiu treści na szerokich ekranach.
/* 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);
}
Każde zadanie powinno wyglądać jak osobny wiersz, z odpowiednimi odstępami.
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; }
Gdy zadanie jest ukończone, powinno to być od razu widoczne.
.done .task-text {
text-decoration: line-through;
color: #777;
opacity: 0.85;
}
Trzymaj przyciski w jednym stylu, by wyglądały jak elementy tej samej aplikacji.
button {
border: none;
border-radius: 8px;
padding: 8px 10px;
cursor: pointer;
}
button:hover { filter: brightness(0.95); }
To wystarczy, żeby UI był schludny i przyjazny — bez zaawansowanych trików. Teraz podłączymy zachowanie przez JavaScript.
Mając pole, przycisk i listę, sprawimy, by działały. Cel: gdy ktoś wpisze zadanie i kliknie Add (lub naciśnie Enter), nowy element pojawi się w liście.
W pliku JavaScript najpierw pobierz elementy, a potem nasłuchuj dwóch akcji: kliknięcia przycisku i wciśnięcia Enter w polu.
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(), by usunąć zbędne spacje na początku/końcu.\u003cli\u003e, ustawia jego tekst i dodaje do listy.Jeśli nic się nie dzieje, sprawdź, czy id w HTML dokładnie pasuje do selektorów w JavaScripcie — to częsty błąd początkujących.
Gdy dodawanie działa, dajmy zadaniom akcje: oznaczanie jako zrobione oraz usuwanie.
Zamiast trzymać zadania jako zwykłe ciągi znaków, użyj obiektów. Dzięki temu każde zadanie ma stabilną tożsamość i miejsce na status „zrobione”:
text: treść zadaniadone: true lub falseid: unikalny numer, żeby odnaleźć/usunąć właściwe zadaniePrzykład:
let tasks = [
{ id: 1, text: "Buy milk", done: false },
{ id: 2, text: "Email Sam", done: true }
];
W czasie renderowania każdego zadania dodaj checkbox albo przycisk „Done” oraz przycisk „Delete”.
Listener (nasłuchiwacz zdarzeń) to sposób reagowania na kliknięcia — przypinasz go do przycisku (lub całej listy), a on uruchamia kod po kliknięciu.
Wzorzec przyjazny początkującym to delegacja zdarzeń: dodaj jeden nasłuchiwacz na pojemnik listy i sprawdzaj, co zostało kliknięte.
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);
});
W renderTasks():
data-id="${task.id}" do każdego przycisku..done).Teraz twoja lista ma problem: po odświeżeniu strony wszystko znika.
Dzieje się tak, bo zadania istnieją tylko w pamięci JavaScript. Po przeładowaniu strony pamięć się zeruje.
localStorage jest wbudowany w przeglądarkę. Wyobraź sobie, że to małe pudełko, w którym możesz zapisać tekst pod nazwą (kluczem). Świetne do projektów dla początkujących, bo nie trzeba serwera ani kont.
Zapiszemy całą listę zadań jako tekst JSON, a potem załadujemy przy otwieraniu strony.
Za każdym razem, gdy dodasz zadanie, oznaczysz jako zrobione lub usuniesz, wywołaj saveTasks().
const STORAGE_KEY = "todo.tasks";
function saveTasks(tasks) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
Gdziekolwiek aktualizujesz tablicę tasks, zrób to natychmiast po zmianie:
saveTasks(tasks);
renderTasks(tasks);
Kiedy strona się ładuje, odczytaj zapisane dane. Jeśli nic nie ma, użyj pustej listy.
function loadTasks() {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : [];
}
let tasks = loadTasks();
renderTasks(tasks);
I to tyle: aplikacja teraz pamięta zadania po odświeżeniu.
Wskazówka: localStorage przechowuje tylko tekst, więc JSON.stringify() zamienia tablicę na tekst, a JSON.parse() przywraca ją do tablicy.
Testowanie brzmi nudno, ale to najszybszy sposób, żeby twoja aplikacja przestała być „działa na moim komputerze” i zaczęła „działać zawsze”. Zrób szybki test po każdej drobnej zmianie.
Przejdź przez główny przepływ w tej kolejności:
Jeśli któryś krok nie działa, napraw go, zanim dodasz nowe funkcje. Małe aplikacje robią się chaotyczne, gdy nakładasz problemy na problemy.
To przypadki, których nie przewidziałeś, a które użytkownicy i tak zrobią:
Częsta poprawka to blokowanie pustych zadań:
const text = input.value.trim();
addButton.disabled = text === "";
(Możesz uruchamiać to przy każdym zdarzeniu input, i jeszcze raz tuż przed dodaniem.)
Gdy kliknięcia nic nie robią, zwykle to:
id lub klasa między HTML a JS).app.js nie znaleziony).Gdy coś zachowuje się losowo, loguj:
console.log("Adding:", text);
console.log("Current tasks:", tasks);
Sprawdź Konsolę przeglądarki w poszukiwaniu błędów (czerwony tekst). Gdy naprawisz problem, usuń zbędne logi, żeby nie zaśmiecały projektu.
Aplikacja jest „gotowa”, gdy jest wygodna dla prawdziwych ludzi — na telefonach, z klawiaturą i z czytnikami ekranu.
Na małych ekranach małe przyciski są frustrujące. Daj elementom dotykowym wystarczająco dużo miejsca:
W CSS powiększenie padding, font-size i gap często wystarcza.
Czytniki ekranu potrzebują jasnych nazw kontrolkom.
\u003clabel\u003e (najlepsza opcja). Jeśli nie chcesz go pokazywać, możesz go ukryć wizualnie w CSS, ale pozostaw w HTML.aria-label="Delete task", żeby czytnik wiedział, co robi.To pomaga osobom korzystającym z technologii wspomagających zrozumieć interfejs.
Upewnij się, że cały interfejs działa bez myszy:
\u003cform\u003e, by Enter działał naturalnie).Używaj czytelnego rozmiaru (16px to dobry punkt wyjścia) i silnego kontrastu (ciemny tekst na jasnym tle lub odwrotnie). Unikaj używania tylko koloru do pokazania „zrobione” — dodaj też wyraźny styl jak przekreślenie.
Gdy wszystko działa, poświęć 10–15 minut na porządki. Ułatwi to przyszłe poprawki i pomoże zrozumieć projekt po powrocie.
Ma być mało i przewidywalnie:
/index.html — struktura strony (input, przycisk, lista)/styles.css — wygląd (odstępy, fonty, styl „zrobione”)/app.js — logika (dodaj, oznacz, usuń, zapisz/ładuj)/README.md — notatka dla „przyszłego siebie”Jeśli wolisz podfoldery, możesz też użyć:
/css/styles.css/js/app.jsPamiętaj, żeby \u003clink\u003e i \u003cscript\u003e miały dopasowane ścieżki.
Kilka szybkich wskazówek:
taskInput, taskList, saveTasks()Na przykład łatwiej czyta się:
renderTasks(tasks)addTask(text)toggleTask(id)deleteTask(id)README.md może być prosty:
index.html w przeglądarce)Przynajmniej spakuj folder po ukończeniu ważnego kroku (np. „localStorage działa”). Jeśli chcesz historię zmian, Git jest świetny — ale nie jest konieczny. Nawet jedna kopia zapasowa może uchronić przed przypadkowym usunięciem.
Publikacja to udostępnienie plików (HTML, CSS, JavaScript) publicznie, żeby inni mogli otworzyć link i z nich skorzystać. Ponieważ to aplikacja statyczna (działa w przeglądarce, bez serwera), możesz ją hostować za darmo.
Kroki w skrócie:
Jeśli masz osobne pliki, sprawdź nazwy plików i linki (np. styles.css vs style.css).
Dla najszybszego „wrzuć i idź”:
localStorage działa)?Gdy wszystko działa, wyślij link znajomemu i poproś o test — świeże spojrzenie szybko wykrywa błędy.
Zbudowałeś działającą aplikację to‑do. Jeśli chcesz dalej się uczyć bez skakania do dużych projektów, te dodatki wniosą wartość i nauczą przydatnych wzorców.
Dodaj przycisk „Edytuj” przy każdym zadaniu. Po kliknięciu zamień etykietę na małe pole input (wypełnione treścią) oraz przyciski „Zapisz” i „Anuluj”.
Wskazówka: trzymaj dane jako tablica obiektów (z id i text). Edycja to: znajdź zadanie po id, zaktualizuj text, renderuj i zapisz.
Dodaj trzy przyciski na górze: All, Active, Done.
Przechowuj obecny filtr w zmiennej, np. currentFilter = 'all'. Podczas renderowania pokazuj:
Zachowaj lekkość:
YYYY-MM-DD) i pokaż go obok zadaniaNawet jedno dodatkowe pole uczy, jak zaktualizować model danych i UI razem.
Gdy będziesz gotów, główna zmiana to: zamiast zapisywać do localStorage, wysyłasz zadania do API (serwera) przez fetch(). Serwer przechowuje je w bazie, więc zsynchronizują się między urządzeniami.
Jeśli chcesz spróbować tego skoku bez przepisywania wszystkiego, platforma typu Koder.ai może pomóc: opisz funkcje w chatcie (endpointy, tabele bazy, zmiany UI), iteruj w trybie planowania i eksportuj kod źródłowy wygenerowanego projektu React/Go/PostgreSQL, gdy będziesz gotów.
Spróbuj zbudować aplikację do notatek (z wyszukiwaniem) albo tracker nawyków (codzienne odhaczanie). Wykorzystują te same umiejętności: renderowanie listy, edycję, zapisywanie i prosty design UI.