For developers
A brief appendix for self-builders — Enova's build commands, tech stack, file map, the 62-locale i18n workflow, and the stable data-* hook contract.
This page is the short reference for developers customizing Enova from source. Most site editors won't need anything here — uploading the pre-built enova.zip is enough. If you want to fork the theme, change styles at the source level, or rebuild after edits, read on.
Custom code is overwritten on theme update
If you modify theme files directly and then upload an updated enova.zip through Ghost Admin, your changes are erased. Fork the repo, commit your changes to your fork, and rebuild your own zip — don't edit files in place on a production install. For small CSS or JavaScript tweaks, prefer Ghost's Settings → Code injection, which survives theme updates — see Code injection for the stable selectors Enova exposes for exactly this.
Build requirements
- Node.js v22.12.0 or later
- Yarn (any recent v1.x)
- Ghost v6.0.0 or later for the running site that will host the built theme
Tech stack
Enova is built with Vite (bundling), Tailwind CSS v4 (styling, with the typography plugin for article content), and Alpine.js (interactivity — sidebars, preferences, the mobile menu). Templates are standard Ghost Handlebars.
Build commands
All commands run from the theme root.
| Command | What it does |
|---|---|
yarn install | Install dependencies. |
yarn dev | Vite dev server with HMR. Useful when iterating on CSS or JS in a local Ghost install pointed at the theme directory. |
yarn build | Production build to assets/built/. |
yarn lint | Runs lint:i18n (translation-parity check) then lint:theme (GScan compliance against Ghost's theme-API expectations). |
yarn test | Alias for yarn lint. |
yarn zip | Run test + build, then package enova.zip for upload. Excludes node_modules, .git, demo templates, scripts, and other source-only files. |
File map
The repo's root templates and key partials at a glance:
Templates (root)
| File | Purpose |
|---|---|
default.hbs | Master layout — <html> state attributes (data-color-scheme, data-navigation-style), the two sidebar shells, header/footer includes. |
index.hbs | Homepage: hero (four layouts) + tabbed home feed. |
post.hbs / page.hbs | Article and static-page templates. |
tag.hbs, author.hbs | Tag / author archives with cover headers. |
newsletter-archive.hbs | The /newsletters/ collection template (requires routes.yaml, below). |
custom-full-width.hbs, custom-left-sidebar.hbs, custom-right-sidebar.hbs | The three selectable custom templates. |
error.hbs, error-404.hbs | Error pages. |
Key partials
| Partial | Purpose |
|---|---|
partials/header.hbs | Sticky header: masthead, logo (light/dark), auth links, preferences. |
partials/sidebar-left.hbs / partials/sidebar-right.hbs | The two sidebars; the right one hosts the whole widget stack. |
partials/sidebar-donate.hbs, partials/sidebar-sponsors.hbs, partials/sidebar-house-ad.hbs | The presence-gated monetization widgets (driven by pages slugged donate / sponsors / house-ad). |
partials/hero-*.hbs | The four homepage hero layouts. |
partials/home-feed.hbs | Tabbed homepage feed (tag tabs from the home_feed_tags setting). |
partials/post-card.hbs | Reusable feed card, grid and list variants. |
partials/content-post.hbs + partials/post/ | Post hero dispatch (#video / split / cinematic / default) and post-page pieces. |
partials/newsletter-archive/ | The five archive card layouts, hero, and year rail. |
partials/footer.hbs | Footer brand, nav columns, newsletter form. |
Assets
| Path | Purpose |
|---|---|
assets/css/index.css | Tailwind v4 entry, design tokens, typography. Feature styles split into cards.css, sidebar.css, video.css, donate.css, sponsors.css, dropcap.css, code.css. |
assets/js/index.js | Alpine.js entry — stores, sidebar state, bookmarks, preferences. |
assets/js/navigation.js, header-nav.js | Sidebar nav build (dash-prefixed sub-items, # group headers) and top-bar mode. |
assets/js/video.js | The #video hero + docked mini-player (YouTube, Vimeo, uploaded video). |
assets/js/social-links.js, nav-icons.js | The meta-tag-driven social links and nav-icon systems. |
vite.config.js | Vite build config. |
routes.yaml
Enova ships a routes.yaml at the repo root (and inside enova.zip). It defines the two collections — / (everything except the newsletter tag) and /newsletters/ (only the newsletter tag) — plus the standard tag/author taxonomies. If you fork and change routing, keep the docs' copy in mind: the file is published verbatim in the installation guide.
i18n workflow
Enova ships 62 locales in locales/, and the yarn lint:i18n parity check is enforced — never add a {{t "..."}} call to a template without also adding the matching key to locales/en.json.
The mechanics
scripts/check-i18n.mjswalks every*.hbsfile in the theme.- It extracts every
{{t "..."}}and subexpression(t "...")call. - It diffs the extracted key set against
locales/en.json. - It fails the build on any drift — missing key, orphan key, mismatched value.
The discipline
- Add a translation key first, then reference it in the template.
- Use Ghost's interpolation syntax (
{{t "Members of {site}" [email protected]}}) instead of string concatenation, so word order is correct in non-English locales. - Never hardcode user-visible English in a template.
See also: Publication Language for the reader-facing side of the same system, including the full locale list and RTL support.
The data-* hook contract
Enova's templates carry a documented styling API — the data-region / data-component / data-variant / data-part attributes. If you fork the theme, treat these as public contract: site owners build Code-injection CSS against them, and renaming or removing one breaks that CSS. Add new hooks freely (and document them), but keep existing names stable. The full vocabulary: Hook reference.