React

Calendar e date picker shadcn: date, stato e fusi orari

Il Calendar shadcn è react-day-picker dentro un Popover: tieni la data come stringa YYYY-MM-DD e convertila in Date solo nell'interfaccia.

In sintesi

  • Il `Calendar` di shadcn è react-day-picker avvolto in un file che possiedi: gli aggiornamenti a monte non arrivano più.
  • Il date picker è un `Button` in variante outline che apre il `Calendar` dentro un `Popover`.
  • Tieni il valore come stringa `YYYY-MM-DD`: un `Date` porta con sé il fuso del browser che lo ha creato.

Risposta breve: il Calendar di shadcn è react-day-picker avvolto in un componente che possiedi, e il date picker è quel calendario dentro un Popover. Il valore da salvare è una stringa YYYY-MM-DD, non un Date.

Cosa aggiunge il comando

Il CLI copia calendar.tsx e popover.tsx in components/ui, aggiunge react-day-picker e date-fns alle dipendenze e non tocca altro. Da quel momento il file è tuo: gli aggiornamenti di shadcn non arrivano più.

npx shadcn@latest add calendar popover button

Il campo, non solo la griglia

Un calendario da solo non è un campo: manca il valore leggibile, il placeholder e il legame con il form. Il pattern è un Button in variante outline che mostra la data formattata e apre il calendario.

"use client";

import { useState } from "react";
import { format } from "date-fns";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";

export function DateField({ name, label }: { name: string; label: string }) {
  const [value, setValue] = useState("");
  // Local midnight, not UTC midnight: the string is a day, not an instant.
  const selected = value ? new Date(value + "T00:00:00") : undefined;

  return (
    
      
      
        
          
            {selected ? format(selected, "PPP") : "Scegli una data"}
          
        
        
           setValue(day ? format(day, "yyyy-MM-dd") : "")}
          />
        
      
    
  );
}

La stringa, non un oggetto Date

Il valore che attraversa il form è una stringa: 2026-11-01 è lo stesso giorno a Milano, a New York e sul server. Un Date invece porta con sé un istante, e l'istante dipende dal fuso di chi lo crea.

RappresentazioneDove viveCosto
Stringa YYYY-MM-DDStato del campo, form, databaseNessun fuso, nessuna ora
DateSolo dentro il calendario e la formattazioneDipende dal fuso del browser
ISO con ora e ZTimestamp di un evento, non di un giornoServe il fuso dell'utente

Le tre varianti che coprono quasi tutto

  • Data singola: mode="single", il caso del filtro, della scadenza e del campo form.
  • Intervallo: mode="range" con numberOfMonths={2} per i periodi di fatturazione.
  • Giorni bloccati: disabled={{ before: new Date() }} chiude il passato, disabled={{ dayOfWeek: [0, 6] }} chiude i weekend.

Fonte: la pagina Calendar di shadcn/ui (licenza MIT) e il repository react-day-picker (licenza MIT); i formati PPP e yyyy-MM-dd sono di date-fns, anch'esso MIT.

Limite dichiarato

Il calendario disegna i giorni nel fuso del browser, il server non ne ha uno. Finché il valore resta una stringa i due coincidono; appena scrivi new Date("2026-11-01") su un server a ovest di Greenwich, JavaScript legge mezzanotte UTC e il giorno torna indietro di uno. Secondo limite: i giorni disabilitati sono interfaccia, non una regola. Un utente può inviare la domenica che hai chiuso, quindi la validazione va rifatta sul server.

Dove continuare

Nel catalogo trovi la libreria shadcn, un blocco calendario con anteprima e i blocchi della categoria form. Per capire cosa stai installando leggi che cos'è shadcn/ui e come tematizzare shadcn; il confronto tra temi pronti è temi shadcn gratis o premium. Se la data arriva dal server e React si lamenta, la causa è in hydration mismatch sulle date; per le liste lunghe il fratello di questo articolo è select con ricerca.

Domande frequenti

Serve react-day-picker per usare il Calendar?

Sì. `calendar.tsx` è un wrapper di react-day-picker (10.0.2 al 1 novembre 2026) con classi Tailwind: senza quella dipendenza il file non compila.

La data salvata è sbagliata di un giorno. Perché?

Quasi sempre per `new Date("2026-11-01")`, che JavaScript legge come mezzanotte UTC. Passa un'ora locale esplicita: `new Date("2026-11-01T00:00:00")`.

Come faccio un intervallo di date?

Con `mode="range"`: `onSelect` restituisce un oggetto con `from` e `to`. Su mobile tieni un mese solo, su desktop due mesi affiancati con `numberOfMonths={2}`.

Il popover si chiude quando clicco un giorno: è un bug?

No, è il comportamento atteso: il click seleziona e il popover perde il focus. Se vuoi tenerlo aperto, controlla `open` e ignora `onOpenChange` quando la selezione è valida.

Posso usare il calendario dentro un form non controllato?

Sì, con un input nascosto che porta il `name`: il valore inviato è la stringa formattata, non l'oggetto `Date`.