A month grid for picking one date — six rows always, a sliding month change, and the full keyboard model.
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Monday, September 7, 2026
pnpm dlx shadcn@latest add https://matos-ui.com/r/calendar.jsonimport { Calendar } from '@/components/matos-ui/calendar'const [date, setDate] = useState<Date | null>(null)
<Calendar value={date} onValueChange={setDate} /><Calendar
min={new Date()}
max={addMonths(new Date(), 3)}
isDateDisabled={(d) => d.getDay() === 0 || d.getDay() === 6}
/><Calendar locale="pt-BR" /> {/* week starts Sunday, PT month names */}
<Calendar locale="de-DE" weekStartsOn={1} />‹ / › controls don't move
under the cursor, and there's no height animation to chase.AnimatePresence mode="popLayout" keyed on the
visible month, custom carrying the direction, so the outgoing month leaves
flow and the incoming one enters a third of a width away — on
spring.moderate, the panel-lands-exactly tier. The overflow-hidden frame
clips the travel.spring.fast scale-in on the pill,
per cell — no layoutId. A cell exposes isSelected / onSelect, so range
selection is additive later rather than a rewrite.lib. lib/date.ts is pure, dependency-free, and the
only thing that knows the grid shape. The public boundary is the native
Date; swapping the lib for @internationalized/date later is a local
change, not a breaking one. Everything works in local y/m/d terms and
constructs dates at noon so day arithmetic can't fall into a DST gap.getWeekStart), not
Intl.Locale.prototype.getWeekInfo() — that isn't in Firefox. weekStartsOn
always wins.←→ day, ↑↓ week, Home/End week
edges, PageUp/PageDown month, Shift+those year, Enter/Space select.
Crossing a month edge navigates and keeps focus on the moved day. It's a
<table> of <button>s (aria-pressed for selection, aria-current="date"
for today) — a screen reader navigates it as a table.prefers-reduced-motion turns the slide and the pop into opacity
crossfades; selection still lands, instantly.| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | null | — | Controlled selection. |
defaultValue | Date | null | null | Initial selection when uncontrolled. |
onValueChange | (value: Date | null) => void | — | Fires with the picked date. |
month | Date | — | Controlled visible month. |
defaultMonth | Date | selection or today | Initial visible month. |
onMonthChange | (month: Date) => void | — | Fires when the visible month changes. |
min / max | Date | null | — | Selectable range bounds. |
isDateDisabled | (date: Date) => boolean | — | Disable specific dates the range allows. |
weekStartsOn | number | locale's first day | 0 (Sunday) – 6 (Saturday). |
locale | string | "en-US" | BCP-47 tag for weekday / month names. |
showOutsideDays | boolean | true | Fill the grid with adjacent-month days. |
size | "sm" | "md" | "lg" | "md" | Cell size. |
Install Matos UI
Choose a package manager and copy one command for every component.