Schedule Template

Schedule Template

A simple template that lets you display events on a timeline, as well as organize them in groups (week days, conference rooms etc…)

Nucleo icons

Sponsored by Nucleo, a free application to collect, customize and export all your icons as icon font and SVG symbols. Made by the CodyHouse folks!

All the resources available on CodyHouse are released under the MIT license.

We’ve come across this web component many times: when we check the schedule of a conference, or the timetable of the classes of our gym. From a web designer perspective, it is handy to have a simple, responsive template to use if you ever need to create a schedule table. So we built one!

Creating the structure

The HTML structure is composed of three different elements: a div.timeline for the events timeline(09:00, 09:30, ..), a wrapping the events list and a div.event-modal for the modal window used to provide more details about the selected event.

<div class="cd-schedule">
   <div class="timeline">
         <!-- additional elements here -->

   <div class="events">
         <li class="events-group">
            <div class="top-info"><span>Monday</span></div>

               <li class="single-event" data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1">
                  <a href="#0">
                     <em class="event-name">Abs Circuit</em>

               <!-- other events here -->

         <li class="events-group">
            <div class="top-info"><span>Tuesday</span></div>

               <!-- events here -->

         <!-- additional here -->

   <div class="event-modal">
      <header class="header">
         <div class="content">
            <span class="event-date"></span>
            <h3 class="event-name"></h3>

         <div class="header-bg"></div>

      <div class="body">
         <div class="event-info"></div>
         <div class="body-bg"></div>

      <a href="#0" class="close">Close</a>

Adding style

On small devices (window width less than 800px), all the events inside an .events-group are lined up horizontally: we set a display: flex to the .events-group > ul element and an overflow-x: scroll to make the events scrollable.

.cd-schedule .events .events-group > ul {
  position: relative;
  padding: 0 5%;
  /* force its children to stay on one line */
  display: flex;
  overflow-x: scroll;
  -webkit-overflow-scrolling: touch;

.cd-schedule .events .single-event {
  /* force them to stay on one line */
  flex-shrink: 0;
  float: left;
  height: 150px;
  width: 70%;
  max-width: 300px;

As for the .event-modal, it is has a fixed position and it is moved to the right outside the viewport. When the user selects an event, the .modal-is-open class is used to translate the .event-modal back into the viewport.

.cd-schedule .event-modal {
  position: fixed;
  z-index: 3;
  top: 0;
  right: 0;
  height: 100%;
  width: 100%;
  visibility: hidden;
  transform: translateX(100%);
  transition: transform .4s, visibility .4s;

.cd-schedule.modal-is-open .event-modal {
  /* .modal-is-open class is added as soon as an event is selected */
  transform: translateX(0);
  visibility: visible;

On bigger devices, all the events are in absolute position and placed inside a timetable: the top position and the height of each event are evaluated using the data-start and data-end attributes of the event itself and set using JavaScript (more in the Events handling section).

@media only screen and (min-width: 800px) {
  .cd-schedule .events {
    float: left;
    width: 100%;
  .cd-schedule .events .events-group {
    width: 20%;
    float: left;
  .cd-schedule .events .single-event {
    position: absolute;
    z-index: 3;
    /* top position and height will be set using js */
    width: calc(100% + 2px);
    left: -1px;

As for the .event-modal, the opening/closing animation is created using jQuery combined with CSS Transitions and Transformations (more in the Events handling section).

Events handling

To implement this event schedule, we created a SchedulePlan object and used the scheduleReset() and initEvents() functions to init the schedule and attach event handlers to the proper elements.

function SchedulePlan( element ) {
   this.element = element;
   this.timeline = this.element.find('.timeline');
   this.eventsWrapper = this.element.find('.events');
   this.eventsGroup = this.eventsWrapper.find('.events-group');
   this.singleEvents = this.eventsGroup.find('.single-event');


On big devices, the scheduleReset() method takes care of placing the events inside the timetable and set their height. To evaluate the height, for example, we calculate the duration of the event (data-end minus data-start), divide it by the 'eventUnit' (in our case it's 30 minutes) and then multiply it by the height of 'timeline unit' (in our case, 50px).

var self = this;
   //place each event in the grid -> need to set top position and height
   var start = getScheduleTimestamp($(this).attr('data-start')), //getScheduleTimestamp converts hh:mm to timestamp
       duration = getScheduleTimestamp($(this).attr('data-end')) - start;

   var eventTop = self.eventUnitHeight*(start - self.timelineStart)/self.timelineUnitDuration,
       eventHeight = self.eventUnitHeight*duration/self.timelineUnitDuration;
      top: (eventTop -1) +'px',
      height: (eventHeight+1)+'px'

When the user selects an event, the jQuery load() function is used to load the content of the event just selected (its data-content is used to determine the file content to be loaded).
In addition to that, on big devices, the .event-modal is animated to show the event content. First, the .event-modal is placed on top of the selected event and its height and width are changed to be equal to the ones of the selected event; then the .header-bg and .body-bg elements are scaled up to create the morphing animation; at the end of this animation, the modal content is revealed.

SchedulePlan.prototype.openModal = function(event) {
   var self = this;
   var mq =;
   this.animating = true;

   //update event name and time
   this.modal.attr('data-event', event.parent().attr('data-event'));

   //update event content
   this.modalBody.find('.event-info').load(event.parent().attr('data-content')+'.html .event-info > *', function(data){
      //once the event content has been loaded


   if( mq == 'mobile' ) {, function(){;
         self.animating = false;
   } else {
      //change modal height/width and translate it
         top: eventTop+'px', //this is the selected event top position
         left: eventLeft+'px', //this is the selected event left position
         height: modalHeight+'px', //this is the modal final height
         width: modalWidth+'px', //this is the modal final width
      transformElement(self.modal, 'translateY('+modalTranslateY+'px) translateX('+modalTranslateX+'px)');

      //set modalHeader width
         width: eventWidth+'px',  //this is the selected event width
      //set modalBody left margin
         marginLeft: eventWidth+'px',

      //change modalBodyBg height/width and scale it
         height: eventHeight+'px',
         width: '1px',
      transformElement(self.modalBodyBg, 'scaleY('+HeaderBgScaleY+') scaleX('+BodyBgScaleX+')');

      //change modal modalHeaderBg height/width and scale it
         height: eventHeight+'px',
         width: eventWidth+'px',
      transformElement(self.modalHeaderBg, 'scaleY('+HeaderBgScaleY+')');, function(){
         //wait for the  end of the modalHeaderBg transformation and show the modal content;
         self.animating = false;

One note: we implemented a simple load() function to upload the new html content, but you may wanna replace it with a $.ajax call in order to handle errors, beforeSend request etc. according to your project.

💌 Join our newsletter

Get our monthly recap with the latest CodyHouse news