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.
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.
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.
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.
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.
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.
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.
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.
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.