Components/Calendar

Calendar

Twelve published components and 105 variants, most of them spent on one thing: a day cell that knows where it sits in the month.

Calendar / Day Button · Components frameLive in the browser · drawn to match the file
Size
Variant
State
Rounded
11
Size=Default · Variant=Range · State=Selected · Rounded=Left28 × 28 in the fileshown at 2.4×
The day button doing its job

One month, five variants at once

Click two days to set a range. Every cell below picks its own variant from where it sits - outside the month, today, inside the range, at one of its ends, or booked and unavailable. That is the whole point of forty variants: the grid never has to draw anything by hand.

June 2026
MoTuWeThFrSaSu
selected 8–14 June · next click sets the start
Defaultan ordinary day in this month
Currenttoday, a muted fill and no selection
Outsidea day belonging to the next month
Rangeinside the selection, square in the middle
Bookedunavailable, drawn without a fill
The fourth axis

Rounded, and the pill inside the strip

A range is a run of muted cells. The two ends need one rounded side each and the middle needs none, so Rounded is a real axis with Left, Right and None - and it only means anything on the Range variant.

A selected end is two shapes, not one: the muted strip keeps the half-rounded corner at 8px, and a dark 10px pill sits inside it holding the number. Drawing it as a single shape would mean the pill could not overhang the strip's square side.

Left · None · None · Right, at real size
891011
why the matrix is 40 and not 150

Two sizes times five variants times five states times three roundings would be 150. Most of those combinations mean nothing: Booked is only ever Disabled, Outside is only Default or Hover, and Rounded matters on Range alone. The file holds the forty that exist and leaves the rest out.

The other ten components

Eight calendars built from the same cell

Day Button and Custom Day Button are the atoms. Everything below is assembled from them and published in its own right, so a range picker or a preset list is one instance rather than forty-two cells placed by hand.

Two months side by side, one selection running across the join. Click twice to set a range.
Calendar / Basic · Range Calendar · 424 × 260
January 2025
SuMoTuWeThFrSa
February 2025
SuMoTuWeThFrSa
The same thing at three months, for picking a quarter.
Calendar / Basic · Range Calendar - 3 columns · 636 × 296
January 2025
SuMoTuWeThFrSa
February 2025
SuMoTuWeThFrSa
March 2025
SuMoTuWeThFrSa
The title becomes two dropdowns, so a distant month is two clicks away.
Calendar / Basic · Month and Year Selector · 214 × 260
SuMoTuWeThFrSa
A gutter down the left holding the ISO week number for each row.
Calendar / Week Numbers · 244 × 280
January 2025
5212345
SuMoTuWeThFrSa
Custom Day Button with Show Extra Text: a price under every number.
Calendar / Custom Cell Size · 352 × 380
SuMoTuWeThFrSa
A month with the ranges people actually ask for, listed beside it.
Calendar / Presets · 300 × 454
September 2025
SuMoTuWeThFrSa
The same month with a date and a time field under it.
Calendar / Date and Time Picker · 220 × 465
September 2025
SuMoTuWeThFrSa
Date10 Sep 2025
Time
A right-to-left month with Persian numerals, proving the grid is not hard-coded to one script.
Calendar / Basic · Persian · 250 × 332
خرداد ۱۴۰۴
شیدسچپج
Calendar / Arrow Button · 16 variants

Previous and Next across four states, at 28px and 48px. Transparent until hovered, then the same muted fill every other control uses; focus is a 3px ring, disabled is half opacity.

DefaultHoverFocusDisabled
The small parts · Day Header, Week Number

Two standalones small enough to miss: the 28 × 20 weekday label above each column, and the 32 × 32 cell in the week-number gutter. Both are published so a hand-built calendar uses the same pieces as the finished ones.

MoDay Header
36Week Number
In the file

Twelve components, 105 variants

Read straight out of the Penpot file. The axes below are the property names you will see in the variant dropdown.

Calendar / Day ButtonSize × Variant × State × RoundedDefault · md × Default · Current · Outside · Range · Booked × Default · Hover · Selected · Selected (Focus) · Disabled × None · Left · Right40
Calendar / Custom Day ButtonVariant × State × Rounded × Show Extra Textthe same, less Booked, plus Yes · No for a second line of text36
Calendar / Arrow ButtonSize × Variant × StateDefault · lg × Previous · Next × Default · Hover · Focus · Disabled16
Calendar / BasicType × MobileBasic · Range Calendar · Range Calendar - 3 columns · Month and Year Selector · Persian × No · Yes7
With Month and Year Dropdown / ItemTypeMonth and Year · Month Only · Year Only3
With time presets / ItemStateDefault · Hover · Selected3
Six standalonesnoneDay Header · Week Number · Week Numbers · Custom Cell Size · Presets · Date and Time Picker6

Using the Calendar

Because a cell in a month grid has to answer four questions at once: how big, what it is (an ordinary day, today, a day from the next month, part of a range, unavailable), what the pointer is doing, and whether it sits at the end of a range. Get those four right and the grid never draws anything by hand.

A selected range is a run of muted cells. The two ends want one rounded side each and the middle wants none. Rounded gives you Left, Right and None, and it only means anything on the Range variant - everywhere else it stays None.

The muted strip keeps its half-rounded corner at 8px, and a dark 10px pill sits inside it holding the number. As one shape the pill could not overhang the strip's square side, which is exactly what makes a range read as continuous rather than as separate cells.

Two sizes times five variants times five states times three roundings is 150, and most of that is meaningless - Booked is only ever Disabled, Outside is only Default or Hover, Rounded matters on Range alone. The file holds the forty combinations that exist. The same pruning takes Custom Day Button to 36.

Forms · 105 variants