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.
| Rappresentazione | Dove vive | Costo |
|---|---|---|
Stringa YYYY-MM-DD | Stato del campo, form, database | Nessun fuso, nessuna ora |
Date | Solo dentro il calendario e la formattazione | Dipende dal fuso del browser |
ISO con ora e Z | Timestamp di un evento, non di un giorno | Serve 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"connumberOfMonths={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`.