Year
Twelve months at a glance, with a dot for each event color on a day and a popover of its events.
Try it in the live demo: pick Year in the view switcher, or press Y.
createYearView shows the anchor's calendar year, January to December, as twelve small month
grids. Weeks start on weekRules.firstDay. Each date shows once: a week that reaches into the
month before or after leaves those cells empty, so every month keeps its own shape.
Add it
Copy registry/year-view into your app (it uses shadcn's popover), along with the theme and
registry/calendar, as described in UI source. Then
register the view:
import { createYearView } from "@/components/calendar/year-view/year-view";
export const Calendar = createCalendar<Meeting>()({
eventAdapter,
views: [
createMonthView<Meeting>({ getEventColor: (meeting) => meeting.color }),
createYearView<Meeting>({ getEventColor: (meeting) => meeting.color }),
createDayView<Meeting>(),
],
});The view is named year. Previous and Next move by one year. Its range is the whole year, so
onVisibleRangeChange asks for a year of events at a time: see
loading events by range.
Anatomy
YearView renders the parts below. To change one, edit it in the registry/year-view files you
copied.
- Year view
YearView - Month
YearMonthGrid - Month name, opens the month
- Weekday initials
- Today
aria-current="date" - Outside the month
- Day cell
data-date - Event colors
data-color - Day popover
YearDayPopover - Event
YearEvent
YearView year-view.tsx
├── YearMonthGrid year-month.tsx, one per month
│ ├── Month name opens the month view
│ ├── Weekday initials
│ └── Day cell data-date, the popover's trigger
│ ├── Date aria-current="date" on today
│ └── Event dots data-color, one per color
└── YearDayPopover year-day-popover.tsx, one for the whole year
├── Date opens the day view
└── YearEvent a [data-event-id] button per eventThe popover is one Base UI popover with a detached trigger in every day, so the year never mounts hundreds of popovers.
Options
Prop
Type
What it shows
- Months: two to a row on a phone, up to six on a wide screen. Each keeps room for six weeks, so months side by side line up.
- Days: today's date sits in a filled circle. Under each date, a dot for every event color on that day, up to three, in the order the day's events start. All-day and multi-day events count on every date they cover.
- A day's events: clicking a day opens a popover with its events, all-day first, then by start
time. Each event is a
<button data-event-id>, so the app's own event details open from it as from any other view. The date at the top opens the day in the view namedday. - Month names open that month in the view named
month. - Closed days: with availability, see below.
- Scrolling: where the months don't fit, the view opens with today's month, or the day you last focused, in view. Today, Previous, Next and a picked date scroll their month into view. Only the view scrolls, never the page around it.
- Editing: none. The year shows events without moving or creating them; open a day or a month to edit.
Closed days
With availability, a day without working hours is named "closed" for screen
readers, as in the month view. What's shaded depends on closedDays:
"exceptions"(the default) shades the dates an exception closes, such as a public holiday or an offsite. Weekly closed days, such as weekends, stay plain: the weekday columns already show them, and a third of the year in gray would bury the holidays."all"shades the weekly closed days too."none"shades nothing.
Shaded days next to each other in a week join into one band with rounded ends, so a weekend or a
long holiday reads as one stretch, and the rows stay apart. To draw it differently, such as hatching,
pass renderUnavailable; its band carries the reason, the exception's label and whether open time
meets each end.
Set closedDays when you create the view, or per calendar with
viewOptions={{ year: { closedDays: "all" } }} on Root.
Keyboard
Tab into the preview above, or click a day, and try these keys. The Accessibility page has the full map.
| Focus | Key | Effect |
|---|---|---|
| A day | ← → | Previous or next day, across months |
| A day | ↑ ↓ | Same weekday a week earlier or later, across months |
| A day | Home, End | First or last day of the week's row in its month |
| A day | Page Up, Page Down | Same day in the previous or next month |
| A day | Enter, Space | Open the day's events. Tab moves through them; Escape returns to the day |
| A day | Shift+Enter | Open the day in the day view |
Moving past December 31 or before January 1 navigates to the next or previous year.
Build your own
The strategy pieces are exported, for a year that looks different, such as a heat map of busy days:
getYearRange, navigateYear, getYearTitle, projectYear and yearSpatialNavigation.
projectYear returns twelve months of week rows, each day with its event fragments in display
order, or null outside the month. See Custom views.
Last updated on