Flexbox Responsive Calendar
Last modified: 22nd September 2022
There’s been a long running argument as to the most semantic way to code a calendar. For me the answer is tables, but in the age of responsive websites they aren’t always a viable option.
I had to develop a calendar recently for a project I was working on and I decided to build it using Flexbox. If you’re not familiar, this article ‘A Guide to Flexbox‘ is a great starting point.
The HTML
I went with an ordered list as this felt like the most semantic, non-tabular way of displaying a calendar. It’s pretty simple, nothing out of the ordinary.
<ol class="calendar-month">
<li class="week">
<ol class="days">
<div class="week__header">Monday</div>
<div class="week__header">Tuesday</div>
<div class="week__header">Wednesday</div>
<div class="week__header">Thursday</div>
<div class="week__header">Friday</div>
<div class="week__header">Saturday</div>
<div class="week__header">Sunday</div>
</ol>
</li>
<li class="week">
<ol class="days">
<li class="day day--empty"></li>
<li class="day day--empty"></li>
<li class="day day--empty"></li>
<li class="day">
<a href="#">
<span class="day__number">1</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">2</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">3</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">4</span>
</a>
</li>
</ol>
</li>
<li class="week">
<ol class="days">
<li class="day">
<a href="#">
<span class="day__number">5</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">6</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">7</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">8</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">9</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">10</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">11</span>
</a>
</li>
</ol>
</li>
<li class="week">
<ol class="days">
<li class="day">
<a href="#">
<span class="day__number">12</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">13</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">14</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">15</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">16</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">17</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">18</span>
</a>
</li>
</ol>
</li>
<li class="week">
<ol class="days">
<li class="day">
<a href="#">
<span class="day__number">19</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">20</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">21</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">22</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">23</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">24</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">25</span>
</a>
</li>
</ol>
</li>
<li class="week">
<ol class="days">
<li class="day">
<a href="#">
<span class="day__number">26</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">27</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">28</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">29</span>
</a>
</li>
<li class="day">
<a href="#">
<span class="day__number">30</span>
</a>
</li>
<li class="day day--empty"></li>
<li class="day day--empty"></li>
</ol>
</li>
</ol>
The CSS
I’m using Sass here but feel free to adjust for CSS or LESS. The ‘flex’ is applied to the .week
class, treating each .week
as an individual row.
The &:first-letter
removes the rest of the word when you reduce down to mobile.
I did it originally using span’s and display:none but this felt tidier.
$border: #e8e8e8;
$cell-width: 14.2857%;
/* Calendar Styles */
.calendar-month {
width: 90%;
margin: 0 auto;
padding: 20px;
background: #fff;
list-style-type: none;
.week {
display: flex;
flex-direction: row;
flex-wrap: wrap;
&__header {
order: 1;
background: #fff;
border-right: 1px solid $border;
text-align: center;
padding: 20px;
text-transform: uppercase;
color: #333;
letter-spacing: 2px;
width: $cell-width;
box-sizing: border-box;
&:nth-child(7) {
border-right: 0;
}
&:first-letter {
font-size: 12px;
}
@media (max-width: 767px) {
padding: 10px 0;
font-size: 0;
}
}
}
.days {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
width: 100%;
}
.day {
position: relative;
order: 1;
width: $cell-width;
background: #fff;
border-right: 1px solid $border;
border-top: 1px solid $border;
transition: 0.5s;
box-sizing: border-box;
&:nth-child(7n + 7) {
border-left: none;
}
&:nth-child(7n + 7) {
border-right: none;
}
&:after {
/* Create responsive square div */
content: "";
display: block;
padding-bottom: 100%;
}
&:hover {
background: #eee;
}
&__number {
position: absolute;
right: 20px;
top: 20px;
margin: 0;
@media (max-width: 767px) {
right: 5px;
top: 5px;
}
}
&--empty {
background: #f8f8f8;
&:hover {
cursor: default;
background: #f8f8f8;
}
}
}
a {
display: block;
width: 100%;
height: 100%;
}
}
Any improvements?
I’m all ears! Hit me up on Twitter @jo_eyre or drop me an email.