McGarrah Technical Blog

Visual Indicators for Draft and Future Posts in Jekyll

· 9 min read

My previous article on Jekyll Run plugin configuration documented a frustrating problem: when you run jekyll serve --drafts --future, draft and future-dated posts appear in your listings but look identical to published posts. You can’t tell at a glance which articles are live on production and which are still waiting.

After scrolling past 130+ posts trying to spot my drafts one too many times, I added visual indicators — a pencil icon for drafts, a robot icon for future-dated posts (because robots are cool and futuristic), and italic text for both. The indicators only appear during local development because drafts and future posts don’t exist in production builds. Making system state visible at a glance is a UX principle that applies equally to monitoring dashboards, CI/CD pipelines, and content management — if you have to dig to find the status, the status isn’t working.

Drafts Future
(PENCIL) (ROBOT)

The Problem

Running jekyll serve --drafts --future --unpublished renders everything into site.posts. The archive page, home page, and paginated listings all show drafts and future posts mixed in with published content. There’s no visual distinction.

This matters when you have 50 drafts and 5 future-dated posts queued up. You want to:

What Jekyll Exposes

Before building anything, I needed to confirm what data Jekyll makes available in templates.

Future posts are straightforward. Every post has a post.date, and Jekyll provides site.time (the build timestamp). Compare them:

{% if post.date > site.time %}
  <!-- this post is future-dated -->
{% endif %}

Drafts are trickier. Jekyll doesn’t set a post.draft flag or expose the source collection. But drafts come from the _drafts/ directory, and that path is available:

{% if post.path contains '_drafts/' %}
  <!-- this post is a draft -->
{% endif %}

This works because post.path contains the relative path from the site root, including the directory name.

In production, neither check matters — drafts and future posts aren’t in site.posts at all when building without --drafts and --future. The conditionals are inert. No performance cost, no risk of leaking unpublished content.

The Implementation

Adding Icons to the SVG Sprite

This blog uses a Font Awesome SVG sprite that’s built at compile time from icons referenced in _config.yml. Only icons listed under navigation and external get included. To add the draft and future icons without polluting those lists, I added a new config key:

# _config.yml
post_status_icons:
  - {icon: pencil-alt}     # draft posts
  - {icon: robot}          # future-dated posts

And extended the sprite template to include them:

<!-- assets/fontawesome/icons.svg -->
{% assign keys = 'navigation,external,post_status_icons' | split: ',' %}
{% for key in keys %}
{% for link in site[key] %}
  {% assign icon = link.icon %}
  {% assign svg = site.data.font-awesome.icons[icon].svg | first %}
  <symbol id="{{ icon }}" viewBox="0 0 {{ svg[1].width }} {{ svg[1].height }}">
    <path d="{{ svg[1].path }}" />
  </symbol>
{% endfor %}
{% endfor %}

This adds exactly two SVG symbols to the sprite — no CDN load, no external requests.

Archive Page

The _includes/archive.html gets the detection logic and conditional rendering:

{% for post in site.posts %}
{%- assign is_draft = false -%}
{%- assign is_future = false -%}
{%- if post.path contains '_drafts/' -%}{%- assign is_draft = true -%}{%- endif -%}
{%- if post.date > site.time -%}{%- assign is_future = true -%}{%- endif -%}
<li>
  <time datetime="{{ post.date | date_to_xmlschema }}">{{ post.date | date: "%Y-%m-%d" }}</time>
  {%- if is_draft %}
  <svg aria-label="Draft" class="icon icon-status" title="Draft">
    <use xlink:href="{{ "/assets/fontawesome/icons.svg" | relative_url }}#pencil-alt"></use>
  </svg>
  {%- elsif is_future %}
  <svg aria-label="Future" class="icon icon-status" title="Scheduled">
    <use xlink:href="{{ "/assets/fontawesome/icons.svg" | relative_url }}#robot"></use>
  </svg>
  {%- endif %}
  {%- if is_draft or is_future %}
  <em><a href="{{ post.url | relative_url }}">{{ post.title }}</a></em>
  {%- else %}
  <a href="{{ post.url | relative_url }}">{{ post.title }}</a>
  {%- endif %}
</li>
{% endfor %}

Draft entries get a pencil icon. Future entries get a robot icon. Both get italic text. Published posts render normally with no extra markup.

Here’s what future-dated posts look like in the archive with the robot icon and italic styling:

Future post indicators in the archive view

And drafts with the pencil icon:

Draft post indicators in the archive view

Home Page Excerpt Views

The _includes/meta.html header component passes the detection through as include parameters:

{% include meta.html post=post preview=true is_draft=is_draft is_future=is_future %}

Inside meta.html, the icon renders next to the post title:

<h1>
  <a href="{{ include.post.url | relative_url }}">{{ include.post.title }}</a>
  {%- if include.is_draft %}
  <svg aria-label="Draft" class="icon icon-status" title="Draft">
    <use xlink:href="{{ "/assets/fontawesome/icons.svg" | relative_url }}#pencil-alt"></use>
  </svg>
  {%- elsif include.is_future %}
  <svg aria-label="Future" class="icon icon-status" title="Scheduled">
    <use xlink:href="{{ "/assets/fontawesome/icons.svg" | relative_url }}#robot"></use>
  </svg>
  {%- endif %}
</h1>

The excerpt <article> wrapper also gets a class for italic styling:

<article{% if is_draft or is_future %} class="post-preview-unpublished"{% endif %}>

CSS

Two additions to _sass/classes.sass:

.icon-status
  height: .85em
  width: .85em
  opacity: .6
  margin: 0 .2em

.post-preview-unpublished
  font-style: italic

The status icons are slightly smaller and more transparent than navigation icons — they’re informational, not interactive. The italic class applies to the entire excerpt card for draft and future posts.

Files Changed

File Change
_config.yml Added post_status_icons with pencil-alt and robot
assets/fontawesome/icons.svg Extended sprite loop to include post_status_icons
_includes/archive.html Draft/future detection with icons and italics
_includes/home.html Same treatment for paginated excerpt view
_includes/meta.html Icon badge next to post title in headers
_layouts/home.html Same treatment for list-style home layout
_layouts/paginate.html Same treatment for paginate layout
_layouts/archive.html Same treatment for archive layout
_sass/classes.sass Added .icon-status and .post-preview-unpublished

Why These Icons

Pencil (pencil-alt) for drafts — universally understood as “editing” or “work in progress.” It’s already in Font Awesome Free and visually distinct at small sizes.

Robot for future posts — a nod to the automated scheduled publishing via GitHub Actions cron. The daily build at 00:05 UTC is the “robot” that publishes future-dated posts when their date arrives. It’s also visually distinctive and unlikely to be confused with any other status.

I considered clock and hourglass for future posts but they’re too generic — they could mean “reading time” or “loading.” The robot is unambiguous in context.

Production Safety

This feature is inherently safe for production:

The only “cost” in production is two unused <symbol> elements in the SVG sprite file. They add negligible bytes and are never rendered by the browser.

References


About the Author: Michael McGarrah is a Cloud Architect with 25+ years in enterprise infrastructure, machine learning, and system administration. He holds an M.S. in Computer Science (AI/ML) from Georgia Tech and a B.S. in Computer Science from NC State University, and is currently pursuing an Executive MBA at UNC Wilmington. LinkedIn · GitHub · ORCID · Google Scholar · Resume