Theming with Twig

The true power of Pico is in its limitless customization.
This guide will help get started with Twig and the endless possibilities it provides.


So, you’ve had a little taste of Pico and you’re wanting more. Now that you know the basics of Installing Pico and Creating Content, it’s time to take your site to the next level.

Pre-existing Possibilities

If you haven’t seen it yet, there’s a link up in the navbar to our Themes Gallery. This gallery consists of community developed themes. There are a lot of possibilities here, but keep in mind that these themes were all created and developed by community members and not by the Pico Project itself.

Generally, we’re happy to help with any issues you might encounter using these themes, just be aware that we aren’t directly involved with them. Unfortunately, many of these themes were contributed once, then forgotten about by developers that moved on. That’s not to say they aren’t still useful! They just might need a little care to see their full potential.

We know that this is a bit of a pain point for new users at the moment, so we’re working on curating a set of “Officially Supported” themes for the future.

Making Pico Your Own

Pico’s Themes are generated from Twig Templates. Twig is an incredibly powerful templating language developed by Symfony.

As you start to explore Twig, you should absolutely check out Twig’s Documentation! We just cannot recommend it enough, it’s an amazing resource for learning the language.

We’re going to start getting our hands dirty at this point, so you’ll want at least a basic understanding of HTML and CSS before you dig any deeper.

Don’t worry, you don’t have to be a professional web designer or anything. But for the sake of simplicity, we’re going to assume from this point forward that seeing HTML and CSS code won’t scare you away.

Where to Begin?

Whether you’re new to Twig or just to Pico, your first stop should be our Default Theme! It’s already in your themes/ folder, and it’s also available on GitHub.

In Pico, a lot of functionality is left up to the Theme to provide. This allows Pico to be super flexible. There’s essentially no rules here! You can build your site entirely your way. Dream big, anything is possible!

But that flexibility comes at the expense of requiring the theme to do some heavy lifting.

The Default Theme is intentionally simple and straightforward. We’d like it to be something of a “reference implementation” for you. The way functionality is provided in the Default Theme is a good guide for how you should code it yourself.

In fact, the Default Theme can also make a great starting template! Just make a copy of the themes/default/ folder and get developing. Just don’t forget to change your theme: in config.yml to match the new folder name!

Other Options

Default Theme not doing it for you? That’s fine, it’s a little bare-bones of a starting place. There are plenty of Themes in the Themes Gallery you could start from (license permitting).

Also, a little pro-tip: Once you’ve got a good grasp on theming Pico, it’s really easy to convert free HTML templates into proper Pico themes. The web has plenty of these available for many different types of website. Quite a few of the designs in our gallery are actually ports of freely available HTML templates!

What’s in a Theme?

At its most basic, a Pico theme needs two things: an index.twig template and a pico-theme.yml file to configure it. You’ll find many of our themes actually lack pico-theme.yml. Don’t worry, they’re just old, but they’ll still be compatible thanks to Pico’s ongoing support for backwards compatibility.

Templates

Your index.twig is the main HTML/Twig template that tells Pico how to render your page.

But you’re not limited to just the one template. Maybe you want a separate page layout on specific parts of your site? Setting the Template: property in the metadata of your page will make it use a separate template. For example, setting Template: blog will tell Pico to use blog.twig instead.

This isn’t the only way to switch layouts either, as Twig is powerful enough that you could code multiple layouts right into index.twig with the right logic!

Configuring Themes with pico-theme.yml

As of Pico 2.0, themes should include a config file named pico-theme.yml within their folder. This config file is similar to Pico’s config.yml. Properties that you set here will also be exposed to your theme as config.theme_config, but more on that in a bit.

Here we see the Default Theme’s pico-theme.yml.

api_version: 3                  # Use Pico's latest API version for themes

meta:                           # Register meta headers used by this theme
    Logo: logo                  # The URL to your website's logo (value is passed to Pico's "url" Twig filter)
    Tagline: tagline            # Your website's tag line, shown right below your site title (supports Markdown)
    Social: social              # A list of social icons that will be shown in your website's footer;
                                #     You must specify a "title", "url" and "icon" per entry

twig_config:                    # Twig template engine config
    autoescape: html            # Let Twig escape variables by default
    strict_variables: false     # If set to true, Twig will bail out when unset variables are being used
    charset: utf-8              # The charset used by Twig templates

widescreen: false 
  • api_version - The API Version lets Pico know how to interpret your theme and whether it will need any backwards compatibility features. The current API is version 3.

  • meta - If your theme uses additional YML metadata properties in your page headers, you can register them here. Registering a property here allows you to effectively alias a prettier version of it for your users. In this example from the Default Theme, someone can define a Logo in their page header, and that gets exposed to Twig as the lowercase logo. This can also be a convenient place to document their functions inline for your user, but this is of course optional. We’ll go into more details about accessing metadata headers in a bit.

  • twig_config - Optional configuration to pass to Twig. If you don’t specifically have a need to change these, you should just copy the values shown here.

  • Custom Options - Anything else you define in this file will be available to your theme as config.theme_config. You can use the values here to provide some global configuration options to your users. In the Default Theme, setting widescreen: true will make the main content area of the page wider. In the Twig template, it does this by attaching an extra, widescreen, class to the body if this value is set to true (and then of course applying some extra CSS styles to it).

Tell Me About Twig

While you’re familiarizing yourself with the Default Theme, take a look at Twig’s Documentation and the overview page Twig for Template Designers. Compare some of the syntax in the Overview to the examples within the Default Theme.

Twig is incredibly powerful, so we’ll only be covering the Pico related bits in this guide. Don’t worry though, we’ll still have some examples to get you going.

Pico’s Global Variables

As you’re building your site, there’s going to be a few variables you’ll want to use to fill in the blanks. After all, what good would having the same static page for your whole site be?

Use these variables to dynamically populate the content on your page.

Global Variable Description
{{ site_title }} The title of your website, as defined in config.yml.
{{ config }} Contains the entirety of your configuration from config.yml and other config files. You’ll probably use this more for running logic than printing to the page.
{{ base_url }} The base URL to your Pico site (eg, http://example.com). Pico will try to determine this automatically, but it can be manually set in config.yml if needed. Don’t use this for links to your content pages, use the Link filter instead.
{{ theme_url }} The URL to the currently active theme’s folder.
{{ assets_url }} The URL to Pico’s assets folder.
{{ themes_url }} The URL to Pico’s themes folder. Don’t confuse this with theme_url, this is the folder that contains that one.
{{ plugins_url }} The URL to Pico’s plugins folder.
{{ version }} Pico’s current version string (e.g. 2.0.0)
{{ meta }} Contains the YML Metadata values of the current page.
{{ meta.title }} The title of the page (from the Title YAML header).
{{ meta.description }} The description of the page (from the Description YAML header).
{{ meta.author }} The author of the page (from the Author YAML header).
{{ meta.date }} The date of the page (from the Date YAML header).
{{ meta.date_formatted }} The formatted date of the page as specified by the date_format parameter in your config.yml.
{{ meta.time }} The Unix timestamp derived from the Date YAML header.
{{ meta.robots }} The Robots YAML header. Used in your theme to fill out the content attribute of your meta robots HTML tag. See the Default Theme for implementation details.
{{ content }} The content of the current page after it has been processed through Markdown.
{{ previous_page }} The entire data of the previous page, relative to current_page.
{{ current_page }} The entire data of the current page. See the “Pages” section below for details.
{{ next_page }} The entire data of the next page, relative to current_page.

About URLs

Remember that URLs (e.g. {{ base_url }}) never include a trailing slash / so be sure to add one if needed. Also, theme_url, assets_url, themes_url, and plugins_url all contain base_url. You don’t need to add it yourself.

Let’s use a URL variable. Say you wanted to link to a CSS stylesheet at themes/my_theme/example.css. The HTML/Twig for this would read <link rel="stylesheet" href="{{ theme_url }}/example.css" type="text/css">.

Arrays and Dot Notation

Some of the variables Pico exposes, such as config and meta are actually arrays of data and not singular values.

In Twig, we can access these using dot notation. In the list above, you can see this on items like meta.title. Twig’s dot notation is a shorter way of writing meta['title'], or, the item within meta named title.

This applies to config too. Maybe you want a different page layout if pages are ordered alphabetically? Then you could check to see if config.pages_order_by was set to alpha. Values from your pico-theme.yml can also be accessed through config.theme_config.

Escaping

Keep in mind that Twig automatically escapes all HTML strings by default. This means if you added something like Tagline: My <strong>favorite</strong> color to your page and tried to print it with {{ meta.tagline }}, you’d literally see My <strong>favorite</strong> color on the page (markup and all)!

The recommended way to add formatting like this to your YML is to use Markdown syntax instead and then use Pico’s markdown filter. This would look like Tagline: My **favorite** color and {{ meta.tagline|markdown(singleLine=true) }}. We’ll cover the markdown filter more when we talk about Filters below.

Another potential solution to this is to use Twig’s raw filter (eg, {{ meta.tagline|raw }}). Just be aware of the potential security implication when doing so. Using raw will allow any HTML to be printed to the page as if it were “safe”.

The {{ content }} is the only variable that isn’t HTML escaped, since it contains the markdown-generated HTML for the page.

Pico’s Page Variables

When using Pico, your markdown .md content files are exposed to Twig as an array via the pages() function. You can also access their data using the current_page, prev_page, and, next_page variables.

Each page has a number of different variables that you can access using the Dot Notation we discussed above.

Page Variable Description
{{ id }} The relative path to the content file (which acts as its unique ID).
{{ url }} The URL to the page.
{{ title }} The title of the page (from the Title YAML header).
{{ description }} The description of the page (from the Description YAML header).
{{ author }} The author of the page (from the Author YAML header).
{{ date }} The date of the page (from the Date YAML header).
{{ date_formatted }} The formatted date of the page as specified by the date_format parameter in your config.yml.
{{ time }} The Unix timestamp derived from the Date YAML header.
{{ raw_content }} The raw content of a page, not yet processed through Markdown (You can use the Content filter we’ll discuss later to access the processed content instead).
{{ meta }} The metadata values of the page (see the global {{ meta }} variable above for details).
{{ prev_page }} The entire data of the previous page, relative to current_page.
{{ next_page }} The entire data of the next page, relative to current_page.
{{ tree_node }} The page’s node in Pico’s page tree. Usage of this is a rather advanced topic check out Pico’s page tree documentation for more details.

The pages() Function

The pages() function provides powerful depth-control over which pages you’re interested in. This allows Pico to always respond lightning-fast when accessing pages.

You may sometimes see older themes access the pages variable directly as this used to be the convention in older versions of Pico. While it’s still possible to access pages in this manner, we won’t discuss it here because it’s not recommended anymore.

The pages() function is a rather exhaustive subject, so it’s got its own in-depth guide to go into more detail.

By default, pages() returns a list of all main pages. This includes top level pages like content/index.md and content/page.md, but also sub-level indexes like content/sub/index.md (but not any of the other pages in sub/).

The pages() function can also take a few input variables. If you give it a page id or folder name (eg pages("blog")), it will return only the children of that page on the page tree. So in this example it would return pages like content/blog/blog-post-1.md).

Another important pages() trick is using depthOffset=-1 to include the parent page as well. This is commonly used when building page navigation, that way you can include a link to your index.md. We’ll go over that more when we get to examples though.

Accessing Specific Pages

Although the pages() function is used whenever you want to access the pages array, individual pages can still be accessed through the pages variable directly without a performance hit.

Why would you need to access a specific page from within your theme? Plenty of reasons! For example, in Pico’s Default Theme, a file named _meta.md is used to configure your site’s logo, tagline, and social media links. This gives the user an easy option to define their configuration right alongside their content.

If you wanted to access the tagline from this file, you’d use {{ pages["_meta"].meta.tagline }}. You can access any page this way, by using its page id as a selector (inside the square brackets [ ]).

Some themes even use the metadata of index.md as configuration by accessing it with {{ pages["index"].meta.property }}. Don’t forget that page IDs don’t include the .md from the file name!

Pico Provided Filters

One of the features that makes Twig so powerful is its ability to Filter data. Twig filters are amazingly flexible with their ability to be piped together. In addition to Twig’s built-in filters, Pico includes several of its own filters. Apply these to any variable with an I-bar |, just as you would with Twig’s own filters.

Filter Description
link Use the link filter to create internal links to your content files. This guarantees that will work whether URL rewriting is enabled or not. Just pass a page ID string through the filter to get back its URL (e.g. {{ "sub/page"|link }} might return https://example.com/pico/?sub/page).
url The url filter replaces URL placeholders (like %base_url%) within strings. This is helpful for when you’d like to use URLs in your metadata headers. For example, if your pages had an Image: property, you could set this with Image: %assets_url%/my_image.png, then utilize it in your Twig template with {{ meta.image|url }} to have Twig fill out the assets_url part for you.
content You can use the content filter to get the markdown processed content of any page. Just pass this filter the page ID string and you’re good to go. {{ "sub/page"|content }}.
markdown The markdown filter allows you to process any string as markdown. For example, if you wanted to allow some formatting in your page Description:, you could pass it through the markdown filter with {{ meta.description|markdown }}. You can also pass metadata as a parameter to have it replace %meta.*% placeholders (so, {{ "Written by *%meta.author%*"|markdown(meta) }} might return “Written by John Doe”). Keep in mind that by default, the markdown filter returns each paragraph in your content wrapped in paragraph tags <p></p>. If you don’t want your output to have these, use the singleLine parameter, like this: {{ "This is a *single* line"|markdown(singleLine=true) }}.
sort_by Arrays can be sorted by their keys using the sort_by filter. Let’s say you wanted to make some navigation, with your pages appearing in a certain order. You could use {% for page in pages|sort_by([ 'meta', 'nav' ]) %}...{% endfor %} to iterate through all your pages, ordered by their nav property. The selector [ ] in this example is looking at page.meta.nav. By default, items which couldn’t be sorted are moved to the bottom of the array. You can change this behavior by specifying bottom, top, keep, or remove as a second parameter. Top and bottom are self explanatory, keep will keep items in their original order, while remove will remove them from the results altogether. So, if you wanted to not include pages that didn’t have nav set, you could use pages|sort_by([ 'meta', 'nav' ], remove).
map You can return all values of a given array key using the map filter. For example, {{ pages|map("title") }} would return all page titles.
url_param and form_param The url_param and form_param Twig functions allow you to access HTTP GET and POST values. GET values are query strings passed by url (think of your search terms tacked onto the end of a google url like ?my_search_term=something), while POST data is sent behind the scenes (like when you submit an HTML form). Using url_param and form_param allows you to implement features like pagination, tags and categories, dynamic pages, and more - with nothing but Twig! There’s an in-depth guide to this functionality that goes into more detail.

Up Next…

There’s an awful lot of variables on this page, but how do we use them to build a template? It’s probably easier to just show you the basics rather than explain them.

On the next page we’ll break down the Default Theme, going over the basics of what makes it work. A lot of its code will be similar, if not identical, to what you’ll need for your own projects. What better way to get you going with Pico than to show you what’s already in the box? See you there!

Deconstructing the Defaults

GitHub Pages - This page was generated from 48ae8bf8adbdaddd97baea3870469b369c421f98 at 2022-06-08 21:05:42 +0000

Pico was made by Gilbert Pellegrom and is maintained by The Pico Community. Released under the MIT license.