Modals
Four real full-screen modals: a calendar, a time wheel, a month-range grid and a plain note viewer. They share one shape, and each has its own quirks on top.
Each modal below is the app’s real one, already open in a phone-sized frame. Use it as you would in the app. In the app, Set, Cancel, ✕ or tapping the dark background closes it. Here it starts again instead, and shows what you set.
Anatomy: one shared shape
Section titled “Anatomy: one shared shape”| Part | Value |
|---|---|
| Scrim | 50% black, covering the whole screen. Tapping it closes the modal |
| Card | Exactly 329px wide, 30px corners. White in light mode, near-black in dark |
| Where it’s drawn | At the very end of the page, not inside whatever opened it |
That last part isn’t decoration. If anything around the opening card has a transform or a filter, it traps a full-screen overlay inside that card’s box instead of letting it cover the screen. Drawing the modal at the end of the page avoids that completely.
Pick, then Set
Section titled “Pick, then Set”Three of the four (Calendar, Time, Month range):
- fade in and out over 200ms
- have a Cancel / Set button pair at the bottom
- confirm nothing until Set is pressed
The Note viewer breaks both patterns on purpose. It has nothing to confirm, so it appears and disappears instantly, with one close button in its header.
Calendar
Section titled “Calendar”A real, styled react-day-picker, not hand-built. It picks a single day or a
date range, and always blocks every day before today.
A picked range gets its own pill shape: rounded on the outside edges, square where it meets the next selected day. A multi-day stretch reads as one band, not a row of separate circles. Today always gets a thin ring, whether or not it’s in the range.
Not the phone’s built-in time input. It’s a hand-built scroll wheel: one column for hours, one for minutes, each snapping to the nearest row as you flick it.
It works like a real iOS or Android wheel. The scroll position is the answer, and the value only updates once the scroll has settled, not on every frame.
When the picked date is today, past hours and minutes are still shown but dimmed. Scrolling onto one snaps back to the earliest allowed time.
Month range
Section titled “Month range”A hand-built 3×4 grid, not the calendar with the days hidden. It’s used where an exact day is more than the question needs (jaunt setup’s “roughly which months work?”).
- It pages by year, with prev / next arrows.
- Past months are dimmed and can’t be picked. The current month gets the same thin ring the calendar gives today.
- One tap is already an answer: it becomes both ends of a one-month range. Tapping a different month afterwards stretches it into a real range instead of starting over.
The plain one: a title pill reading “Note” and a close button (the same pairing every card header uses), over a scrollable, read-only block of text. No fade, no Cancel / Set: there’s nothing to confirm, just something to read and close.
Its card is the pale green info-card fill, not the field fill the other three use.
Do and don’t
Section titled “Do and don’t”Ask with the phone's own box
Are you sure?
For "Are you sure?", use the phone's own confirm box with exact wording (see Voice and tone). Don't build a custom one.
Accessibility
Section titled “Accessibility”- The fade follows “reduce motion”: with it on, a modal appears and disappears straight away.
- Days and times that can’t be picked are marked as disabled, not just dimmed.
- Each modal is a dialog named by its title (“Select time”). Focus moves into it when it opens, and back to what opened it when it closes.
- Tab and Shift+Tab stay inside it. Escape closes it, the same as Cancel (or the Note’s ×).
- The time wheel works by scrolling, and the calendar by tapping, with a mouse or touch.
Source
Section titled “Source”src/web/components/CalendarModal.tsxsrc/web/components/TimePickerModal.tsxsrc/web/components/MonthRangeModal.tsxsrc/web/components/NoteModal.tsx