Booker Calendar
Anatomy, states, and interaction rules for the month-view Booker's calendar pane.
This page is a migration blueprint for the month-view Booker's calendar pane. Cal defines the supported booking flows; the previews show the target Coss composition. Pair it with Booker Event Information when assembling the Booker.
Examples use synthetic availability and start with “today” in September 2026. They make no API requests. This is an interactive migration reference, not a production-ready calendar replacement. Week and column layouts, the time-slot list, and the booking form are outside this page's scope.
Anatomy
The map shows the calendar's parts in order, with their intended spacing. Each label links to its section. The calendar is a plain middle pane: background, outer border, shadow, and corners belong to the enclosing Booker, not an additional calendar card. The layout selector applies to the map and all previews below.
The layout depends on the whole Booker's width, not the isolated calendar's width. The map's layout selector exposes all three atom arrangements:
| Booker layout | Pane spacing | Calendar geometry |
|---|---|---|
| Mobile · below 48rem | 16px sides, 12px top, 16px bottom | 36px-high dates, 4px gaps, 18px month heading |
| Compact desktop · 48–64rem | 8px on all sides | Square dates, 4px gaps, 16px month heading |
| Wide desktop · 64rem and above | 16px sides, 12px top, 16px bottom | Square dates, 6px gaps, 18px month heading |
Web app means the feature exists in Cal; explicit target rules identify deliberate behavior changes. API v2 means it is possible with API v2 data and caller configuration, not that the current atom already implements it. Partial identifies a contract gap. See Data contracts for the required mappings.
Month and navigation
The heading shows the displayed month and year. Previous and next move one month at a time; controls are disabled at known boundaries. Changing month clears a selected time and chooses the first selectable date in the new view, unless an existing selection remains visible and valid or the mobile flow requires an explicit date action.
Use the attendee's timezone for “today” and the event's resolved booking-window rules for navigation bounds. Cal's ROLLING_WINDOW counts available days; ordinary ROLLING counts calendar or business days. Do not collapse these into the same date calculation. Navigation limits are not a replacement for disabling individual dates outside the exact booking window.
Only disable next-month navigation at a known booking boundary. An unlimited event has no last month, and an unfetched month is not an unavailable month. The owner supplies resolved bounds separately from the range already loaded; November in the basic examples is a fixture boundary, not a product limit.
Both boundaries reached
When all bookable dates fall in the displayed month, both navigation controls are disabled. In this example the permitted range is September 15–25.
No upper booking boundary
This navigation-only example has no maximum date. Next stays available beyond November; the real owner must request each newly viewed range.
Weekdays
Use Cal's selected UI language for names and the host's configured first day of the week for ordering. The weekday row and date grid must share that order. Date labels remain Gregorian, including when the language uses different numerals.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|
| lun | mar | mer | gio | ven | sab | dom |
|---|
Day cells
A date is bookable when the resolved availability response contains at least one offered slot. The server owns notice periods, buffers, host eligibility, seats, and booking limits. Do not infer availability from the weekly schedule alone. An out-of-office day is a separate state described below.
Available
An available date uses a muted fill. Hover and keyboard focus use the same primary ring. Selecting it updates the date and clears any previously selected time; selecting it again keeps it selected.
Unavailable
Past dates, dates outside the booking window, and dates with no offered slots use no fill, lighter text, and normal weight. They cannot be activated. Availability still loading must use a loading state, not an unavailable date.
Today
The dot identifies today in the attendee's timezone. It does not imply that the date is available. Update this reference when the timezone changes or the session crosses midnight.
Selected
The selected date uses the primary fill and contrasting text. Selection is separate from keyboard focus and from a selected time slot.
Out of office
When every entry for a day is an absence, Cal displays the absence emoji instead of the day number. The accessible label must still include the full date and “Out of office.” A day with both real slots and absence entries keeps its regular day number; the time-slot area handles the partial-day information.
Required extension: The current Coss atom has no absence day state. These previews apply Cal's behavior to the atom's day styling. Preserve absence metadata separately from real bookable slots.
No public information
With neither showNotePublicly enabled nor an alternative host, the absence day is disabled. Private note text must never enter public preview data or the public UI.
Public note or alternative host
This day remains selectable when showNotePublicly is enabled or an alternative host exists, so the time-slot area can show absence information or offer that host. The flag controls selection even when the note text is empty; do not use a nonempty note as the condition. Render note text only when it exists and its public flag is enabled. Selecting an absence must not create a bookable slot from its marker.
The out-of-office example shows disabled absences, public notes, and alternative-host information together.
Day grid
Reserve six rows so the calendar height stays stable between months. Leading cells before the month and unused trailing cells stay empty. They are not selectable dates and must not receive keyboard focus.
Shifted weeks
For the current month, once today reaches the fourth row, extend the grid into the next month so today's week and two following weeks remain visible. Drop early weeks only when necessary to stay within six rows. Future months keep their regular month grid. This is the target atom behavior; Cal currently uses a different cutoff and drops the first seven dates.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Selected: September 28, 2026
Next-month days
The first visible next-month day includes a short month label. Available overflow dates show the full month on hover or keyboard focus with no tooltip delay. Their selection and keyboard behavior are the same as other available dates.
Target selection rule: Keep the displayed month when an overflow date is selected, as Cal's monthly Booker does. The current atom hook switches months; this blueprint deliberately keeps selection independent from the displayed month. The previous interactive example demonstrates this rule.
Fetch every visible date, including overflow. An empty response and a date outside the fetched range are different states.
Loading
Initial loading replaces the heading, weekday labels, and visible day cells. There is no actionable date until availability is known.
While fetching another range or recalculating after a duration/timezone change, keep the heading and weekday labels and replace the date cells. Announce the update without moving focus into the grid. Background refresh can retain valid cached data; loading must not temporarily announce an empty month.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Availability lifecycle
These controls simulate requests rather than calling an API. Use them to inspect loading, refresh, context changes, errors, and late responses. A response must match the active request context before it can update the calendar; a refresh must revalidate selection without treating pending data as an empty result.
Select a sample time, then remove the selected day with Refresh. Change duration repeatedly to test overlapping requests; the final duration must win. Fail a request and retry, or advance “today” to revalidate past dates. These controls and their feedback are documentation chrome, not calendar UI.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Empty and error outcomes
These messages belong in the time-slot area in the target composition, not over the calendar. They are included here because navigating the calendar determines which outcome is shown. Keep navigation available whenever another permitted range can be checked.
Deliberate change: Cal currently auto-advances once when its initial current month is empty and can open a no-availability dialog. This blueprint follows the atom's explicit outcome and action instead: do not silently advance the month or open that dialog alongside these messages.
The owning Booker must resolve state before choosing an outcome: a pending reschedule host choice takes priority; loading is not empty; a failed request shows an error; a selected public absence shows absence information. Only a successful, relevant response can establish an empty result. Preserve Cal's suppression of no-availability interruptions in settings preview and while the reschedule host-choice dialog is open.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
No date selected
When a later available date is already known, use the atom's “View first availability” action to select it. When later dates have not been loaded, offer to check the next range. Do not claim that the event has no future slots based only on a limited cache.
An ended window has no next-range action. A failed request offers retry and must not be presented as unavailable dates. These buttons report the callback they would request; they do not fetch slots.
Interaction rules
| Action | Expected result |
|---|---|
| Open a link with a date | Preserve the date if it is still selectable; otherwise use the first selectable date in view. |
| Select a date | Update the date, clear the selected time, and show that day's slots or public absence information. |
| Change month | Keep a valid visible selection; otherwise select the first selectable date, except in explicit mobile selection mode. |
| Change duration, timezone, or host context | Requery availability and revalidate the selection. Ignore responses from the previous context. |
| Refresh stale availability | Retain a still-valid selection; clear/replace it if its day becomes unavailable. |
| Open the mobile two-step flow | Start without a selected date; only an explicit date action opens time slots. |
| Select a date on ordinary mobile | The owner scrolls to time slots at most once; respect the embed's disableAutoScroll setting. Do not also scroll when using the two-step modal flow. |
The preview feedback underneath complete calendars describes state changes. It is documentation chrome, not an extra element in the production calendar pane.
Keyboard and right-to-left
Tab enters the grid at one selectable date, including a visible overflow date. Arrow keys move by day or week and skip unavailable dates. Home/End move to the first/last selectable date of the week. Page Up/Down move a month and clamp the day at month end; Shift moves a year. Enter/Space select the focused date. Left/right arrows follow the visual direction in RTL. Keyboard focus alone does not change the selected date or time, even when it moves into another month.
An unknown destination requests its range before restoring focus in the original navigation direction. If the range resolves empty, focus the grid rather than an unavailable date. Cancel pending focus if the user moves to another control or clicks elsewhere; late responses must not steal focus.
The previews correct the atom's month-overflow and disabled-week-boundary keyboard cases. These are target behaviors for both the app migration and the atom; documenting them here does not update either production implementation.
| السبت | الأحد | الاثنين | الثلاثاء | الأربعاء | الخميس | الجمعة |
|---|---|---|---|---|---|---|
Selected: 8 سبتمبر 2026
Complete examples
Standard month
September 8 is today. Weekdays have synthetic slots; weekends do not. Navigate through November to check selection and stable grid height.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Selected: September 8, 2026
Preserved date
The attendee arrived with September 23 selected. It stays selected even though earlier dates are available.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Selected: September 23, 2026
Restricted booking window
Only September 15–25 is permitted. Boundary dates remain eligible if they have slots; both month controls are disabled.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Selected: September 15, 2026
With out-of-office days
September 10 is disabled, September 11 exposes a public note, and September 14 offers another host. Select the latter two to inspect their different outcomes.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Selected: September 8, 2026
Explicit mobile selection
This demonstrates Cal's optional mobile embed behavior, not every mobile booking page. No day is selected on entry or month navigation. Select a date to trigger the time-slot step; use Back to return. The callback is simulated here; modal opening, focus return, and ordinary mobile auto-scroll remain the surrounding Booker's responsibility.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
Choose a date to open the time-slot step.
Data contracts
Keep the calendar controlled by the owning Booker. BookerCalendarPreview is a docs-only reference under apps/design-system/components, not a new production component or API adapter.
| Concern | Web app | API v2 | Migration rule |
|---|---|---|---|
| Available days | viewer.slots.getSchedule / existing schedule adapter | /v2/slots (2024-09-04) | Normalize offered slots into calendar dates in the attendee's timezone. Keep original timestamps, including distinct slots with the same local time during a DST change. |
| Booking window | periodType, dates, periodDays, periodCountCalendarDays | bookingWindow.{type,value,rolling} | Distinguish calendar/business-day limits, a window of available days, a fixed date range, and no limit. Preserve the server's timezone-aware boundaries. |
| Out of office | away, emoji, showNotePublicly, public note, toUser | The 2024-09-04 slots response filters out away entries | Partial. This is an API response gap, not just missing atom rendering. Preserve a separate absence channel for the web adapter and keep the public-note flag separate from note text. |
| Rescheduling | rescheduleUid, rescheduleWithSameHost | bookingUidToReschedule, rescheduleWithSameHost | Include the original booking and host preference in the request and cache identity. |
| Routed hosts | Routing responses, eligible members, CRM-owner context | Full routing/CRM-context equivalence is not established; rrHostSubsetIds is hidden from the public API schema | Keep Cal's existing schedule adapter. An internal host-subset input is not a documented public API guarantee, and the atom wrapper does not pass this context. |
| Language and week start | Cal locale; first host's weekStart | Caller locale and normalized host configuration | Pass locale, week start, direction, and translated labels explicitly. |
| Date and month | Booker store and URL parameters | Client state | Use plain date/month keys. Do not serialize a calendar month as a browser-local instant for a server to reinterpret. |
| Loading and refresh | Query state, invalidation, context changes | Query state and cache policy | Track which range is loaded. Refresh stale data and “today”; reject stale responses after context changes. |
Performance and data loading
These rules belong to the owning Booker's data layer, not the calendar renderer. The atom fetches three-month ranges, warms a later range in the background, shares in-flight requests, and reuses cached availability, initial data, and resolved event metadata. Preserve fast navigation through Cal's existing query cache rather than adding a second independent cache.
- Load the visible range first. Include overflow dates. Bound background fetching by the booking window and a limited lookahead/concurrency budget; respect readiness and embed fetch guards. A failed prefetch must not replace a usable calendar with an error and can be retried when needed.
- Reuse matching data. Navigation into a fresh, covered range should not repeat its request. Prefetch into the cache the next view actually reads; share overlapping in-flight work and reuse initial data only when its context matches. Avoid reloading unchanged event metadata on navigation.
- Keep availability fresh. Define expiry and cache retention; preserve the tRPC path's focus refresh, periodic refresh, and targeted invalidation. Do not copy the atom's indefinite covered-month cache. Cached availability never replaces selected-slot checks or final server validation.
- Key by the full booking context. Include the range and every availability-affecting request input, including effective timezone, duration, reschedule booking/host preference, and routing/host filters. Do not copy the older API path's incomplete manual key. Ignore obsolete-context responses; cancel obsolete requests where supported.
- Keep transitions stable. Follow Loading: no empty-state flash while fetching, and no unnecessary skeleton when usable cached data exists. A background refresh must still revalidate the selected date and time.
- Avoid repeated date work. Reuse locale/timezone formatters and derive the grid and slot index when their inputs change, not for every unrelated render. Use lightweight calendar-date arithmetic without losing timezone or DST correctness.
Measure before choosing the prefetch budget. Three months is the atom's strategy, not a migration requirement. Compare cold-load time, cached navigation latency, request count, response size, and server work with and without warming. Include rapid navigation, context changes, and large collective/round-robin events. The availability lifecycle demo simulates request correctness, not real prefetching or performance.
Production integration still required
The page covers the target calendar surface; migration is complete only after the owning Booker is wired and verified:
- Connect real availability, booking-window bounds, public absence metadata, and every supported reschedule/routing context.
- Preserve URL/store selection, exact slot timestamps, context-specific cache identity, stale-response rejection, invalidation, and timezone/midnight updates.
- Integrate the time-slot surface, host-choice priority, mobile modal/scroll behavior, keyboard focus recovery, and screen-reader announcements through loading and refresh.
- Carry forward custom branding, embed events, settings-preview overrides, overlay-calendar controls, and timezone access when event details are hidden.
- Verify these flows in the app, including all three Booker layouts. Synthetic previews do not replace product integration tests.
Implementation references
| Area | Source |
|---|---|
| Target composition and styling | Coss Booker calendar |
| Atom availability windows and cache | Booker data hook, availability requests |
| Calendar integration and mobile selection | apps/web/modules/bookings/components/DatePicker.tsx, booker.tsx |
| Day states and absence visibility | packages/features/calendars/components/DatePicker.tsx |
| Absence details and forwarding | apps/web/modules/bookings/components/OutOfOfficeInSlots.tsx |
| Selection and URL state | packages/features/bookings/Booker/store.ts |
| Availability context, refresh, and fetch guards | apps/web/modules/schedules/hooks/useSchedule.ts |
| Cal next-month range expansion | packages/features/bookings/Booker/utils/isMonthViewPrefetchEnabled.ts, packages/features/schedules/hooks/useTimesForSchedule.ts |
| Booking-window semantics | packages/features/slot-rendering/period-limits.ts |
| API request and window shapes | packages/platform/types/slots/slots-2024-09-04/inputs/get-slots.input.ts, packages/platform/types/event-types/event-types_2024_06_14/inputs/booking-window.input.ts |
| Public API absence filtering | apps/api/v2/src/modules/slots/slots-2024-09-04/services/slots-output.service.ts |
Current product entry points include personal booking pages (/<username>/<event-slug>) and team booking pages (/team/<team-slug>/<event-slug>), including their embedded month-view variants. These are examples, not an exhaustive route inventory.
On This Page