Theming Guide

Learn how to create custom themes for your Accent CMS site.

Styling for Markdown-Based Content

Since Accent CMS renders markdown to HTML, the best CSS frameworks are those that style semantic HTML elements directly. Classless CSS frameworks work exceptionally well because they require no special markup - your markdown automatically looks great.

Key considerations:

  • Semantic HTML elements (<article>, <header>, <nav>, <main>, <footer>) are styled automatically
  • Typography is handled out of the box - headings, paragraphs, lists, blockquotes
  • Code blocks with syntax highlighting work with most frameworks
  • Tables and forms render properly without custom classes
  • Dark mode support via prefers-color-scheme or data attributes

Avoid frameworks that require heavy class annotations (like Tailwind or Bootstrap utilities) since markdown output cannot include arbitrary classes.

Available Themes

accent init installs the simple starter theme (and the default theme powers the documentation site). Starter templates with ready-made themes are available via accent init --list.

ThemeFrameworkBest For
simpleSimple.cssClean, classless styling with dark mode (installed by accent init)
defaultCustom CSSStarting point, minimal styling (powers the docs site)

Beyond the bundled themes, published themes install straight from the ecosystem registry – signature- and checksum-verified:

accent theme search dark       # find a theme by name, description, or tag
accent theme list --remote     # or browse everything the registry publishes
accent theme install agentic   # install one
accent theme outdated          # see which installed themes are behind
accent theme update agentic    # move one to the latest version

Installing does not activate: set theme.name in config.yaml to switch to it.

A theme is yours to edit once installed – which is exactly why accent theme update will not silently overwrite your work. If any installed file was modified, added, or removed since install, the update refuses and lists each difference; re-run with --force to discard the listed changes, or copy them somewhere safe first. See The Hub for the full theme, plugin, and template distribution story, including how retracted versions are handled.

Simple and Pico themes use classless CSS frameworks that style HTML elements directly. Your markdown content renders beautifully without any template modifications:

  • Automatic dark/light mode based on system preference
  • Responsive typography and spacing
  • Styled tables, code blocks, and forms
  • Minimal CSS footprint

Traditional Frameworks

The Cinder theme demonstrates using a class-based framework (Bootstrap). This approach requires more template customization but offers more control over component styling.

Lightweight Grid Frameworks

The Skeleton theme uses a minimal CSS boilerplate (~400 lines) that provides:

  • 12-column responsive grid system
  • Base typography for markdown elements
  • Styled buttons, forms, tables, and code blocks
  • Raleway font for clean typography

How Skeleton maps to markdown:

MarkdownHTML OutputSkeleton Styling
# Heading<h1>4-5rem, weight 300, letter-spacing
**bold**<strong>Inherited
[link](url)<a>Cyan color (#1EAEDB)
`code`<code>Gray background, rounded corners
```code<pre><code>Block display, padding
- list<ul><li>Circle markers, proper spacing
> quote<blockquote>2.5rem margin
`table`

Using the grid in templates:

Skeleton’s grid is available for custom layouts in templates:

<div class="row">
    <div class="eight columns">
        {{ page.content | safe }}
    </div>
    <div class="four columns">
        <aside>Sidebar content</aside>
    </div>
</div>

Grid classes: .one.column through .twelve.columns, plus .one-half, .one-third, .two-thirds.

Theme Structure

A theme consists of templates, assets, and a configuration file:

site/themes/my-theme/
├── theme.yaml           # Theme configuration
├── templates/           # Jinja2 templates
│   ├── base.html.jinja
│   ├── default.html.jinja
│   └── blog.html.jinja
└── assets/              # Static files
    ├── css/
    │   └── style.css
    ├── js/
    │   └── main.js
    └── images/

Theme Configuration

The theme.yaml file defines your theme:

name: My Theme
description: A custom theme for my site
author: Your Name
version: 1.0.0

assets:
  css:
    - css/style.css
  js:
    - js/main.js

Templates

Accent CMS uses MiniJinja, a Jinja2-compatible template engine.

Base Template

Create templates/base.html.jinja:

<!DOCTYPE html>
<html lang="{{ site.language }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}{{ page.title }} - {{ site.name }}{% endblock %}</title>
    {% for css in theme.assets.css %}
    <link rel="stylesheet" href="/theme/assets/{{ css }}">
    {% endfor %}
</head>
<body>
    <header>
        <nav>
            <a href="/" class="site-title">{{ site.name }}</a>
            <ul>
            {% for p in pages %}
                {% if p.menu.visible %}
                <li><a href="{{ p.url }}">{{ p.title }}</a></li>
                {% endif %}
            {% endfor %}
            </ul>
        </nav>
    </header>

    <main>
        {% block content %}{% endblock %}
    </main>

    <footer>
        <p>&copy; {{ site.name }}</p>
    </footer>

    {% for js in theme.assets.js %}
    <script src="/theme/assets/{{ js }}"></script>
    {% endfor %}
</body>
</html>

Page Template

Create templates/default.html.jinja:

{% extends "base.html.jinja" %}

{% block content %}
<article>
    <h1>{{ page.title }}</h1>
    {% if page.date %}
    <time>{{ page.date }}</time>
    {% endif %}

    <div class="content">
        {{ page.content | safe }}
    </div>

    {% if page.tags %}
    <div class="tags">
        {% for tag in page.tags %}
        <span class="tag">{{ tag }}</span>
        {% endfor %}
    </div>
    {% endif %}
</article>
{% endblock %}

Available Template Variables

Site Variables

VariableDescription
site.nameSite name from config
site.descriptionSite description
site.languageSite language code
site.main_siteOptional main-site link block from config; none when unset
site.main_site.urlMain-site URL the brand wordmark links to (non-empty; enforced at config load)
site.main_site.hostDisplay host derived from url (scheme, userinfo, port, and path stripped)
site.main_site.labelWordmark text; falls back to site.name only when unset – an explicit empty string is honored
site.main_site.badge_labelBack-to-home chip text; defaults to "Docs" when unset
site.main_site.cta_labelCall-to-action button text (optional)
site.main_site.cta_urlCall-to-action target URL (optional)

Linking Back to a Main Site

A docs (or other satellite) site often fronts a separate product or marketing site. Declaring that relationship in config.yaml lets the theme render navigation back to it:

site:
  main_site:
    url: https://example.com
    label: Example              # optional; defaults to site.name
    badge_label: Docs           # optional; text of the back-to-home chip
    cta_label: Get a license    # optional; CTA renders only with both cta_* keys
    cta_url: https://example.com/pricing

The bundled default theme reacts to the block in three places: the header wordmark links to main_site.url while a small chip (badge_label, default “Docs”) next to it keeps the way back to the site’s own home page, an accent-colored button in the header links cta_label to cta_url, and the footer carries main_site.host – the host name derived from the URL. Without the block, none of this renders and the wordmark links to the site’s own home page. In your own templates, gate on the block the same way:

{% if site.main_site %}
<a href="{{ site.main_site.url }}">{{ site.main_site.host }}</a>
{% endif %}

Page Variables

VariableDescription
page.titlePage title
page.contentRendered HTML content
page.datePublication date
page.authorAuthor name
page.leadShort summary; falls back to the first paragraph when no lead: is set
page.tagsList of tags
page.urlPage URL – what the router keys on. Use it for links within the site and for comparing against the current page
page.canonical_urlThe URL to advertise this page under: use it for <link rel="canonical">, og:url, and anything else naming the page to the outside world. Same as page.url for almost every page

The two differ for one page. A home page in a home/ directory is reachable at both /home and the site root, because accent serve resolves the root through it and accent build writes the root index.html from it. Both return 200 with the same content, so the page needs a single URL to be advertised under, and that is the root. Point your canonical tag at page.canonical_url and the tag will agree with what sitemap.xml lists; point it at page.url and the two will name different URLs for one page, which is worse than either on its own – a search engine then has two contradictory claims and picks between them itself.

Theme Variables

VariableDescription
theme.nameTheme name
theme.assets.cssList of CSS files
theme.assets.jsList of JS files
VariableDescription
pagesAll visible pages for navigation

Taxonomy (Tags)

VariableDescription
taxonomy.tagsAll tags with name and count, sorted alphabetically
taxonomy.current_tagCurrent tag name (on /tags/{tag} pages only)
taxonomy.pagesPages for current tag (on /tags/{tag} pages only)

Tag index and per-tag listing pages are served at /tags and /tags/{tag}. Create tags.html.jinja and tag.html.jinja templates to customize their appearance. See the Templating Guide for full examples.

Built-in Styling Pipeline

Accent CMS includes a built-in CSS processing pipeline that handles Sass/SCSS compilation, Tailwind-style utility CSS, and production minification – no Node.js required. Configure it in your theme.yaml:

styling:
  sass:
    enabled: true
  utilities:
    enabled: true
    scan:
      - templates/**/*.jinja
  processing:
    minify: true

See the Styling Guide for full documentation.

Styling Tips

  1. Mobile-first - Start with mobile styles, then add breakpoints
  2. CSS Variables - Use custom properties for easy theming
  3. Semantic HTML - Use proper elements for accessibility
  4. Minimize dependencies - Keep your CSS lean and fast

Activating Your Theme

Set your theme in config.yaml:

theme:
  name: my-theme

Then restart the server to see your changes.