What we learned turning a Ghost theme into a product

Dark mode as tokens, translating every string, 18 Admin settings and a one-line CSS bug: what shipping Vanta as a paid Ghost theme taught us.

Vanta post in dark mode showing highlighted Rust and Go code blocks with copy buttons
Did you enjoy this article? Share it:

A theme that runs your own blog and a theme strangers pay for are different products. Building Vanta, the Ghost theme this site runs on, the design turned out to be the smaller part of the work. The work that mattered most was unglamorous: dark mode built on tokens, every string translatable, every option in Ghost Admin, and a CSS bug that survived two releases.

Dark mode is a token problem, not a filter

The quickest dark mode is a filter or a second stylesheet. Both break the moment a publisher changes the accent color or drops in a screenshot. Vanta instead declares every surface and text color as a CSS custom property, then redefines only those properties per mode:

html[data-theme="dark"],
html:not([data-theme="light"]) {
    --color-bg-site: #090d16;
    --color-text-primary: #f8fafc;
    --color-text-secondary: #94a3b8;
}

html[data-theme="light"] {
    --color-bg-site: #ffffff;
    --color-text-primary: #0f172a;
    --color-text-secondary: #475569;
    --color-accent-cyan-text: #0e7490;
}

Components never mention dark or light; they only use var(--color-text-secondary). The last line shows why that matters. Our cyan accent passes contrast on a dark background but not on white, so light mode swaps in a darker cyan for text only, and no component had to change. If you want the longer version of this idea, we wrote about three-tier design tokens separately.

The other half is timing. A tiny inline script in the <head> picks the mode before the first paint, so nobody sees a flash of the wrong colors. A choice made with the toggle wins and is stored in the browser. Without one, Vanta follows the reader's system setting.

Translating a theme means finding every string

Ghost themes translate through the {{t}} helper and JSON locale files. Wrapping the visible text is the easy part. Vanta 1.0.2 shipped with the interface translated into 13 languages, and we still found 20 English strings in 1.0.3. They were all invisible: aria-label attributes on the reading tools and share buttons, and the title tooltips of the image lightbox. A screen-reader user on a Spanish site would have heard English.

What found them was a boring search for labels that start with a capital letter and are not wrapped in {{t}}:

grep -rn 'aria-label="[A-Z]' partials/*.hbs *.hbs

Two habits keep this manageable. Source translations live in one folder and the build merges them with Ghost's shared theme translations, so nobody edits generated files by hand. And every new label goes through {{t}} from the first commit, not in a cleanup pass.

Every option belongs in Ghost Admin

A buyer who has to edit a .hbs file to change the homepage headline will write to support, or ask for a refund. Vanta exposes 18 settings in Ghost Admin: hero text, feed layout, newsletter copy, the theme toggle, the dark-mode logo and each reading tool. Ghost allows a theme 20 custom settings, so the limit forces choices. We kept switches for things publishers turn on or off, and left fine-grained styling to Code Injection.

The one-line CSS bug that survived two releases

This is the one we are least proud of. The main content wrapper used a padding shorthand:

.site-main {
    flex-grow: 1;
    padding: 40px 0 80px;
}

That 0 also zeroed the horizontal gutter another class was supposed to provide. On a desktop you never notice, because the content sits in a centered column. On a phone, text ran edge to edge on every post and page. It shipped in 1.0.1 and 1.0.2 and we only caught it while taking 390-pixel-wide screenshots for a marketplace listing. The fix sets only what that rule owns:

.site-main {
    flex-grow: 1;
    padding-top: 40px;
    padding-bottom: 80px;
}

The lesson is not about CSS shorthands. Look at real content at phone width before every release, not just the homepage.

The unglamorous part: license, credits and docs

The theme zip first went out with the MIT license file inherited from the repository it was forked from. That file said buyers could redistribute the theme, while our terms said they could not. Version 1.0.3 fixed it: a commercial license that matches the terms, a CREDITS.md that keeps the notices of the open-source scripts we bundle, and documentation inside the download and online. None of it shows in a screenshot, and all of it is part of the product.

If you want to see how this plays out, you are reading it: this blog runs Vanta, the first theme from PlanetThemes. The theme costs $69 one-time at planethemes.com/vanta and includes 6 months of email support and free updates for the whole 1.x series. The documentation is public.

Frequently asked questions

Does Vanta follow the reader's system dark mode setting?

Yes. On a first visit Vanta follows the reader's system light or dark setting. A choice made with the toggle in the header takes priority and is remembered in the browser.

How many languages does Vanta support?

The interface is translated into 13 languages, including Spanish, German, French, Brazilian Portuguese and Chinese. Vanta uses the language set in Ghost's general settings.

Can I change Vanta's settings without editing code?

Yes. Vanta has 18 settings in Ghost Admin under Settings, Design and branding, Customize. They cover the hero, feed layout, newsletter copy, theme toggle and reading tools.

What does Vanta cost?

Vanta costs $69 one-time. It includes 6 months of email support and free updates for the whole 1.x series.

Did you enjoy this article? Share it:
P

Planet Themes

Verified Author

Writer and contributor at Vanta Tech & Creator.

NEWSLETTER

Subscribe to our newsletter

Get the latest posts delivered straight to your inbox.

Great! Check your inbox to confirm your subscription.

Member discussion

0 comments

Start the conversation

Become a member of Vanta Tech & Creator to start commenting.

100%