Creating a Calendar with the HTML <details> Element
I was recently setting up a calendar on the Irish Left Archive website and in the absence of a handy Javascript content-switcher or similar in the existing codebase, I made a simple one using the <details>
element. I hadn't experimented a great deal with <details>
before, but it's another useful tool for removing some of that old Jquery we all still have floating around.
It's always an interesting exercise to see how far a dynamic UI element can get using CSS alone, and the calendar has worked quite well, with reliable support in modern browsers. You can see the live version on the Irish Left Archive website, but here's a simplified demo to get an idea:
Code
The HTML consists of twelve <details>
elements, containing an <ol>
list of days of each month and the month name in the <summary>
. The default state of details
can be styled to a grid of squares for each month, and details[open]
to display the calendar for that month.
January
01
…
…
Using this structure, the initial, unopened view can be styled into a grid with something like this:
}
}
}
}
}
For the sake of the example, I've assumed box-sizing: border-box
is set globally for simplicity, and set four items to a row. Depending on the responsive breakpoints it's being integrated with, this can of course be set higher – for example, the live version sets .calendar details
to 16.6%
for rows of six on full width devices.
By default, <summary>
is displayed as list-inline
with an arrow marker. Changing display
removes that in most cases, but according to MDN, Safari needs to be reminded with the custom marker selector… It also doesn't default to using a link-style cursor, so this is added explicitly, along with a hover colour as a further visual cue.
The reason for position: relative
and the large min-height
on the calendar is to allow using position: absolute
to overlay the open state without it covering any succeeding content. This means align-content: start
is also needed to stop the rows being widely spaced.
Perhaps a neater solution would be to apply the forced height only to .calendar:has(details[open])
, and then applying the above in a @support
query, for example:
}
Unfortunately :has()
isn't supported in Firefox yet. With better support, it could also be used for example to make the open calendar a modal using body:has(.calendar details[open])
, but for now the open view uses absolute positioning in the container, with a forced container height.
}
}
}
}
}
Overlaying the clickable months with the open month helps to avoid the fact that there's nothing preventing multiple <details>
from being open at the same time, by hiding the other months until the open one is closed again.
The <summary>
element is restyled to act as a header for the month view. (The flex
and alignment are there to push the "Close" button to the right. That's also why there's a <p>
in there, though I'm sure there's a tidier solution.)
Finally, the content of the calendar itself is set out as a grid of seven-item rows with the following:
}
}
(Note the top and left borders are set on the <ol>
, and the right and bottom on the <li>
elements to avoid doubling.)
To make it a little less boring, I added an SVG background to the closed summary box (it could just use details:not([open])
, but we already have selectors targeting both states to slot the extra rules in to):
}
}
The base64 encoded SVG is just a simple grid of lines to represent the calendar table. Note it uses the preserveAspectRatio
attribute, which is useful in this case since the position and scale of the background is more important than the aspect ratio.
If you're reading this page with dark mode enabled, you'll notice the SVG background clashes. Because the Left Archive site has a fixed theme, I haven't adjusted it for dark mode, and because the image is external, it can't inherit colours. This could be solved by using background-mask
instead (though this still needs prefixing for Chrome support). That's the approach this website takes for icons, for example (see e.g. the topics on this post).
Finally, it's good to have a visual indication of the month opening, so I've added a short sliding-in animation. Using a transition is also an option, but the shift from the small <summary>
box to the position: absolute
open view makes that a bit messy.
}
{
from }
to }
}
Put all that together and you have the example above. A useful version would include content on the calendar of course – in the live version on the Irish Left Archive, it's used to show the type of material we have for the "On This Day" page for each day of the year, and to link through to that page when there's something to see.
Obviously there are still some improvements that could be made with a little bit of Javascript. One person went for the back button to close the open calendar, for example, so there could be a case for adding to the history (which could also give direct links to each open month state). That said, I'd be careful of the knock-on effects of that – a cumulative history change of open and closed states would be more trouble. Personally, muscle memory keeps making me expect the Esc key to be bound to close the calendar as well.
A bit of height adjustment with Javascript to remove that space below it would be good in the case of the demo above, where it's clearly an issue, but I don't think it's a problem in the live use-case I had, where there isn't content below the calendar. Aside from those relatively small UX issues, it's a useful exercise in how much can be achieved in modern CSS without resorting to Javascript.