# Customize an oCMS theme

*Published: 2026-11-02*

A custom oCMS theme is a directory of HTML templates, JSON configuration and static assets. This guide adds navigation, an editable color and a footer widget area to your own theme. It uses the identifier `workshop`; keep that identifier consistent in directory names and asset URLs.

Run a local site first using [Download](/download) and the [configuration guide](/configuration). The [first-site walkthrough](/build-your-first-ocms-site) covers a small HTML/CSS change; the [gallery](/themes) offers ready-to-use designs. Work in your own custom theme and keep a backup before replacing an existing design.

## 1. Keep the complete theme structure

The example directory is `custom/themes/workshop/`. An HTML theme needs a base layout and all eight page templates, including routes that are easy to overlook during a homepage redesign:

```
workshop/
  theme.json
  templates/
    layouts/base.html
    pages/home.html
    pages/page.html
    pages/list.html
    pages/category.html
    pages/tag.html
    pages/search.html
    pages/404.html
    pages/form.html
    partials/header.html
    partials/footer.html
  static/css/theme.css
```

Each page template defines `{{define "content"}}` and closes it with `{{end}}`. The base layout includes `{{template "content" .}}` between the shared header and footer. Keep existing page data and form controls when extending a working theme; a homepage-only template is not a complete site.

## 2. Declare settings and a widget area

Merge these declarations into your theme's `theme.json`. The template mapping belongs to the same directory tree shown above:

```
{
  "name": "Workshop",
  "version": "1.0.0",
  "engine": "html",
  "description": "A local theme customization exercise",
  "templates": {
    "home": "pages/home.html",
    "page": "pages/page.html",
    "list": "pages/list.html",
    "category": "pages/category.html",
    "tag": "pages/tag.html",
    "search": "pages/search.html",
    "404": "pages/404.html",
    "form": "pages/form.html"
  },
  "settings": [
    {
      "key": "accent_color",
      "label": "Accent color",
      "type": "color",
      "default": "#1a6fb5"
    }
  ],
  "widget_areas": [
    {
      "id": "footer-1",
      "name": "Footer links",
      "description": "Supporting links below the page content"
    }
  ]
}
```

Keep the directory identifier `workshop` even if you later change the display name. Theme imports use the directory name as their identifier. Restart the local process after filesystem changes so that configuration and templates are reloaded.

## 3. Render the configured menu

In Admin → Menus, use a menu with slug `main` for header navigation. Add published pages or custom links and save the order. A theme receives those items in `.Navigation`. For example, put this inside your header partial:

```
<nav aria-label="Main navigation">
  <ul>
    {{range .Navigation}}
    <li>
      <a href="{{.URL}}"{{if .IsActive}} aria-current="page"{{end}}>{{.Title}}</a>
      {{if .Children}}
      <ul>
        {{range .Children}}
        <li><a href="{{.URL}}">{{.Title}}</a></li>
        {{end}}
      </ul>
      {{end}}
    </li>
    {{end}}
  </ul>
</nav>
```

This example keeps child links visible, so it does not need a JavaScript dropdown. Style the lists for your layout and check keyboard focus. A footer menu with slug `footer` is available separately through `.FooterNav`. Menus can have language-specific versions; preserve the URLs supplied by oCMS rather than constructing translated page URLs from titles.

## 4. Use the editable accent color

Open Admin → Themes → Settings for the theme and save an accent color. Templates receive saved values through `.ThemeSettings`. In the base layout's head, use the color with a fallback:

```
<style nonce="{{.CSPNonce}}">
  :root { --workshop-accent: {{or (index .ThemeSettings "accent_color") "#1a6fb5"}}; }
</style>
```

Use the variable in `static/css/theme.css`:

```
a { color: var(--workshop-accent, #1a6fb5); }
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--workshop-accent, #1a6fb5);
  outline-offset: 4px;
}
```

The nonce associates the inline style with the site's Content Security Policy. Keep ordinary text and attribute values in Go's normal escaping contexts. An editable color still needs a contrast check against the theme's backgrounds.

## 5. Render the footer widget area

Create and activate a widget in the theme's **Footer links** area. Its area identifier must match `footer-1` in `theme.json`. Render the area from the footer partial:

```
<footer>
  {{range (index .Widgets "footer-1")}}
  <section>
    {{if .Title}}<h2>{{.Title}}</h2>{{end}}
    {{.Content}}
  </section>
  {{end}}
</footer>
```

If the area has no active widgets, the loop emits nothing. Confirm that the widget belongs to this theme, area and language before changing the template. Keep widget heading levels appropriate to the surrounding page.

## 6. Version assets and test the complete site

For HTML themes using the existing asset helper, link the stylesheet from the base layout like this:

```
<link rel="stylesheet" href="{{scriptURL "/themes/workshop/static/css/theme.css"}}">
```

`scriptURL` adds the process's asset version. Restart after deploying template or asset changes, then confirm that the rendered URL's version changed and that its response contains the new CSS. Use the same helper for the theme's JavaScript. A theme using its own versioned URLs needs to update those versions when assets change.

Preserve the integration hooks in a complete layout: `privacyHead` before `analyticsExtHead`, the body hooks `analyticsExtBody` and `informerBar`, and `embedHead`/`embedBody` where supported. Pass the supplied CSP nonce to hooks that require it. The [theme reference (GitHub Wiki)](https://github.com/olegiv/ocms-go/wiki/Theme-System) lists their full signatures.

1. Activate the theme locally. If `OCMS_ACTIVE_THEME` is set, it overrides the saved admin selection at startup; see [configuration precedence](/configuration#paths).
2. Check a homepage, Page, Post, blog list, category, tag, search, form and nonexistent URL. Check empty lists as well as populated ones.
3. Change and save the color, then reload. Add a footer widget and confirm its content appears only in the intended area.
4. Use the keyboard and a narrow viewport. Check focus, menu links, image crops, long code examples and optional JavaScript fallbacks.
5. Keep the theme in version control and follow [backup and restore](/backup-and-restore) before production changes. Theme file updates do not require rebuilding the Go application.

---

[Source](https://ocms.tech/theme-customization)
