Creating templates for plym
plym supports custom templates which are easy to build, and are fully customizable. Read this guide to start creating templates for your plym blog.
1. Overview
A plym template is a directory of Jinja2 and CSS files used to produce HTML for the blog index and individual posts.
Templates in plym are strictly body fragments. You only design what goes inside the <body> tag. Plym automatically generates the document skeleton (<!DOCTYPE>, <html>, <head>, <body>), SEO meta tags, CSS bundling, webfont fetching, and asset injection.
2. File Layout
Template directories must use lowercase kebab-case naming and be placed in plym/templates/.
plym/templates/<template-name>/
├── index.html
├── post.html
├── template.yaml
└── css/
└── base.css
| File / Directory | Status | Purpose |
|---|---|---|
index.html |
Required | Body fragment for the blog index. |
post.html |
Required | Body fragment for a single post. |
css/ |
Required | Directory for stylesheet files. All *.css files are concatenated in alphabetical order. |
template.yaml |
Optional | Design defaults shipped with the template (fonts, colors, Prism theme). |
3. Context Variables
Plym passes specific context variables to your Jinja templates. Below are the available objects and their properties.
Global Context (site)
Available in all templates.
| Variable | Type | Nullable | Description |
|---|---|---|---|
site.name |
String | No | The name of the site. |
site.website |
String | No | The marketing domain URL. |
site.blog_home |
String | No | Public URL where plym is mounted. |
site.blog_prefix |
String | No | Normalized prefix, either /blog or empty. |
site.language |
String | No | ISO language code. |
site.pagination.page_size |
Integer | No | Number of posts per index page. |
site.colors.<color> |
String | No | Hex values for primary, secondary, accent, background. |
site.favicon |
String | Yes | URL to the favicon. |
site.logo |
String | Yes | URL to the logo image. |
site.public_blog_url() |
Method | No | Returns the full {scheme}://{blog_home} URL. |
Index Context (posts)
Available only in index.html. Represents a list of posts for the current page.
| Variable | Type | Nullable | Description |
|---|---|---|---|
post.title |
String | No | Title of the post. |
post.slug |
String | No | URL-friendly identifier. |
post.excerpt |
String | Yes | Short summary. |
post.cover |
String | Yes | URL to the cover image. |
post.author.display_name |
String | No | Name of the writer. |
post.author.avatar_url |
String | Yes | URL to the author's avatar. |
post.published_at |
Datetime | Yes | Publish date. Format with strftime. |
post.reading_time |
Integer | No | Estimated reading time in minutes. |
post.tags |
List | No | List of objects containing id, name, and slug. |
Post Context (post)
Available only in post.html. Contains all variables from the Index Context, plus the following:
| Variable | Type | Nullable | Description |
|---|---|---|---|
post.content |
String | No | Rendered HTML of the post body. Must be emitted with the safe filter. |
post.toc |
List | No | Recursive list of objects (level, id, name, children) for table of contents. |
4. Design Defaults (template.yaml)
Templates can declare baseline design tokens. These serve as defaults and can be overridden by the operator's main config.yaml. Only the fields below are permitted.
fonts:
heading: Inter
body: Merriweather
colors:
primary: "#1a1a1a"
secondary: "#6b6b6b"
accent: "#E9793A"
background: "#ffffff"
prism:
theme: tomorrow
5. CSS and Styling
Do not hardcode hex values or font families in your CSS. Use the CSS variables automatically generated and injected by plym. Plym adds specific classes to the body wrapper (plym-index or plym-post) so you can scope your styles.
| CSS Variable | Maps To | Example Fallback Usage |
|---|---|---|
var(--color-primary) |
config.colors.primary |
color: var(--color-primary); |
var(--color-secondary) |
config.colors.secondary |
color: var(--color-secondary); |
var(--color-accent) |
config.colors.accent |
color: var(--color-accent); |
var(--color-background) |
config.colors.background |
background: var(--color-background); |
var(--font-heading) |
config.fonts.heading |
font-family: var(--font-heading), sans-serif; |
var(--font-body) |
config.fonts.body |
font-family: var(--font-body), serif; |
Available Font Weights
Plym restricts downloaded Google Fonts for performance.
| Font Slot | Available Weights | Italics Allowed |
|---|---|---|
| Heading | 600, 900 | No |
| Body | 400 | No |
Note: Any other weight, or italic styles, will be synthesized by the browser (faux bold / faux italic).
6. Markdown Target Elements
Your CSS must account for the HTML output generated by plym's markdown renderer.
| Markdown Element | HTML Output Example |
|---|---|
| Headings (H2-H4) | <h2 id="slug"><a class="toclink" href="#slug">Text</a></h2> |
| Fenced Code | <pre><code class="language-python">...</code></pre> |
| Inline Code | <code>...</code> |
| Images | <img src="url" alt="alt" loading="lazy" decoding="async"> |
| Task Lists | <li class="task-list-item"><input type="checkbox" disabled checked>...</li> |
| Strikethrough | <del>strike</del> |
| Footnotes | <sup id="fnref:1"><a href="#fn:1">...</a></sup> |
7. Minimum Viable Template
Below is a functional starting point for a template.
index.html
<header><a href="/"><h1>{{ site.name }}</h1></a></header>
<main>
{% for post in posts %}
<article>
<h2><a href="{{ site.blog_prefix }}/{{ post.slug }}">{{ post.title }}</a></h2>
{% if post.excerpt %}<p>{{ post.excerpt }}</p>{% endif %}
<small>
{{ post.author.display_name }}
{% if post.published_at %} · {{ post.published_at.strftime('%b %d, %Y') }}{% endif %}
</small>
</article>
{% else %}
<p>Nothing here yet.</p>
{% endfor %}
</main>
post.html
<header><a href="/">{{ site.name }}</a></header>
<article>
<h1>{{ post.title }}</h1>
{% if post.cover %}<img src="{{ post.cover }}" alt="">{% endif %}
{{ post.content | safe }}
</article>
css/base.css
body {
max-width: 720px;
margin: 0 auto;
padding: 1rem;
color: var(--color-primary);
background: var(--color-background);
}
a { color: var(--color-accent); }
img { max-width: 100%; height: auto; }
pre { overflow-x: auto; padding: 1rem; }
5. Performance and Accessibility Budgets
Templates submitted to the main plym repository must meet the following strict budgets.
Performance Targets
| Metric | Threshold |
|---|---|
| Lighthouse Performance | ≥ 95 |
| LCP (Simulated 3G) | < 1.5 s |
| Minified CSS Total | < 30 KB |
Inlined <style> block |
< 50 KB |
| External Requests | 0 (No external CSS/JS/Fonts allowed) |
Accessibility Rules
| Requirement | Detail |
|---|---|
| Interactivity | All interactive elements must be keyboard reachable with visible focus rings. |
| ARIA Labels | Required on icon-only buttons or links. |
| Contrast | WCAG AA contrast (≥ 4.5:1) for body text against background. |
| Motion | Must respect prefers-reduced-motion: reduce. |
6. Anti-Patterns
Avoid the following practices; they might cause validation errors.
- Writing
<html>,<head>, or<meta>tags in template files. - Requesting external assets (CDNs, JS frameworks, font files) via
<link>or<script>. - Rendering
post.contentwithout thesafepipeline filter. - Assuming nullable variables (e.g.,
post.cover,site.favicon) will always be present without{% if %}guards.
← Index