What are some good ways to implement breadcrumbs on a Jekyll site?
I have improved slightly on the answers given earlier. I have removed the unordered list and seperated the items with a character (forward slash). I have added a filter for 'index.html' and '.html', so urls like 'mysite.com/path/index.html' and 'mysite.com/path/item-name.html' are also supported. Finally I have capitalized the titles. This results in something that looks like this:
Home / Path / Item name
{% assign crumbs = page.url | remove:'/index.html' | split: '/' %}
<a href="/">Home</a>
{% for crumb in crumbs offset: 1 %}
{% if forloop.last %}
/ {{ crumb | replace:'-',' ' | remove:'.html' | capitalize }}
{% else %}
/ <a href="{% assign crumb_limit = forloop.index | plus: 1 %}{% for crumb in crumbs limit: crumb_limit %}{{ crumb | append: '/' }}{% endfor %}">{{ crumb | replace:'-',' ' | remove:'.html' | capitalize }}</a>
{% endif %}
{% endfor %}
PS. I have created an online resource for snippets like this: jekyllcodex.org/without-plugins
This should give breadcrumbs at any depth (with a caveat, see end). Unfortunately, the Liquid filters are fairly limited, so this is an unstable solution: any time /index.html
appears, it is deleted, which will break URLs that have a folder that starts with index.html
(e.g. /a/index.html/b/c.html
), hopefully that won't happen.
{% capture url_parts %} {{ page.url | remove: "/index.html" | replace:'/'," " }}{% endcapture %}
{% capture num_parts %}{{ url_parts | number_of_words | minus: 1 }}{% endcapture %}
{% assign previous="" %}
<ol>
{% if num_parts == "0" or num_parts == "-1" %}
<li><a href="/">home</a> </li>
{% else %}
<li><a href="/">home</a> » </li>
{% for unused in page.content limit:num_parts %}
{% capture first_word %}{{ url_parts | truncatewords:1 | remove:"..."}}{% endcapture %}
{% capture previous %}{{ previous }}/{{ first_word }}{% endcapture %}
<li><a href="{{previous}}">{{ first_word }}</a> » </li>
{% capture url_parts %}{{ url_parts | remove_first:first_word }}{% endcapture %}
{% endfor %}
{% endif %}
</ol>
How it works is:
- separates the URL, ignoring
index.html
(e.g./a/b/index.html
becomesa b
,/a/b/c.html
becomesa b c.html
), - successively takes and removes the first word of
url_parts
, to iterate through all but the last word (e.g. it goesa b c.html
-> (a
,b c.html
) -> (b
,c.html
); then we stop). - at each step, it makes the breadcrumb link using the current
first_word
, andprevious
which is all the previous directories seen (for the example above, it would go""
->"/a"
->"/a/b"
)
NB. the page.content
in the for loop is just to give something to iterate over, the magic is done by the limit:num_parts
. However, this means that if page.content
has fewer paragraphs than num_parts
not all breadcrumbs will appear, if this is likely one might define a site variable in _config.yml
like breadcrumb_list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
and use site.breadcrumb_list
as the placeholder instead of page.content
.
Here is an example (it doesn't use precisely the same code as above, but it's just a few little modifications).
I made the following breadcrumbs with liquid templates, its as elegant as I could manage :) It uses jQuery to set the active class on the li last li item.
<ul class="breadcrumb">
<li><a href="#"><i class="icon-home"></i>Home</a> </li>
{% assign crumbs = page.url | split: '/' %}
{% assign crumbstotal = crumbs | size %}
{% for crumb in crumbs offset:2 %}
{% unless crumb == 'index.html' %}
<li><span class="divider">»</span> {{ crumb | remove: '.html' }} </li>
{% endunless %}
{% endfor %}
</ul>
<script>$(".breadcrumb li").last().addClass('active');</script>