← Index

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.content without the safe pipeline filter.
  • Assuming nullable variables (e.g., post.cover, site.favicon) will always be present without {% if %} guards.

Written by

Administrator