Four Kitchens

Jekyll event schedule

6 Min. ReadDevelopment

While working on the DrupalCamp Austin schedule, I was surprised to find that there weren’t any Jekyll templates or plugins available for event schedules. Since it was relatively simple to put one together I want to share some code we generated in the process of hosting the event.

Like many community-driven conferences, DrupalCamp Austin is a multi-day, multi-track event with lunch breaks, keynotes, and evening events like parties. These templates were built to easily handle all of that, so if your event is similar they will probably work for you. Read on for a breakdown of our Jekyll event schedule.


Very little is required in _config.yml file. Simply add pretty permalinks and your individual session pages will be generated at the appropriate locations for internal linking between schedule and detail pages.

permalinks: pretty

Post format

Jekyll posts all start with a date stamp: YYYY-MM-DD, but we treated the filenames slightly different than a normal Jekyll post. By adding some extra numbers related to session times, we can control the order of sessions all happening on one day. Although this is strictly alphabetization and not in any way specifying the time of a Jekyll post, it produces the correct time-based sorting when every session is named with this convention.


Looking inside a session, we have a pretty basic setup. If you’re familiar with Jekyll at all, you know about YAML Front Matter. Here are the required properties for scheduled sessions:

title: Session title
category: session
published: true
accepted: true
date: 2013-06-22 9:00am

Some are obvious; on a real site we’ll need to categorize or display different types of content and to make this demo have drop-in functionality you need title, category, and published. The other two are specific to our schedule.

  • accepted is a flag that is useful for events with community-submitted content, allowing you to distinguish which sessions you have accepted for inclusion on the schedule. For our event, it’s possible to have a published session submission that is not included on the schedule. Your event may have different rules so feel free to ignore this.
  • date specifies when the session starts, including the time. This property should be considered the canonical source of the session’s time. The filename is merely an ordering mechanism, and the timestamp described previously is not included in the “post date” internal to Jekyll.

Schedule templates


This is a full Jekyll page, and it prints the entire schedule using this block of code. The only notable thing is the posts are reversed, meaning the schedule will display sessions oldest first, since the default is like a blog with newest posts first.

{% for post in site.categories.session reversed %}
    {% capture day %}{{ | date: "%A" }}{% endcapture %}
    {% if day == 'Saturday' %}
        {% include schedule-day.html %}
    {% endif %}
{% endfor %}

If you have a multi-day event, just duplicate this loop for each day. Our demo code has two days but one is commented out.


This template holds most of the important logic for rendering the schedule:

{% if == 'full' and post.accepted == true %}
    {% include session-full.html %}
{% elsif post.accepted == true %}
    {% capture slot %}{% cycle 'begin', 'skip', 'end' %}{% endcapture %}
    {% if slot == 'begin' %}
        <section class="slot">
            <h3 class="time">{{ | date: "%I:%M%p" | downcase }}</h3>
    {% endif %}
    {% include session.html %}
    {% if slot == 'end' %}
    {% endif %}
{% endif %}

To create time slots that accomodate any number of tracks, we used a rotating variable via the {% cycle %} tag. Cycle does exactly what you might think and cycles through a list of values supplied by the developer. You can add as many as you want, for example if you want to make our three-track schedule only two tracks, remove the skip value, four tracks would require two instances of skip, and so on.

The markup that opens and closes each time slot is output only when the cycle tag says to. By placing cycle in the room-specific conditional (instead of near the top of the template), ‘full’ slots aren’t counted by cycle and the loop logic stays simple while still accommodating an unpredictable number of full-width slots.


This template is pretty straightforward, but check out the data attributes. The template takes the YAML values from each post and converts them to a more predictable format for use by CSS/JS. This is accomplished with a few Liquid filters to remove white space and force lowercase. A little further down I’ll discuss some neat uses of data attributes as opposed to classes.

<article class="session"
  data-room="{{ | downcase | replace:' ','-' }}"
  data-track="{{ post.track | downcase | replace:' ','-' }}"
  data-difficulty="{{ post.difficulty | downcase | replace:' ','-' }}">

Singularity and Breakpoint

CSS can obviously be accomplished however you want, but we’re fans of Singularity for grids and Breakpoint for media query management. I’ve provided some barebones CSS to make the schedule function, and beyond that it makes no assumptions. It’s ready for your design.

For the schedule layout we have a responsive grid with one breakpoint: one column until everything can fit, then we switch to a fluid, asymmetric 4-column grid with columns having ratios of 2/3/3/3. Here’s the entire layout in 20 lines:

// breakpoints
$sched-cols: 820px;

// grid settings
$grids: 1;
$gutters: 1/4;
$grids: add-grid(2 3 3 3 at $sched-cols);

// schedule layout
.schedule {, section.slot {clear: both; }

  @include breakpoint($sched-cols) {
    h3.time { @include grid-span(1,1); }
    [data-room="room-1"] { @include grid-span(1,2); }
    [data-room="room-2"] { @include grid-span(1,3); }
    [data-room="room-3"] { @include grid-span(1,4); }
    .full { @include grid-span(3,2); }

Singularity’s grid-span mixin is pretty slick. First of all it knows about our grid inside the breakpoint mixin due to the definition up top. Second, the syntax is very clean and quick to comprehend. The two arguments are column-span and position. For example: grid-span(1,1) is a single column starting at the first column, grid-span(1,4) is the last column of our grid, and grid-span(3,2) spans 3 columns starting at the 2nd column. See an expanded, commented version of this stylesheet on GitHub.

Data attributes for use by CSS and JS

However you choose to accomplish your layout, I recommend using the data attributes to position each session under the correct heading. Relying on data attributes instead of nth-child() or another markup-specific selector means you don’t have to worry about the exact order that Jekyll outputs the schedule, because the session just falls under the correct heading. This may not sound like a big deal, but renaming files just to accommodate schedule changes is annoying… trust me! Our room headings use data attributes as well, allowing everything to be controlled by a single selector.

It’s also more convenient to use data attributes if you want to write any sort of JS to enhance the schedule. DrupalCamp Austin’s track filters run off of the data-track attribute, allowing you to filter out chunks of the schedule you might not be interested in. Example:

Animated GIF showing DrupalCamp Austin schedule filters in action

Demo and code

That’s what you were looking for anyway, right? I hope you enjoyed the walkthrough, but the real action is in the GitHub repo. Pick our code apart and feel free to suggest improvements!

Although it’s possible to tu\r\n \this into a plugin, I chose not to. Part of the appeal of small Jekyll sites is free hosting/deployment via GitHub Pages, which currently disallows the use of plugins. Providing templates instead of a plugin means that everyone including GitHub Pages users can make use of the files.

If a vanilla example isn’t quite compelling enough, check out the DrupalCamp Austin schedule, with a real design and some nice bells and whistles like sticky headers: