The best way to learn is through example, right?
Let’s break down the Default Theme and examine it piece by piece.
Found a typo? Something is wrong in this documentation? Just fork and edit it!
Pico’s Default Theme is intentionally simple and straightforward. You can consider it the “reference implementation” of Pico’s default functionality. A lot of the code you see here will be similar, if not identical, to what you’ll need for your own projects. The Default Theme itself can also act as a good starting template for building your own theme.
The best way to learn is through example, right? Let’s get started breaking down the Default Theme into bite sized code snippets. As we go, we’ll stop to explain all the Twig code and what it’s doing.
There’s nothing overly complicated about the head
section of your Pico page. The Twig bits mostly involve using simple logic to check if the user defined a few variables, then adding them to the HTML head
.
<title>{% if meta.title %}{{ meta.title }} | {% endif %}{{ site_title }}</title>
{% if meta.description %}
<meta name="description" content="{{ meta.description|striptags }}" />
{% endif %}
Here we have an HTML Title tag, providing the page title. In the example, we check if the page has a meta.title
, then if so, we print the title followed by an I-bar separtor |
and the site_title
. With this setup, your page would look something like “My Page Title | My Website” in the browser’s tab or titlebar.
Next, if there’s a meta.description
for the page, we add that to the HTML as well. In this case, we’ve run it through Twig’s striptags
filter to remove any formatting the user may have provided. While we may want this formatting later when we’re building the page itself, remember, we’re still filling out the head! It’s not going to be happy with some extra tags jammed in there.
Both of these examples check if
a variable exists before printing the corresponding code. In Twig, you can check for the existance of any variable with simple if
statements like these.
It’s a little more complicated than that, Twig’s just good at figuring out what should evaluate to true
and what should evaluate to false
. You can check the Twig Docs on If Statements for more information. Generally though, if something seems like it should be false, like null
, 0
, an empty array []
, or of course false
, it will be. Almost everything else evaluates as true
.
Be warned though, this can sometimes get a little confusing. A variable that isn’t defined and a variable that is explicitly set to false
will look identical to this simple if
statement. If you ever need to know that something was intentionally set to false
, you should also check if it is defined
.
As you can see, unlike some languages, Twig won’t crash from trying to access a variable that doesn’t exist. It just returns null
instead. In Pico, this is because Twig’s strict_variables
option defaults to false
. If you enable strict_variables
in either your config.yml
or pico-theme.yml
, Twig will instead throw an error in these situations.
{% if meta.robots %}
<meta name="robots" content="{{ meta.robots }}" />
{% endif %}
Does not compute? Don’t worry about it. This bit is to set up the HTML robots
meta tag, which allows you to tell some search engines whether or not to crawl a particular page. Including this in your theme is nice touch (Pico even defines the Robot
metadata header out-of-the-box), but its entirely optional. When in doubt, just copy this block as is, and it’ll be there for anyone that wants it.
{% if current_page %}
<link rel="canonical" href="{{ current_page.url }}" />
{% endif %}
Another bit for your friendly neighborhood webcrawler. The canonical
tag tells search bots the proper URL to index your page as. This can help them to filter out things like query strings and redirected links that might otherwise confuse them. Again, this is optional, but it really doesn’t hurt to include it.
<link rel="stylesheet" href="{{ theme_url }}/css/style.css" type="text/css" />
<link rel="stylesheet" href="{{ theme_url }}/css/droidsans.css" type="text/css" />
<link rel="stylesheet" href="{{ theme_url }}/css/fontello.css" type="text/css" />
You probably want to link some stylesheets or Javascript to your theme. The best way to do this is using theme_url
to get the location of your specific theme’s folder. This will make sure those resourses always get found, no matter what your Pico installation looks like.
{% if pages["_meta"].meta.logo %}
<div id="logo">
<a href="{{ "index"|link }}">
<img src="{{ pages["_meta"].meta.logo|url }}" alt="" />
</a>
</div>
{% endif %}
This one’s not too hard, but there’s a few different things going on. First, we check if the user has filled out the Logo:
metadata property in their _meta.md
page (if not, that’s it, we stop there).
Next, we’re making a link to the index
page using the link
filter. This will generate a proper link to the index
page regardless of whether the user has [URL Rewriting][] enabled or not. It’s the recommended way to manually link to your content pages.
Finally, we attach an image using the meta.logo
property from _meta.md
as our source URL. We run it through the url
filter to make sure any of Pico’s [URL variables][], like %base_url%
and %assets_url%
get replaced in the output.
<div id="title"{{ pages["_meta"].meta.tagline ? ' class="tagline"' }}>
<a href="{{ "index"|link }}">
<h1>{{ site_title }}</h1>
{{ pages["_meta"].meta.tagline|markdown }}
</a>
</div>
The first thing to look at in this example is the outer containing div
tag. There’s an interesting code snippet we haven’t seen before here. This bit of code uses a Ternary Operator, which is like a condensed if else
statement. It’s definitely more of an advanced syntax, but it can be really helpful in places where a traditional if else
block of code would be cumbersome.
In our example, we’re asking “Does meta.tagline
(on _meta.md
) exist? If so, print ' class="tagline"'
to the page.” So, if a user has defined the Tagline:
meta property, this div
will have an extra class
on it that it wouldn’t otherwise have (allowing us to apply some extra styles in our CSS). In most cases, a Ternary statement is formatted condition ? response_if_true : response_if_false
, though you’ll notice in the example, we aren’t actually doing anything if false.
<div id="nav" role="navigation" tabindex="-1">
<ul>
{% for page in pages(depthOffset=-1) if not page.hidden %}
<li{% if page.id == current_page.id %} class="active"{% endif %}>
<a href="{{ page.url }}">{{ page.title ?: page.id }}</a>
</li>
{% endfor %}
</ul>
</div>
“For Page in Pages()” has a nice rhyme to it, huh? Need to access your Pages? This simple for
loop is usually how you’ll do it.
In our example, the pages() function is given depthOffset=-1
that way it’ll also return the index
page in its results. So, we’re looping through each page
that the pages()
function returns, as long as that page isn’t hidden
(meaning it has Hidden: true
in its metadata).
Next, as we’re building our navigation list, we check if the page id
we’re looking at matches the id
of the current_page
. If so, we add the active
class to that list item for styling.
Finally, we link to the page’s url
, using the page title
for the link text if it exists. If there’s no title, we fall back to using the page’s id
.
You’ll notice this looks similar to the [Ternary Operator][] we discussed above. This time, it’s a shorthand version which equates to “Is there a page.title
? If so, use that. If not, use page.id
instead.”
<div id="main" role="main">
<div class="container">
{{ content }}
</div>
</div>
What, you were expecting something hard? {{ content }}
will print the Markdown processed content of the current page. Navigate to a different page, you’ll see different content. Easy as that.
<div class="social">
{% for social in pages["_meta"].meta.social %}
<a href="{{ social.url }}" title="{{ social.title }}" role="button">
<span class="icon-{{ social.icon }}" aria-hidden="true"></span>
<span class="sr-only">{{ social.title }}</span>
</a>
{% endfor %}
</div>
Let’s look at how the Default Theme handles social links. Like Logo and Tagline, these are extra metadata headers defined in pico-theme.yml
.
Here we have another for
loop, but this time we aren’t iterating over pages()
. Instead, we’re looking at each entry within the social
header. For each item, we’re making a new link and filling out the details with the item’s url
, title
, and icon
. Nothing we haven’t seen before here.
The only thing special to note is that social.icon
is only half the class name. The Default Theme uses [Fontello][] for icons, which uses the convention of starting every name with icon-
. To make it simpler for the end user, the icon-
part is hardcoded here and they’ll only have to provide the specific icon’s name. This is a fairly typical practice for web icons, [Font Awesome][] for example often starts theirs with fa-
. While it’s certainly not a Pico specific thing, keeping the user experience in mind almost always results in better code!
Where to go from here. Two choices.