- Paligo Documentation
- Nexus
- Nexus themes
- Build a custom theme
Nexus themes
Build a custom theme
A custom theme is a folder of templates, stylesheets, scripts, images and fonts, packaged as a ZIP file. This section is for the person who builds it, such as a web developer. It assumes you know HTML and CSS.
The templates are written in a template language called Twig. Paligo gives each template the page content and information about the site, such as its title, languages and navigation. The template decides what HTML to produce around them.
The quickest way to build a theme is to start from Paligo Base:
Download Paligo Base, as described in Download a theme, and unzip it.
Change its templates, styles and options.
Preview it on your own content with Paligo Theme Studio, or upload it to a test layout.
When it is ready, upload it to the layout you publish with, or push it from Paligo Theme Studio.
You can also have an AI coding assistant, such as Claude Code or GitHub Copilot, make the changes for you. Paligo Base includes instructions that tell the assistant what a theme must contain and what it can use. See Build a theme with an AI coding assistant.
The topics in this section describe each part of a theme:
Start from Paligo Base: how Paligo Base is built, and where to change it.
Build a theme with an AI coding assistant: how to have an AI coding assistant build or change a theme.
Theme package structure: the folders and files, and how to package them.
The theme manifest: the
theme.jsonfile that describes the theme.Theme templates: the templates Paligo uses and what each one must contain.
Theme options reference: how to offer options in the Layout Editor.
Theme assets: stylesheets, scripts, images and fonts.
Translate a theme: text in the theme that changes with the language.
Template variables: everything a template can show.
Paligo components and helpers: ready-made search, navigation and language controls.
Search in a theme: how a theme works with Nexus Search, Algolia and Coveo.
What theme templates can use: the parts of Twig that themes can and can't use.
Theme validation messages: what the checks report and how to fix it.
A theme controls only the look and structure of the pages. It can't contain server code, and templates run with a restricted set of Twig features, so a theme can't read or change anything outside its own pages.
Start from Paligo Base
Paligo Base is a complete theme, written to be changed. Download it, as described in Download a theme, and change as much or as little as you want: its colors and fonts, its templates, or all of it. This topic describes how it is built, so that you know where to make a change.
Where things are
File | Contains |
|---|---|
| The page frame for every page: the header, the navigation sidebar and the footer. Read it first. |
| Topic pages: the breadcrumb trail, the content, the previous and next links, and the on-page table of contents. |
| The portal page: the banner, the featured topics and the list of topics. |
| The search results page. |
| The logo and site title, the portal banner, the search box and the light and dark mode button. |
| All of the theme's styles. Its first section sets the colors, fonts and sizes. |
| Styles for the output of the |
| Code colors, used when the layout's Code colours is Theme colours. |
| Scripts for the navigation sidebar, the on-page table of contents, the light and dark mode button and the portal. |
| The theme's own texts, in English. |
| A short introduction to the theme. |
| Instructions for AI coding assistants. See Build a theme with an AI coding assistant. |
| Points Claude Code to |
Change colors, fonts and sizes
Most changes only need the CSS custom properties at the top of assets/css/main.css. Every color, font and size in the theme comes from them:
Property | Controls |
|---|---|
| Links, buttons and highlights. The Accent color option sets these. |
| The two colors of the gradient on the built-in logo mark and the current navigation item. |
| The page background, and the background of the sidebar, code blocks and admonitions. |
| Text colors, from strongest to faintest. |
| Borders. |
| The color of each type of admonition. |
| The text font and the code font. |
| The size and line spacing of text. |
| Rounded corners. |
| The height of the header, the widths of the sidebar and the on-page table of contents, and the greatest width of the page. |
| Code colors, when Code colours is Theme colours. They are in |
:root {--paligo-primary: #0b7a75;--paligo-primary-hover: #075955;--paligo-font: "Source Sans 3", system-ui, sans-serif;}
Paligo Base loads the Inter font from Google Fonts. To serve your own font instead, add it to assets/fonts/ as a WOFF2 file, declare it with @font-face, change --paligo-font, and remove the @import of Google Fonts at the top of main.css.
Light and dark mode
Paligo Base follows the reader's light or dark setting, and readers can switch with the button in the header. The dark colors are a second set of the same properties, in main.css under @media (prefers-color-scheme: dark) and :root[data-theme="dark"]. If you change a color, change it in both sets.
When a reader selects the button, the <html> element gets data-theme="light" or data-theme="dark", and the browser remembers the choice.
Change the page structure
base.html.twig is divided into Twig blocks, which the other templates replace where they need to. To change one part of every page, edit that block in base.html.twig:
Block | Contains |
|---|---|
| The header and the navigation sidebar together. |
| The menu button for the sidebar on small screens. |
| The search box in the header. |
| The language menu and the light and dark mode button. |
| The breadcrumb trail. |
| The navigation sidebar. |
| The main area of the page. |
| The content inside the main area. |
| The page title in the browser tab. |
| Classes on the |
Keep {{ paligo.head }} and {{ paligo.foot }} in base.html.twig, and keep your stylesheets after {{ paligo.head }}, so that your styles take priority over Paligo's.
Style your content
Paligo writes the HTML for your content, and gives it these classes:
Content | Classes |
|---|---|
Admonitions |
|
Figures |
|
Tables |
|
Code |
|
Accordions |
|
Glossary terms |
|
Interface names and keys |
|
Untranslated text in a draft |
|
Paligo's own stylesheet styles these, and main.css styles them again. To change them, change the rules in main.css.
Right-to-left languages
Paligo Base sets dir="rtl" on pages in right-to-left languages, but its stylesheet isn't mirrored for them. If you publish in Arabic or Hebrew, check every page type and add the rules you need, such as for [dir="rtl"] .site-nav.
Keep it working with Paligo
Keep the
site-search__…class names if you change the search box. See Search in a theme.Keep an option's
keyintheme.jsonwhen you change the option. Layouts store their values by key, so renaming a key loses the value set for it on every layout.Check the theme in light and dark mode, and on a topic page, the portal page and the search page.
Build a theme with an AI coding assistant
An AI coding assistant, such as Claude Code or GitHub Copilot, can build or change a theme from a description of what you want. You don't need to explain Paligo's rules to it first. Paligo Base includes an AGENTS.md file with instructions for coding assistants. It describes:
the files a theme needs, and how to package them
the rules a theme must follow to work with Paligo, such as keeping
paligo.headandpaligo.footand the search box's class namesthe parts of Twig that templates can use
the information and components that Paligo gives a template
how to declare theme options
how to check the result
Coding assistants read AGENTS.md when they start working in a folder. Claude Code reads CLAUDE.md instead, and the CLAUDE.md in Paligo Base tells it to read AGENTS.md.
To build a theme with an AI coding assistant:
Download Paligo Base, as described in Download a theme, and unzip it.
Open the theme folder in Visual Studio Code, and start Paligo Theme Studio's preview, as described in Preview a theme in Paligo Theme Studio.
Start your coding assistant in the theme folder.
Describe the theme you want. For example:
Change the colors and fonts to match our website, use our logo in the portal banner, and add links to our support pages to the footer.
The preview updates each time the assistant saves a file, so you can follow its changes.
Check the result in the preview: on a topic page, the portal page and the search page, and in light and dark mode. Ask the assistant to change anything that isn't right.
When the theme is ready, upload it to a layout, as described in Upload a custom theme, or push it from Theme Studio, as described in Push a theme to a layout.
If Paligo reports a problem with the theme, give the assistant the messages, and it can fix them. For what they mean, see Theme validation messages.
An assistant that can control a web browser can also look at the preview itself. Open the preview in a browser, as described in Open the preview in a browser, and the assistant can read the page, follow links and check how it looks.
Keep AGENTS.md and CLAUDE.md in your theme if you go on working on it with a coding assistant. Paligo doesn't publish them.
Theme package structure
A theme is a folder with this structure. Only theme.json and templates/page.html.twig are required.
my-theme/├── theme.json the theme manifest (required)├── templates/│ ├── page.html.twig every topic page (required)│ ├── base.html.twig a shared page frame the others extend│ ├── portal.html.twig the portal page│ ├── search.html.twig the search results page│ └── partials/ smaller templates that others include│ └── header.html.twig├── assets/ stylesheets, scripts, images and fonts│ ├── css/main.css│ ├── js/theme.js│ └── img/logo.svg└── i18n/ translations of the theme's own text├── en.json└── de.json
Folders under templates/ and assets/ can have any name and any depth.
Create the ZIP file
Compress the contents of the theme folder, not the folder itself, so that theme.json is at the top of the ZIP file.
macOS: open the theme folder, select everything in it, and choose Compress.
Windows: open the theme folder, select everything in it, and choose Send to > Compressed (zipped) folder.
If you compress the folder itself, Paligo still accepts the theme, but shows a warning. If the files are more than one folder deep, the upload is rejected.
Files that your computer adds are ignored and don't need to be removed: __MACOSX folders, .DS_Store, files starting with ._, Thumbs.db and desktop.ini. So are .git folders and .gitignore, .gitattributes, .gitkeep and .gitmodules files.
Limits
Limit | Value |
|---|---|
Size of the ZIP file | 20 MB |
Size of the files when unzipped | 100 MB |
Number of files and folders in the ZIP file | 5,000 |
Allowed file types
A theme can only contain files with these extensions:
.twig .css .js .json .svg .png .jpg .jpeg .webp .woff2 .ico .txt .md .map
If the ZIP file contains any other type of file, the whole upload is rejected. A theme can never contain .php files.
These are also rejected: symbolic links, two files with the same path, and paths that start with / or a drive letter or that contain ...
The theme manifest
Every theme has a theme.json file at its top level. It names the theme, says which version of the theme contract it is written for, and lists its options.
{"name": "Acme Docs","version": "1.0.0","author": "Acme Inc.","contract": "^1.0","options": [{"key": "accent","type": "color","label": "Accent color","description": "Links, buttons and the portal banner.","default": "#3a46e8"}],"i18n": { "sourceLanguage": "en" }}
Field | Required | Description |
|---|---|---|
| Yes | The theme's name. It is used in the name of the downloaded ZIP file. |
| Yes | The theme's version. Use a version number such as |
| Yes | The versions of the theme contract the theme works with. Use |
| No | Who made the theme. |
| No | The settings the theme offers in the Layout Editor. See Theme options reference. |
| No |
|
The contract
The theme contract is the set of variables, functions and rules that Paligo gives templates, as described in this section. Its current version is 1.0. The contract field says which versions your theme works with, so that a later, incompatible version of the contract can't break a published theme.
Use "^1.0", which means any 1.x version. These forms are also accepted:
Form | Means |
|---|---|
| Exactly 1.0 |
| Any 1.x version |
| Any 1.0.x version |
| Any 1.x version |
| Any version |
| Either range |
Comparisons such as ">=1.0" are not accepted.
Theme templates
Paligo uses up to four templates, all in the templates/ folder:
Template | Used for | Required |
|---|---|---|
| Every topic page | Yes |
| The portal page, the site's front page | No |
| The search results page | No |
| Nothing directly. The others can extend it, so the page frame is written once | No |
If the theme has no portal, search or base template, Paligo uses a plain built-in one. The search page is only produced when the layout has a search engine turned on.
Every other template, such as a partial for the header, can have any name and is used only when one of your templates includes it. Paligo checks every .twig file in the theme, including any that nothing uses.
A minimal theme
This page.html.twig produces a complete page:
<!doctype html><html lang="{{ site.lang }}"{% if site.dir %} dir="{{ site.dir }}"{% endif %}><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{{ page.title }} | {{ site.title }}</title><link rel="stylesheet" href="{{ asset('css/main.css') }}">{{ paligo.head }}</head><body><main><h1>{{ page.title }}</h1>{{ page.content }}</main>{{ paligo.foot }}</body></html>
What every page needs
{{ paligo.head }}just before</head>. It adds the stylesheet for your content, the search setup, analytics, SEO tags and the layout's Custom <head> code.{{ paligo.foot }}just before</body>. It adds the scripts for search, code copy buttons and glossary popovers, and the layout's Custom end-of-body code.lang="{{ site.lang }}"on<html>. Search with several languages uses it to choose the right index.dir="{{ site.dir }}"on<html>whensite.diris set, so right-to-left languages display correctly.
If a template leaves out paligo.head or paligo.foot, Paligo adds it before </head> or </body> itself and writes a warning in the publishing log. Place them yourself so that you control where they go.
Share a page frame between templates
Write the parts every page shares once in base.html.twig, and fill in the parts that differ with blocks:
templates/base.html.twig:
<!doctype html><html lang="{{ site.lang }}"{% if site.dir %} dir="{{ site.dir }}"{% endif %}><head><meta charset="utf-8"><title>{% block title %}{{ page.title }} | {{ site.title }}{% endblock %}</title><link rel="stylesheet" href="{{ asset('css/main.css') }}">{{ paligo.head }}</head><body>{% include 'partials/header.html.twig' %}{% block content %}{% endblock %}{{ paligo.foot }}</body></html>
templates/page.html.twig:
{% extends 'base.html.twig' %}{% block content %}<main><h1>{{ page.title }}</h1>{{ page.content }}</main>{% endblock %}
Template names are relative to the templates/ folder.
HTML is escaped
Everything a template prints is HTML-escaped, so text from your content or the layout can't break the page. The values that already are HTML print as HTML without anything extra: page.content, paligo.head, paligo.foot and the output of the paligo_…() components.
Limits
Each page must finish rendering within 10 seconds. A template that includes itself, or a macro that calls itself without stopping, is stopped after 128 levels.
Theme options reference
Theme options are settings your theme offers in the Layout Editor, so that the people who publish with it can adjust it without changing the theme. You declare them in the options list of theme.json, and templates read the value chosen for the layout as theme.options.<key>.
{"key": "accent","type": "color","label": "Accent color","description": "Links, buttons and the portal banner.","default": "#3a46e8"}
Field | Required | Description |
|---|---|---|
| Yes | The name templates use. An option without a key is ignored. |
| No | The kind of value, which decides the field in the Layout Editor. See the types below. A missing or unknown type gives a text field. |
| No | The option's name in the Layout Editor. Without one, the key is shown. |
| No | Help text shown under the field, up to 500 characters. |
| No | The value used until someone changes it. Give every option a default, so that the theme works before anyone sets it. |
| For | The choices. |
Types
Type | Also written as | In the Layout Editor | Value in templates |
|---|---|---|---|
| A color picker | A string | |
|
| A text field | A string |
|
| A list of choices | A string |
|
| Enable or Disable |
|
|
| A text field, checked for a number when the layout is saved | A number |
| Upload image | The image's file name |
color: The value must be a hex color with 3, 4, 6 or 8 digits, such as #3a46e8, or a CSS color name, such as rebeccapurple or transparent. Values such as rgb(…) and hsl(…) are not accepted. Use a default of "" to let the option be left empty, and test for it in the template.
<style>:root { --accent: {{ theme.options.accent }}; }</style>text: Up to 2,000 characters.
select: List the choices in values, either as objects with a value and a label, or as plain strings that are both:
{"key": "hero-contrast","type": "select","label": "Portal banner text and logo","default": "auto","values": [{ "value": "auto", "label": "Automatic (from the background)" },{ "value": "light", "label": "Light" },{ "value": "dark", "label": "Dark" }]}
"values": ["auto", "light", "dark"]A select without any values is shown as a text field.
toggle:
{% if theme.options.sidebar %}<nav class="sidebar">…</nav>{% endif %}
number: Any finite number, such as 68 or 1.5.
image: The person setting the option uploads an image, and Paligo publishes it with the theme's assets. Use asset() to link to it:
{% if theme.options['hero-image'] %}<div class="hero" style="background-image: url('{{ asset(theme.options['hero-image']) }}')"></div>{% endif %}
Reading options in templates
Use a dot for a simple key, and brackets for a key with a hyphen in it:
{{ theme.options.accent }}{{ theme.options['hero-image'] }}
The Layout Editor checks values when the layout is saved. If a stored value doesn't suit the option's type, such as after you change an option from text to toggle, it is ignored when publishing and the default is used.
Theme assets
Everything in the theme's assets/ folder is published with the site: stylesheets, scripts, images and fonts. To link to an asset, use the asset() function with the file's path inside assets/:
<link rel="stylesheet" href="{{ asset('css/main.css') }}"><script src="{{ asset('js/theme.js') }}" defer></script><img src="{{ asset('img/logo.svg') }}" alt="">
Always use asset() rather than writing the path yourself. Pages are published in a folder per language, and asset() gives the path that works from every page. A path containing .. is rejected.
Inside a stylesheet, link to other assets with paths relative to the stylesheet, as usual:
@font-face {font-family: "Inter";src: url("../fonts/inter.woff2") format("woff2");}
Fonts must be in WOFF2 format. Images can be SVG, PNG, JPG, WebP or ICO.
The stylesheet for your content itself, which styles the tables, admonitions, code blocks and other elements in page.content, comes from Paligo and is added by paligo.head. Your theme's stylesheets are loaded alongside it, so you can change any of its styles.
For the class names in your content, such as admonitions, tables and code blocks, see Start from Paligo Base.
Translate a theme
Text that your theme writes itself, such as "Skip to content" or "Open menu", can be translated, so that it matches the language of each page. The text that Paligo writes, such as the content and the site title, is translated as usual. To translate your theme's own text:
Add an
i18nfolder to the theme.Add a JSON file for the theme's source language, named after its language code, such as
i18n/en.json. List each text with a key:{"skipToContent": "Skip to content","openMenu": "Open menu"}Add a file with the same keys for each other language, such as
i18n/de.json:{"skipToContent": "Zum Inhalt springen","openMenu": "Menü öffnen"}In
theme.json, set the source language:"i18n": { "sourceLanguage": "en" }In your templates, use
theme.strings.<key>wherever the text appears:<a class="skip-link" href="#main">{{ theme.strings.skipToContent }}</a>
Each page uses the file that matches its language. A key that is missing from that file is taken from the source language's file.
The file name must match the page's language code exactly, as site.lang shows it. If site.lang is de-DE, the file must be de-DE.json: Paligo doesn't fall back from de-DE to de. Keep each file a flat list of keys and texts, because nested objects are ignored.
site.strings holds a few texts that Paligo translates for you, such as the search box placeholder. See Template variables.
Template variables
Every template has four variables: site for the published site, page for the page being produced, theme for your theme's options and texts, and paligo for markup that Paligo adds. A value that isn't set prints as nothing.
site
Variable | Type | Contents |
|---|---|---|
| text | The layout's Site title, or the publication's title if that is empty. |
| text or null | The layout's Site subtitle. |
| URL or null | The layout's Logo. |
| URL or null | The layout's Logo for dark backgrounds. Null means use |
| URL or null | The layout's Logo link URL. Null means link the logo to the site's front page. |
| true or false | The layout's Open logo link in a new tab. Only matters when |
| URL or null | The layout's Favicon. |
| text or null | The footer copyright line, from the layout's Footer copyright, or from Company name and Copyright year. |
| text | The layout's Site base URL, or empty. |
| text | The language code of the page, such as |
|
| The text direction of the publication's language. Null when it isn't set, which means left-to-right. |
| list | The site's languages. See below. |
| list | The navigation tree. See below. |
| object | The links for a top navigation bar. See below. |
| object or null | The portal page's content. Null if the site has no portal page. See below. |
| object | The search engine. |
| object | Texts that Paligo translates into the page's language: |
| text | The layout's Code colours: |
site.languages: one entry for each language, with:
code: the language code, such asde.label: the language's name in that language, such asDeutsch.default:truefor the site's default language.
site.nav: the pages of the publication as a tree, for a navigation menu. Each entry has:
title: the page title.url: the link to the page. Missing if the entry has no page of its own.children: the entries under it, in the same form.current:truefor the page being produced.ancestor:truefor every entry above the current page.
{% macro tree(items) %}<ul>{% for item in items %}<li{% if item.current %} aria-current="page"{% endif %}>{% if item.url %}<a href="{{ item.url }}">{{ item.title }}</a>{% else %}{{ item.title }}{% endif %}{% if item.children %}{{ _self.tree(item.children) }}{% endif %}</li>{% endfor %}</ul>{% endmacro %}<nav class="sidebar">{{ _self.tree(site.nav) }}</nav>
site.topNav: two lists, either of which can be empty:
items: topics whose section has the roletop-nav-link. Each has atitle, aurlandchildren, a list oftitleandurlfor a dropdown.links: the layout's Top navigation links. Each has atitleand aurl.
Show a top bar only when at least one of the lists has entries.
site.portal: the content for the portal page:
heading: the layout's portal heading, or a translated default.featured: the featured topics, each with atitle,slugandurl.sections: the topics to browse, each with atitle,slugandurl.labels: translated texts for the portal:featuredContent,showMoreandshowLess.startPage: the slug of the topic to start reading from, if there is one.
page
Variable | Type | Contents |
|---|---|---|
| text | The page title. |
| HTML | The page's content. Print it with |
| text |
|
| true or false |
|
| text | The page's file name without |
| text | The page's file name, such as |
| list | The headings on the page, for an on-page table of contents. Each has a |
| list | The pages above this one, starting from the top. Each has a |
| object or null | The previous and next pages, each with a |
| text | A plain-text summary of the page, up to 160 characters. |
| URL | The page's canonical URL, when the layout has Add canonical URL turned on. |
| list | The page's taxonomy values. Each has a |
| text |
|
On the portal and search pages, page.content is empty and the lists are empty.
{% if page.prevPage or page.nextPage %}<nav class="pager">{% if page.prevPage %}<a rel="prev" href="{{ page.prevPage.url }}">{{ site.strings.navPrev }}: {{ page.prevPage.title }}</a>{% endif %}{% if page.nextPage %}<a rel="next" href="{{ page.nextPage.url }}">{{ site.strings.navNext }}: {{ page.nextPage.title }}</a>{% endif %}</nav>{% endif %}
theme
Variable | Contents |
|---|---|
| The value of each theme option for the layout. See Theme options reference. |
| The theme's own texts in the page's language. See Translate a theme. |
paligo
Variable | Contents |
|---|---|
| Markup that must be in every page's |
| Markup that must be at the end of every page's |
Paligo components and helpers
Paligo gives templates ready-made components for common parts of a page, and a few helper functions and filters. A component produces complete, accessible HTML that you style with your own CSS. You can also build the same parts yourself from the Template variables.
Components
Function | Produces | Produces nothing when |
|---|---|---|
| A search box. See Search in a theme. | The layout has no search engine. |
| A breadcrumb trail. | The page is at the top level. |
| A list of the headings on the page. | The page has no headings. |
| Links to the previous and next pages. | There are no previous or next pages. |
| Links to the site in its other languages. | The site has one language. |
<header>{{ paligo_search_box() }}</header><aside>{{ paligo_toc() }}</aside><main>{{ paligo_breadcrumbs() }}{{ page.content }}{{ paligo_pager() }}</main>
The class names to style:
Component | Markup |
|---|---|
|
|
|
|
|
|
|
|
The texts in paligo_pager() and the accessible names of these components are in English. For a site in other languages, build the pager from page.prevPage and page.nextPage with site.strings.navPrev and site.strings.navNext, as shown in Template variables.
Write your own language menu
paligo_language_switcher() produces a plain list. To show the languages in your own way, such as a dropdown in the header, build the menu from site.languages. This example shows the current language's name, and opens to a list of every language:
templates/partials/language-menu.html.twig:
{% if site.languages|length > 1 %}{% set current = site.languages|filter(l => l.code == site.lang)|first %}<details class="language-menu"><summary aria-label="{{ theme.strings.changeLanguage|default('Change language') }}">{{ current.label|default(site.lang|upper) }}</summary><ul>{% for language in site.languages %}<li>{% if language.code == site.lang %}<a href="#" aria-current="true" lang="{{ language.code }}" hreflang="{{ language.code }}">{{ language.label }}</a>{% else %}<a href="../{{ page.translations[language.code]|default(language.code ~ '/') }}" lang="{{ language.code }}" hreflang="{{ language.code }}">{{ language.label }}</a>{% endif %}</li>{% endfor %}</ul></details>{% endif %}
Include it where the menu goes:
{% include 'partials/language-menu.html.twig' %}How the example works:
The menu is shown only when the site has more than one language.
site.languageshas each language'scode, and itslabel, the language's name in that language, such as Deutsch. The current page's language issite.lang.The current language is marked with
aria-current. Thelangattribute lets screen readers say each name in its own language.Each other language links to the same page in that language when Paligo knows it, from
page.translations, and otherwise to that language's front page. This is whatpaligo_language_switcher()does too.
Use <details> and <summary> as in the example, and the menu opens and closes without a script.
Helper functions
asset(path) gives the link to a file in the theme's assets/ folder. See Theme assets.
paligo_contrast_color(background, dark, light) returns whichever of dark and light is easier to read on background, using the WCAG contrast ratio. dark defaults to #161617 and light to #ffffff. Use it to pick a text color that works with a color chosen in a theme option:
<div class="hero" style="background: {{ theme.options.accent }}; color: {{ paligo_contrast_color(theme.options.accent) }}">All three colors must be hex colors, such as #3a46e8. If one isn't, the function returns an empty value.
Filters
excerpt(length) turns HTML into plain text and shortens it to at most length characters, at the end of a word, adding "…" when it shortens it. The default length is 160.
<meta property="og:description" content="{{ page.content|excerpt(200) }}">Search in a theme
The search engine is chosen in the layout's Search settings: Nexus Search, Algolia or Coveo. Your theme provides the search box, and Paligo provides everything else: the index, the scripts that run the search, and a search results page. The theme works with every search engine without changes.
Add the search box
Place paligo_search_box() where the search box goes:
{{ paligo_search_box() }}By default, results drop down under the box as the reader types, with a link to see all results. For the search results page, use paligo_search_box('page'), which lists the results on the page, keeps the search in the page address, and shows taxonomy filters when the layout has them turned on.
{# templates/search.html.twig #}{% extends 'base.html.twig' %}{% block content %}<main><h1>{{ page.title }}</h1>{{ paligo_search_box('page') }}</main>{% endblock %}
When the layout has no search engine, paligo_search_box() produces nothing and no search page is published.
Style the search box
The search box and its results use these classes:
Class | Element |
|---|---|
| The whole search box. |
| The form. |
| The text field. |
| The button that clears the text field. |
| The area that holds the results. |
| The line that says how many results there are. |
| The list of results. |
| One result, its link, title and excerpt. The search words in the excerpt are in |
| Matching headings within a result. |
| The link to all results, in a dropdown. |
| The button that loads more results, on the search page. |
| The taxonomy filters, on the search page. |
The highlighted result has the class is-active.
Write your own search box
You can write the search box's HTML yourself, for example to add your own icon or button, or to put it in a different structure. Paligo's search script finds every element with the class site-search on the page, connects it to the layout's search engine and fills in the results. Your markup only has to use the class names the script looks for.
This example writes the search box once, as a macro, and uses it in two places: as a dropdown in the header of every page, and as a full search on the search results page.
templates/partials/search-box.html.twig:
{% macro box(mode, id, placeholder, strings) %}<div class="site-search" data-search-mode="{{ mode }}"><form class="site-search__form" role="search" method="get" action="search.html"><input class="site-search__input" type="search" name="q" autocomplete="off"placeholder="{{ placeholder }}" aria-label="{{ placeholder }}"{%- if mode == 'dropdown' %} role="combobox" aria-expanded="false" aria-controls="{{ id }}-panel" aria-autocomplete="list"{% endif %}><button type="button" class="site-search__clear" hidden aria-label="{{ strings.clearSearch|default('Clear search') }}">×</button></form><div class="site-search__panel" id="{{ id }}-panel"{% if mode == 'dropdown' %} hidden{% endif %}><p class="site-search__status" role="status" aria-live="polite"></p><ul class="site-search__results"></ul>{% if mode == 'dropdown' %}<a class="site-search__all" hidden></a>{% else %}<button type="button" class="site-search__more" hidden>{{ strings.searchMore|default('Load more results') }}</button>{% endif %}</div>{% if mode == 'page' %}<div class="site-search__facets" hidden></div>{% endif %}</div>{% endmacro %}
The macro takes the mode (dropdown or page), an ID that is unique on the page, the placeholder text and the theme's texts. The ID connects the text field to its results, so that screen readers can announce them.
templates/base.html.twig, with the header search in a block of its own:
{% import 'partials/search-box.html.twig' as search %}<!doctype html><html lang="{{ site.lang }}"{% if site.dir %} dir="{{ site.dir }}"{% endif %}><head><meta charset="utf-8"><title>{% block title %}{{ page.title }} | {{ site.title }}{% endblock %}</title>{{ paligo.head }}</head><body><header>{% block header_search %}{% if site.search.backend|default('none') != 'none' %}{{ search.box('dropdown', 'header-search', site.strings.searchPlaceholder|default('Search'), theme.strings) }}{% endif %}{% endblock %}{% include 'partials/language-menu.html.twig' %}</header>{% block content %}{% endblock %}{{ paligo.foot }}</body></html>
templates/search.html.twig, which empties the header search, because the page has its own:
{% extends 'base.html.twig' %}{% import 'partials/search-box.html.twig' as search %}{% block header_search %}{% endblock %}{% block content %}<main><h1>{{ page.title }}</h1>{{ search.box('page', 'search-page', site.strings.searchPlaceholder|default('Search'), theme.strings) }}</main>{% endblock %}
The search results page uses the address search.html, which is the action of the form. If the reader can't run scripts, the form still opens the search results page.
Paligo checks a theme's search box when the theme is uploaded:
These parts are required, and a missing one is an error: a
<form class="site-search__form">with anaction, asite-search__inputfield namedq, asite-search__paneland asite-search__results.These parts are recommended, and a missing one is a warning:
data-search-modeset todropdownorpage, asite-search__statusand asite-search__clear, asite-search__allin a dropdown, and asite-search__moreandsite-search__facetson the search page.
Translate the search texts
The search texts are in English unless your theme's translation files provide these keys. See Translate a theme.
Key | Text |
|---|---|
| The name of the clear button. |
| The button that loads more results. |
| The link to all results. |
| The name of the search results page. |
| The result count. |
| The heading of the filters. |
| Shown when search can't be loaded. |
| Shown when a search fails. |
Keep page furniture out of the index
Nexus Search indexes the whole page. To leave a part of the page out of the index, such as a navigation menu or a footer, add data-pagefind-ignore to it:
<nav class="sidebar" data-pagefind-ignore>…</nav>What theme templates can use
Templates run in a restricted version of Twig. They can use the features in this topic and nothing else. Using anything else is an error when the theme is checked, even in a part of a template that never runs.
Tags: if, for, set, block, extends, use, include, embed, apply, macro, import, from
Filters: escape, e, upper, lower, capitalize, trim, date, length, slice, join, split, default, merge, sort, first, last, map, filter, keys, json_encode, number_format, replace, striptags, url_encode, excerpt, content_sections
Functions: asset, include, min, max, parent, block, paligo_search_box, paligo_search_results, paligo_breadcrumbs, paligo_toc, paligo_pager, paligo_language_switcher, paligo_contrast_color, related_pages
Tests: all of Twig's tests except constant, such as defined, empty, null, iterable, even, odd, same as and divisible by
Not available, among others:
The
rawfilter. The HTML that Paligo provides already prints as HTML.range()and the..operator. Loop over a list instead.attribute(). Use brackets, such asitem[key].template_from_string(),constant(),random(),cycle(),dump()andsource().The
with,autoescape,do,spacelessandsandboxtags.Calling methods or reading properties of objects. Every value in a template is a text, number, list or map.
For how Twig works, see the Twig documentation.
Theme validation messages
Paligo checks a theme twice when you upload it. The first check is done while the file uploads: if it fails, the upload is refused and nothing changes. The second check renders every template with sample content, and its result is shown in the Layout Editor as Theme validation passed or Theme validation failed.
When uploading
Message | What to do |
|---|---|
The theme ZIP is too large (…); the limit is … | Make the theme smaller than 20 MB, such as by compressing images. |
The theme ZIP has too many files (…); the limit is … | A theme can have at most 5,000 files and folders. |
The theme is too large uncompressed (…); the limit is … | The files can take up at most 100 MB unzipped. |
The uploaded file is not a valid ZIP archive. | Compress the theme again as a ZIP file. |
The theme's files are not at the top of the ZIP (they are inside "…"). | Compress the contents of the theme folder, not the folder. See Theme package structure. |
Required file is missing from the theme: … | The theme must have |
File type not allowed in a theme (rejected): … | Remove the file. See the allowed file types in Theme package structure. |
Symlinks are not allowed in a theme (rejected): … | Replace the symbolic link with the file itself. |
Unsafe path in ZIP (rejected): … | A file's path starts with |
Duplicate entry in ZIP (rejected): … | The ZIP file contains the same path twice. Compress the theme again. |
theme.json is not valid JSON. | Correct the syntax of |
theme.json is missing a "name". (or "version", or "contract" version range) | Add the field. See The theme manifest. |
These are warnings, and the upload still succeeds:
Message | Means |
|---|---|
The theme's files are inside a folder in the ZIP ("…"); it was ignored. | The ZIP file had a folder around the theme. Paligo used its contents. |
theme.json "version" ("…") is not a semver version. | Use a version number such as |
theme.json "author" is ignored (not a string). | Write the author as text. |
When checking the templates
Each message names the template and the line, where there is one.
Message | What to do |
|---|---|
Theme "…" requires contract "…", but this renderer implements contract 1.0. | Set |
Theme is missing the required template "page.html.twig" | Add |
Function "…" is not allowed. (or Filter, or Tag) | The template uses something themes can't use. See What theme templates can use. |
Unknown "…" function. (or filter, or tag) | The name is misspelled, or it isn't part of Twig. |
Unexpected token, Unclosed "…", and other syntax errors | Correct the Twig syntax on the line given. |
Template "…" exceeded the per-template render time limit … | The template took too long. This is almost always a loop that never ends, or a template that includes itself. |
Theme template nesting exceeded the maximum depth of 128. | A template includes or embeds itself, or a macro calls itself without stopping. |
The search box's markup messages, such as a missing | See Search in a theme. |
The check uses sample content, so a template can still fail with real content, for example on a value that the sample doesn't have. Preview the theme on your own content with Paligo Theme Studio before you publish with it.