One calendar, many shapes: redesigning an availability calendar
· TicketingHub · Research, design, Rails, CSS, JavaScript, tests, docs
Every TicketingHub supplier has an availability calendar in their dashboard. It shows each product, each day and each time, with how many guests are booked against how many fit. My working assumption is that people open it to see what runs tomorrow, to close a day, or to check where a group of twelve could still go. The bookings it shows come in through the booking widget, which I also design.
The calendar worked, but it was hard to read. I did this project on my own, from the research to the pull requests: the data analysis, the design, the Rails views, the styles, the JavaScript, the tests and the help article.
This is the calendar as it ships. It is live, so try both views.
Live · the shipped calendar
Switch between Month and Timeline, then turn on Show booked only. Wednesday the 16th is today. Dummy products and bookings; the markup, colours and rules are the shipped ones.
Start with the data, not the screens
Before changing anything I looked at who actually uses this page. I took the 947 suppliers with a booking in the last 90 days and counted two things.
| Products per supplier | Suppliers |
|---|---|
| 1 to 3 | 497 |
| 4 to 10 | 231 |
| 11 to 30 | 161 |
| 31 to 100 | 54 |
| Over 100 | 4 |
| Times a supplier runs per day | Suppliers |
|---|---|
| 1 to 5 | 488 |
| 6 to 15 | 143 |
| 16 to 40 | 64 |
| 41 to 100 | 25 |
| Over 100 | 47 |
The same page has to work for a guide with one tour a day and for a company with hundreds of products. One layout cannot serve those shapes equally well. This project makes a single grid as good as it can be, and the layouts for the other shapes come after.
What the calendar was really showing
The old month view drew every product on every day in full, so the calendar repeated itself seven times a week. When I tried it with real supplier shapes, the things that matter were lost in that repetition: a booking, a closed day, an exception, a time without a guide.
The reason turned out to be simple. A supplier sets up a season, and the calendar draws all of it. That is useful, because it shows which products and times are still empty, and it is why the page has a "Show booked only" filter. But a schedule is a setting, in the same way a price is. Outside the high season most of what the calendar draws reads zero, and in the high season the same calendars get very busy. In both cases the eye is looking for the change against the setting, and the old design gave a booking the same shape and weight as the zeros around it.
So the goal for the month view became clear: keep the whole schedule on screen, and make everything that is not a zero easy to find.
Here is the same week three times: the old month view, the new one, and the timeline, a second view that I come back to further down.
Before · the month view on main
One colour per block, blended from blue to red by how full the whole day is. Bold heads, a rule, and the only thing you can click is the underlined time.
After · shipped, Month
Every time is its own chip in its own band, and the whole chip is the link. A sold-out day turns the block blue, an all-closed day gets the dashed outline, and only an overbooked time is red.
After · shipped, Timeline
The same week and the same bookings, turned on their side. Each product is a row with its total for the week, the chips use the same six bands, and the orange corner marks a day with an exception.
Six bands instead of a blend
The old block had one colour, blended from blue to red by how full the whole day was. A full time and an overbooked time disappeared inside a quiet day, and a day that was simply selling well ended up red.
I replaced the blend with six fixed bands: even tints of the dashboard's blue, darker as a time fills up, up to the blue itself for a full time. Red is kept for a time with more guests than room, and a closed time is a hollow chip that says "Closed". If guests were booked before it was closed, it says "Closed, 2" so they are not forgotten.
Six bands, every pair above 7:1
The old blocks put white on #085ED7 at 5.9:1, and blue on the pale empty block at 4.4:1, which fails even AA.
I wanted every pair to pass WCAG AAA contrast, which asks for 7:1, and the dashboard's blue was too light for that. Darkening it to #0950be fixed the calendar and also changed every primary button, link and badge in the dashboard, so the pull request says so at the top and lists the pages worth checking. In the same change I replaced the blues that were hard-coded in tabs, focus rings, the sidebar and the charts, so the dashboard now has one blue.
Making a time easy to hit
The bands were first applied to the whole block. On a line, the only thing you could click was still the underlined time, a target of about 40 by 18 pixels, and everything to the right of it was dead space.
I drew the same day in six treatments and compared them on a real week.
Six ways to make a time easy to hit
A. Where it started
Reference. The block carries the day's colour and the lines show none of their own. The target is the underlined time, with no hover feedback.
B. Whole line is the link
Smallest change. Same block and colours, but the line is the link: 32px tall, full width, a light hover wash and a focus ring.
C. Hairlines and a chevron
List feel. Like B, plus a hairline between lines and a chevron on hover that says this opens something.
D. A chip per timeChosen
Each time shows its own fullness. The block turns neutral and every line is a chip in its own band, so a full 13:00 and an overbooked 14:00 stand out inside a quiet day. Tallest of the six.
E. Chips in two columns
Dense days in half the height. The same chips, two to a row. Reading left to right is less natural for times than a single column.
F. Line with a fill bar
Fullness as a bar, not a colour. A whole-line target with a 3px bar under it. No second colour scale, but the tallest lines of the row-based options.
Hover over the lines to compare the targets. One day, one product, eight times: an unlimited time, a full one, an overbooked one, a closed one and one with a guide.
I went with a chip per time. Each time is a full-width target with a hover state, and it carries its own band, so a full 13:00 and an overbooked 14:00 stand out inside a quiet day. The timeline already drew its times this way, so both views now share one chip.
Quieting the block
With a chip per time the block was clearer, but also louder. I removed the noise in five small steps and checked each one on the same two days.
Quieting the block, one step at a time
Start
One chip per time, 32px tall, bold times, 4px apart.
+1 Times in normal weight
Eleven bold times in a column were the heaviest thing on the screen.
+2 Chips 2px apart
The chips read as one list, and a day of eleven times is 20px shorter.
+3 Chips 28px tall
Still a comfortable target for a pointer, and another 44px saved on eleven times.
+4 Bare zero muted
An unlimited time with nothing booked shows a quieter 0, so 0/4 and 2/4 stand out.
+5 No zero total
A day whose total would read 0 shows no total, so the number is not said twice.
The same two days in every column: a dense day with nothing booked, and a day with bookings, a full time, a closed one and a guide. The last column is what shipped.
On a day with eleven times, the five steps together save 64 pixels of height, and the numbers that are not zero are easier to find.
The grid around the blocks got the same care. A Mon to Sun row stays in view, hairlines separate days and weeks, a minimum height keeps an empty week from collapsing, days are plain numbers with the month spelt out only where it changes, and a dot marks today. The calendar now opens on thirty days instead of three months.
A second view: the timeline
For a supplier with a handful of products, a better question than "what happens on Tuesday" is "how is the food tour doing this week". The timeline turns the grid on its side. Products run down the left with their total for the week, and the seven days run across. Weeks with nothing on are left out.
Three details are worth pointing out:
- The product names stay in view while the days scroll sideways on smaller screens, because seven columns do not fit next to the sidebar below a wide desktop.
- A day with an exception gets a small orange corner, the complement of the blues, with the exception's name on hover.
- Saving an edit redraws only that product's row, and the link carries the view and the date window, so the redrawn row counts the same days as the one it replaces.
Testing it on four shapes
A design that works on a quiet day can fall apart on a busy one. I ran the final treatment through different supplier shapes, from an empty calendar to one product with a dozen times a day. Here are four of them. The second frame draws the same shapes on the timeline.
Live · the same treatment on four supplier shapes
Three products with four to six times each.
Live · the same four shapes on the timeline
Three products with four to six times each. The timeline leaves out a week with nothing on.
Keeping it fast
The calendar is capped at 5,000 rows, and one supplier gets close to that. Writing the times out inline, instead of rendering a partial per line, and building the event link once per page instead of once per chip, halved the render time of both views at that size.
A few smaller fixes went in with it. Tooltips are now built on first hover instead of all at once. The edit popover no longer closes when a press that started inside its form ends outside it. And "Show booked only" used to remove day cells, which broke the grid. It now keeps every day and empties it.
Tried and rejected
I prototyped folding the empty times into a line like "+15 open". It made the booked chip easier to see, but the calendar was still a listing of the schedule with part of it hidden, so I dropped it.
For venues with many times a day I tried a table with times down the side and days across. It reads well for one product and falls apart as soon as two products share an hour.
What comes next
This work made the month view as good as a single grid can be, and added one more view. The research points further than that. My recommendation in the ticket is to flip what the month treats as content: list the departures that have guests or need attention, and keep the schedule as one quiet line per day.
Beyond that, some shapes want a layout of their own. A catalogue of hundreds of products needs search and an agenda, because the product name is the information and does not fit a narrow cell. A supplier who sells dated events needs a list of events, not a grid.
These directions have not been tested with users yet. The open questions are written down: who the main user of the page is, the owner planning ahead or the person running tomorrow, and which three questions they ask the calendar most.
Outcome
Both views are built, covered by tests and documented in the help article. They are in production now, in a test phase with suppliers, so the next changes will come from how people actually use them. I am also comparing the page's 95th percentile load time with the numbers from before.
What I took from it
The research changed what I think the problem is. The conclusion I wrote in the ticket is that the month view's problem is not density but what it treats as content, and that is why folding empty times away did not help.
Putting the five quieting steps side by side made each one easy to judge. I would work that way again on any dense screen.
What is still missing is people. The open questions about who uses this page and what they ask it are written down, and the test phase with suppliers is where the answers start.
Working together
I did this one on my own: the research, the design, the Rails views, the styles, the JavaScript, the tests and the help article.
If you are planning something similar, I am taking on new work. Write to hello@edigil.com or use the contact page.