Patterns
Booking Calendar
When to use:
users pick a stay or a rental period from a visible calendar, and some days cannot be booked.
Composition notes:
Use selection='range' with min for the first bookable day, max for the end of the booking window, and a disabledDates predicate for unavailable days. The predicate receives ISO days, so it can look them up in the availability data directly. A range can span a disabled day, so validate the range before accepting it (see Error: range across a blocked day); here one message linked to the day grid shows the hint, the error, or the summary, and the Reserve button waits for a valid range.
Trade-offs:
Users see availability before they pick, but the calendar takes much more room than a date field and the app owns range validation.
import {
Button,
Calendar,
CalendarContent,
CalendarDay,
CalendarDays,
CalendarHeader,
CalendarMonth,
CalendarMonthLabel,
CalendarMonths,
CalendarNextButton,
CalendarPrevButton,Day-Cell Custom Rendering
When to use:
days need extra content such as a price, a status, or a count of events.
Composition notes:
Children of CalendarWeeks are the template of a day cell: they are rendered once per day inside a cell context. Read the day with useCalendarDayContext() and put the content inside CalendarDay, so the cell keeps selection, focus, disabled, and overflow behavior. The same works for CalendarMonths with CalendarMonth and useCalendarMonthContext(), and for CalendarYears with CalendarYear and useCalendarYearContext().
The day context is { col, date, fields, label, inMonth }; date is undefined for an empty overflow cell. Take the day number from label or fields.day, never from the ISO date string: in non-Gregorian calendars they differ. Look up data by date, which is the same in every calendar.
Trade-offs:
Users compare days at a glance, but cells get crowded at small sizes (the example uses size='md'), and the extra content is not announced unless it is part of the cell's text. Define the template component outside of render, so React keeps the cells mounted between renders.
import {
Calendar,
CalendarContent,
CalendarDay,
CalendarDays,
CalendarHeader,
CalendarMonth,
CalendarMonthLabel,
CalendarMonths,
CalendarNextButton,
CalendarPrevButton,
CalendarViewButton,Weekday, Month And Year Templates
When to use:
weekday headers, months, or years need their own look, such as dimmed weekends, quarter marks on months, or marked leap years.
Composition notes:
Children of CalendarWeekdays, CalendarMonths, and CalendarYears are templates, like the day cell. Read the item with useCalendarWeekdayContext(), useCalendarMonthContext(), or useCalendarYearContext() and render CalendarWeekday, CalendarMonth, or CalendarYear, so the item keeps its default behavior.
Trade-offs:
Each view can carry its own hint, but visual-only marks such as color or bold text are not announced; the example repeats the quarter and the leap year in the item's aria-label.
import {
Calendar,
CalendarContent,
CalendarDay,
CalendarDays,
CalendarHeader,
CalendarMonth,
CalendarMonthLabel,
CalendarMonths,
CalendarNextButton,
CalendarPrevButton,
CalendarViewButton,Non-Gregorian Calendar
When to use:
the product audience uses a Persian, Hebrew, Japanese, Buddhist, Islamic, or other calendar.
Composition notes:
Create the calendar once with createCalendarSystem, outside of render (see Dates And Calendar Systems), and pass it to calendar together with a matching locale. Month lengths, the number of months, eras, and labels follow the calendar; value, min, max, and disabledDates stay ISO YYYY-MM-DD days. Calendar sets lang from locale but not dir: for a right-to-left locale such as fa-IR put dir='rtl' on the root or an ancestor, and the navigation buttons and chevrons flip with it.
Trade-offs:
Native month lengths, leap months, and eras without changing the stored value or adding dependencies; the calendar data comes from the Intl of the runtime.
import { createCalendarSystem } from '@vega-ui/i18n';
import {
Calendar,
CalendarContent,
CalendarDay,
CalendarDays,
CalendarHeader,
CalendarMonth,
CalendarMonthLabel,
CalendarMonths,
CalendarNextButton,
CalendarPrevButton,One Date In Two Calendars
When to use:
users need to see a date in two calendar systems at once, for example a Gregorian and a Hijri calendar on a travel or government form.
Composition notes:
Share one ISO value between two controlled roots with different calendar and locale. Picking a day in either calendar selects the same day in the other. Each root keeps its own visible month. The roots here show only the days with a plain month label, and the Arabic one is dir='rtl'.
Trade-offs:
No conversion code in the app, but two calendars double the footprint; on narrow screens prefer one calendar and show the other date as text with formatCalendarDate.
import { createCalendarSystem } from '@vega-ui/i18n';
import {
Calendar,
CalendarContent,
CalendarDay,
CalendarDays,
CalendarHeader,
CalendarMonthLabel,
CalendarNextButton,
CalendarPrevButton,
CalendarWeekday,
CalendarWeekdays,App-Wide Locale
When to use:
every calendar of the app shares one locale or calendar system.
Composition notes:
The library has no global providers. Wrap Calendar once with the app settings and the parts, and use the wrapper everywhere; props given to the wrapper still win, as locale='fr-FR' on the second calendar shows.
Trade-offs:
One place to change the settings, but an extra component for each date root the app uses.
import {
Calendar,
CalendarContent,
CalendarDay,
CalendarDays,
CalendarHeader,
CalendarMonth,
CalendarMonthLabel,
CalendarMonths,
CalendarNextButton,
CalendarPrevButton,
CalendarViewButton,Comparison
Quick Decision Rule
Choose Calendar when the user needs a full visual date-selection experience rather than a plain typed field.
Calendar vs DateField
Use Calendar when:
- visual scanning of dates matters
- range and multi-date interactions should feel direct
Use DateField when:
- the flow should behave like a compact text field
- keyboard entry is primary
Calendar vs DateRangeField
Use Calendar when:
- the range should be picked on a visible surface
- month-to-month scanning is part of the task
Use DateRangeField when:
- the flow should stay form-like and compact
Calendar vs DayPicker
Use Calendar when:
- you need header controls and view switching
- the product should not manage date-view coordination itself
Use DayPicker when:
- only day-grid behavior is needed
Calendar vs MonthPicker / YearPicker
Use Calendar when:
- the user should move between day, month, and year views in one coordinated control
Use the lower-level picker components when:
- the UX needs only one of the day, month, or year grids
Choose This Component When
- the date should be chosen visually
- range or multi-date selection matters
- the day, month, and year views should stay coordinated
Do Not Choose This Component When
- a plain field is enough
- only one of the day, month, or year grids is needed
- the UI cannot afford the visual size of a calendar surface
Troubleshooting
Selection Value Shape Feels Wrong
Symptom
The component renders, but the integration state shape does not match expectations.
Likely Cause
selection mode was changed without updating the parent value model.
How To Verify
- inspect
selection - inspect the current
valueshape
Fix
- treat
single,multiple, andrangeas different value contracts
Disabled Dates Still Feel Selectable
Symptom
Users can still navigate to or interact with dates that should be unavailable.
Likely Cause
A custom day template renders its own element instead of CalendarDay, so disabled and excluded behavior is lost.
How To Verify
- compare the custom template with a plain
<CalendarDay /> - test both pointer and keyboard interaction
Fix
- render the custom content inside
CalendarDay, which keeps disabled and excluded state handling
Custom Calendar Content Breaks Navigation
Symptom
Switching between day, month, and year views becomes inconsistent.
Likely Cause
Views or header controls were rebuilt as if they were unrelated components.
How To Verify
- test view switching in the real customized composition
Fix
- return to the standard
CalendarHeaderplusCalendarContentstructure first - reintroduce custom parts incrementally