Search
Bootstrap is used to rapidly develop consistent, responsive websites. It was initially created at Twitter as a way to encourage design consistency in internal tools. Later it was publicly released in August 2011 and has become one of the most popular projects on GitHub.
The most important part of a documentation site is the content so let's start there. The front matter for content pages is simple, using five variables for the most part:
---
layout: docs
title: Accessibility
description: A brief overview of Bootstrap's features and limitations for the creation of accessible content.
group: getting-started
toc: true
---
layout
, title
and description
are self-explanatory. Things get interesting with group
and toc
, which are used to build navigation menus. We'll look at how navigation works in the next section. For the body content, Bootstrap does a great job of using simple markdown. The content is a mix of headings, paragraphs, code blocks, lists, links, and the occasional HTML block for tables and accessibility.
### Visually hidden content
Content which should be visually hidden, but remain accessible to assistive technologies such as screen readers, can be styled using the `.visually-hidden` class. This can be useful in situations where additional visual information or cues (such as meaning denoted through the use of color) need to also be conveyed to non-visual users.
<p class="text-danger">
<span class="visually-hidden">Danger: </span>
This action is not reversible
</p>
Keeping the markdown simple is excellent because it makes it easier for the core team to maintain their documentation and for others to contribute. It also helps keep consistency throughout the site.
The Bootstrap documentation has three levels of navigation:
<li class="nav-item col-6 col-md-auto">
<a class="nav-link p-2{{ if .IsHome }} active" aria-current="page{{ end }}" href="/" onclick="ga('send', 'event', 'Navbar', 'Community links', 'Bootstrap');">Home</a>
</li>
<li class="nav-item col-6 col-md-auto">
<a class="nav-link p-2{{ if eq .Page.Layout 'docs' }} active" aria-current="true{{ end }}" href="/docs/{{ .Site.Params.docs_version }}/getting-started/introduction/" onclick="ga('send', 'event', 'Navbar', 'Community links', 'Docs');">Docs</a>
</li>
<li class="nav-item col-6 col-md-auto">
<a class="nav-link p-2{{ if eq .Page.Title 'Examples' }} active" aria-current="true{{ end }}" href="/docs/{{ .Site.Params.docs_version }}/examples/" onclick="ga('send', 'event', 'Navbar', 'Community links', 'Examples');">Examples</a>
</li>
- title: Getting started
pages:
- title: Introduction
- title: Download
- title: Contents
- title: Browsers & devices
- title: JavaScript
- title: Build tools
- title: Webpack
- title: Accessibility
- title: RFS
- title: RTL
- title: Customize
pages:
- title: Overview
- title: Sass
- title: Options
- title: Color
- title: Components
- title: CSS variables
- title: Optimize
Using a data file here makes sense for a couple of reasons:
{{- range $group := .Site.Data.sidebar -}}
{{- $group_slug := $group.title | urlize -}}
{{- $is_active_group := eq $.Page.Params.group $group_slug -}}
$group_slug
is calculated by slugifying the title in the data file. $group_slug
is then compared to the group
front matter field (The one we saw in the content section ) on the current page being rendered. If they match, then the current page is part of this navigation group. This state is stored in the $is_active_group
variable. The next code block renders the group title. The interesting part from a Jamstack perspective is seeing how they use $is_active_group
. It's used to add a collapsed class if it's not the active group, it's also used to set two accessibility attributes: aria-expanded
and aria-current
.
<li class="mb-1">
<button class="btn d-inline-flex align-items-center rounded {{ if not $is_active_group }} collapsed {{ end }}" data-bs-toggle="collapse" data-bs-target="#{{ $group_slug }}-collapse" aria-expanded="{{ $is_active_group }}"{{ if $is_active_group }} aria-current="true"{{ end }}>
{{ $group.title }}
</button>
Following the group title, we have the logic to render the navigation items. First, the pages in the current group are iterated over. Each page is assigned to $doc.
Then $doc
's title is slugified and checked to see if it's the current page in a similar way to how the active group was calculated. The result of this is assigned to $is_active.
$href
is a combination of the docs_version
, $group_slug
and $doc_slug
. It's a bit magic because there just happens to be a page with that URL. There's no real connection there from Hugo's point of view. This will work as long the team is consistent with their structure and doesn't override any permalinks. As an improvement, you could find the source page using the $group_slug
and $doc_slug
and get the permalink from there. This would be less fragile as you are free to change the permalink. However, it would add extra code and complexity, so it may not be necessary here.
<div class="collapse{{ if $is_active_group }} show{{ end }}" id="{{ $group_slug }}-collapse">
<ul class="list-unstyled fw-normal pb-1 small">
{{- range $doc := $group.pages -}}
{{- $doc_slug := $doc.title | urlize -}}
{{- $is_active := and $is_active_group (eq $page_slug $doc_slug) -}}
{{- $href := printf "/docs/%s/%s/%s/" $.Site.Params.docs_version $group_slug $doc_slug -}}
<li><a href="{{ $href }}" class="d-inline-flex align-items-center rounded {{ if $is_active }} active {{ end }}"{{ if $is_active }} aria-current="page"{{ end }}>{{ $doc.title }}</a></li>
{{- end -}}
</ul>
</div>
</li>
One point worth noting is while it looks easy to update a navigation group or item title in the YAML file, there's more than meets the eye. The titles have urlize
run on them to link them back to the original document as we saw. If you want to update a title for the navigation, you will also have to change the file name of the source file. Otherwise the link will break. As a small improvement, I would opt to have the slugified references in the YAML file and then have an optional front matter field in each document called menu_title
if you want to use something other than title
for menu items.
{{ if (eq .Page.Params.toc true) }}
<div class="bd-toc mt-4 mb-5 my-md-0 ps-xl-3 mb-lg-5 text-muted">
<strong class="d-block h6 my-2 pb-2 border-bottom">On this page</strong>
{{ .TableOfContents }}
</div>
{{ end }}
Successful software frameworks like Bootstrap release many major versions of their software. Each major version likely differs enough from each other to warrant its own documentation. Bootstrap has a clever dropdown that makes it easy to switch between versions.
# Snippet of docs-versions.yml
- group: v3.x
baseurl: "https://getbootstrap.com/docs"
description: "Every minor and patch release from v3 is listed below. Last update was v3.4.1."
versions:
- v: "3.3"
- v: "3.4"
- group: v4.x
baseurl: "https://getbootstrap.com/docs"
description: "Current major release and its minor versions. Last update was v4.6.0."
versions:
- v: "4.0"
- v: "4.1"
- v: "4.2"
- v: "4.3"
- v: "4.4"
- v: "4.5"
- v: "4.6"
This file is iterated over on the versions page to create the version appendix. Each major and minor version of Bootstrap has its own branch (or tag), which contains only that version's documentation. The URL structure has a segment for the version number e.g. /docs/5.0/getting-started/introduction/
. I don't have visibility into how this is deployed but what I suspect is that each site is generated independently, and then combined after the build. CloudCannon has a feature called subpaths that makes it easy to do something similar - mount multiple sites together to look like a single site.
I'm a big fan of this strategy for documentation versioning for several reasons:
Search is a great way to help users quickly find what they're looking for. There's several ways to add search to a static site. Bootstrap uses Algolia, which is hard to beat when it comes to ease of integration and search performance.
Bootstrap is a fantastic example of a well-built documentation site on Hugo. If you're building a documentation site, I'd recommend having a dig around their source code as they typically have elegant solutions to common problems. If Hugo is the static site generator you want to use for your next project, take a look at this Hugo tutorial to get you started.
Give your content team full autonomy on your developer-approved tech stack with CloudCannon.