Tworzenie kalendarza w formularzu HTML

Chcesz dodać do formularza kalendarz, który będzie ładny, użyteczny i dostępny? Świetnie — trafiłeś we właściwe miejsce. W tym artykule w prosty, konwersacyjny sposób pokażę Ci kilka podejść: od najłatwiejszego (wbudowany input[type="date"]) po lekki, własny kalendarz w JavaScript. Dodatkowo dostaniesz praktyczny kod do wklejenia i podrasowania pod swoje potrzeby. Zaczynamy!

Dlaczego w ogóle warto mieć kalendarz w formularzu?

Kalendarz w formularzu to nie tylko ładny gadżet. To przede wszystkim:

  • szybsze i bardziej pewne wprowadzanie dat przez użytkownika,
  • mniejsza liczba błędów formatu (np. 2025-10-08 zamiast 08.10.25),
  • lepsze UX na urządzeniach mobilnych,
  • możliwość zaimplementowania ograniczeń (np. daty minimalnej/maksymalnej),
  • łatwiejsza walidacja po stronie klienta i serwera.

Jeśli chcesz, by użytkownik wybrał datę np. terminu wizyty, datę urodzenia czy datę rezerwacji — kalendarz znacząco podnosi konwersję i zmniejsza frustrację.

Szybkie i proste rozwiązanie: input type="date"

Jeśli zależy Ci na szybkości, kompatybilności i minimalnej ilości kodu — użyj wbudowanego pola daty:

<form>
  <label for="visit-date">Wybierz datę wizyty:</label>
  <input id="visit-date" name="visitDate" type="date" min="2025-01-01" max="2030-12-31" required>
  <button type="submit">Wyślij</button>
</form>

Zalety:

  • Natychmiast działa na przeglądarkach, które wspierają HTML5.
  • Na telefonach pokazuje natywny picker (bardzo wygodne).
  • Obsługuje atrybuty min, max, step i walidację HTML.

Wady:

  • Wygląd i funkcje zależą od przeglądarki i systemu — nie masz pełnej kontroli nad UI.
  • Nie wszystkie przeglądarki traktują type="date" tak samo (np. starsze desktopowe).

Jeżeli to wystarczy — super. Jeśli chcesz pełnej kontroli nad wyglądem i dodatkowymi funkcjami (zakresy godzinowe, wielokrotne wybory, blokowanie dni tygodnia), czytaj dalej.

Kiedy warto zrobić własny kalendarz?

Zastanów się nad własnym rozwiązaniem, gdy chcesz:

  • w pełni dopasować wygląd do designu strony,
  • dodać logikę blokowania dni (np. weekendy, święta),
  • umożliwić wybór zakresu dat (od — do),
  • zintegrować kalendarz z innymi komponentami (godziny, serwisy zewnętrzne),
  • obsłużyć niestandardowe formaty dat.

Poniżej znajdziesz przykładowy, lekki kalendarz napisany w HTML/CSS/JS. Ma podstawowe funkcje: pokazuje miesiąc, umożliwia przechodzenie między miesiącami, zaznaczanie daty i wypełnianie pola tekstowego w czytelnym formacie.

Przykład: lekki, własny kalendarz w JavaScript

Skopiuj i wklej poniższy kod do pliku index.html — to kompletny przykład działający bez zewnętrznych bibliotek.

<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Formularz z kalendarzem</title>
<style>
  body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; padding: 20px; }
  .field { margin-bottom: 12px; }
  .calendar-wrapper { position: relative; display: inline-block; }
  .calendar { position: absolute; top: 44px; left: 0; background: white; border: 1px solid #ddd; box-shadow: 0 6px 18px rgba(0,0,0,0.08); padding: 8px; z-index: 100; width: 260px; }
  .cal-head { display:flex; justify-content:space-between; align-items:center; margin-bottom: 6px; }
  .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
  .cal-cell { padding: 6px; text-align:center; border-radius: 4px; cursor:pointer; user-select:none; }
  .cal-cell:hover { background:#f0f0f0; }
  .cal-cell.disabled { color:#bbb; pointer-events:none; background:transparent; }
  .cal-cell.selected { background:#0b79ff; color:white; }
  .weekday { font-weight:600; color:#666; font-size: 13px; }
  button.icon { background:none;border:none;cursor:pointer;padding:6px;font-size:16px; }
</style>
</head>
<body>
  <h1>Rezerwacja — wybierz datę</h1>
  <form id="bookingForm">
    <div class="field">
      <label for="dateInput">Data:</label>
      <div class="calendar-wrapper">
        <input id="dateInput" name="date" type="text" placeholder="Kliknij, aby wybrać datę" readonly aria-haspopup="dialog" aria-expanded="false" />
        <div id="calendar" class="calendar" hidden role="dialog" aria-label="Wybór daty">
          <div class="cal-head">
            <button type="button" id="prev" class="icon" aria-label="Poprzedni miesiąc">&lt;</button>
            <div id="monthLabel"></div>
            <button type="button" id="next" class="icon" aria-label="Następny miesiąc">&gt;</button>
          </div>
          <div class="cal-grid" id="weekdays"></div>
          <div class="cal-grid" id="days"></div>
        </div>
      </div>
    </div>

    <button type="submit">Zarezerwuj</button>
  </form>

<script>
(function(){
  // Konfiguracja: min i max daty
  const minDate = new Date(2025, 0, 1); // 1 stycznia 2025
  const maxDate = new Date(2030, 11, 31); // 31 grudnia 2030

  const input = document.getElementById('dateInput');
  const calendar = document.getElementById('calendar');
  const monthLabel = document.getElementById('monthLabel');
  const weekdaysEl = document.getElementById('weekdays');
  const daysEl = document.getElementById('days');
  const prevBtn = document.getElementById('prev');
  const nextBtn = document.getElementById('next');

  // Aktualny widoczny miesiąc
  let viewDate = new Date();
  viewDate.setDate(1);

  // Funkcja formatująca datę (YYYY-MM-DD)
  function formatDate(d){
    const y = d.getFullYear();
    const m = String(d.getMonth()+1).padStart(2,'0');
    const day = String(d.getDate()).padStart(2,'0');
    return `${y}-${m}-${day}`;
  }

  // Dni tygodnia (polskie skróty)
  const weekdays = ['Pn','Wt','Śr','Cz','Pt','Sb','Nd'];
  weekdays.forEach(w => {
    const el = document.createElement('div');
    el.className = 'weekday';
    el.textContent = w;
    weekdaysEl.appendChild(el);
  });

  function render(){
    // Nagłówek miesiąca
    const monthName = viewDate.toLocaleString('pl', { month: 'long', year: 'numeric' });
    monthLabel.textContent = monthName;

    // Kasujemy poprzednie dni
    daysEl.innerHTML = '';

    const year = viewDate.getFullYear();
    const month = viewDate.getMonth();

    // Dzień tygodnia pierwszego dnia (0=Sunday in JS) -> chcemy Monday-first
    const firstDay = new Date(year, month, 1);
    let startIndex = firstDay.getDay(); // 0 (niedziela) ... 6 (sobota)
    // Przesunięcie: chcemy, żeby poniedziałek był pierwszy (index 0)
    startIndex = (startIndex + 6) % 7;

    // Ile dni w miesiącu?
    const daysInMonth = new Date(year, month+1, 0).getDate();

    // Dodaj puste komórki przed pierwszym dniem
    for(let i=0;i<startIndex;i++){
      const empty = document.createElement('div');
      empty.className = 'cal-cell disabled';
      daysEl.appendChild(empty);
    }

    for(let d=1; d<=daysInMonth; d++){
      const cell = document.createElement('div');
      const thisDate = new Date(year, month, d);
      cell.className = 'cal-cell';
      cell.tabIndex = 0;
      cell.textContent = d;

      // Sprawdź ograniczenia min/max
      if(thisDate < minDate || thisDate > maxDate){
        cell.classList.add('disabled');
      } else {
        cell.addEventListener('click', () => {
          input.value = formatDate(thisDate);
          closeCalendar();
        });
        // keyboard accessibility
        cell.addEventListener('keydown', (e) => {
          if(e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            input.value = formatDate(thisDate);
            closeCalendar();
          }
        });
      }

      // Zaznacz jeśli to aktualnie wybrana data
      if(input.value && input.value === formatDate(thisDate)) {
        cell.classList.add('selected');
      }

      daysEl.appendChild(cell);
    }
  }

  function openCalendar(){
    calendar.hidden = false;
    input.setAttribute('aria-expanded', 'true');
    render();
  }
  function closeCalendar(){
    calendar.hidden = true;
    input.setAttribute('aria-expanded', 'false');
  }

  input.addEventListener('click', (e) => {
    if(calendar.hidden) openCalendar();
    else closeCalendar();
  });

  // Kliknięcie poza kalendarzem zamyka go
  document.addEventListener('click', (e) => {
    if(!calendar.contains(e.target) && e.target !== input){
      closeCalendar();
    }
  });

  prevBtn.addEventListener('click', () => {
    viewDate.setMonth(viewDate.getMonth() - 1);
    render();
  });
  nextBtn.addEventListener('click', () => {
    viewDate.setMonth(viewDate.getMonth() + 1);
    render();
  });

  // Zamknij na Esc
  document.addEventListener('keydown', (e) => {
    if(e.key === 'Escape') closeCalendar();
  });

  // Przy pierwszym załadowaniu ustaw widok na miesiąc zawierający minDate jeśli current poza zakresem
  (function initView() {
    const today = new Date();
    if(today < minDate) viewDate = new Date(minDate.getFullYear(), minDate.getMonth(), 1);
    else if(today > maxDate) viewDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), 1);
    else viewDate = new Date(today.getFullYear(), today.getMonth(), 1);
  })();

})();
</script>
</body>
</html>

Co robi powyższy kod?

  • Renderuje popupowy kalendarz z polami na dni i nagłówkiem miesiąca.
  • Pozwala przechodzić między miesiącami.
  • Blokuje daty poza zakresem minDate / maxDate.
  • Wypełnia pole w formacie YYYY-MM-DD.
  • Obsługuje klawiaturę (Enter / Space / Esc) i podstawową dostępność (aria).

To prosty, gotowy punkt startowy. Możesz rozbudować go o:

  • wybór zakresu dat (start/end),
  • zaznaczanie wykrywania świąt,
  • integrację z backendem (np. sprawdzanie dostępności terminu),
  • formatowanie w lokalnym stylu (np. dd.mm.yyyy) — pamiętaj, by trzymać wartość input.value w stabilnym formacie do walidacji.

Dodanie kalendarza do formularza może być proste (HTML5 input[type="date"]) lub bardziej zaawansowane (własny komponent JS). W większości przypadków zacznij od natywnego inputu — to szybko i bezpiecznie. Gdy potrzebujesz więcej kontroli nad wyglądem i logiką, stwórz własny kalendarz, korzystając z przedstawionego przykładu jako bazy.

Szukasz taniego i dobrego hostingu dla swojej strony www? - Sprawdź Seohost.pl