Presenting my custom Ghost Theme
I recently built a custom Ghost theme for this site called Kiwi. It started as a fork of Ghost's official Dawn theme, borrowed some ideas from Taste, and then grew into something of its own. Here's what changed and why.
The source is on GitHub: lor-enz/kiwi-ghost-theme
The base: Dawn + Taste
Dawn is Ghost's clean, minimal publication theme. It has solid bones — good typography, a sensible layout system, a featured posts carousel — but it's built around a publication rather than a personal site. The hero section shows your site description and Subscribe/Login buttons.
Taste is Ghost's recipe-oriented theme. It takes a different approach to the hero: it shows a Title and short description front and center.
Kiwi keeps Dawn's HTML structure and layout system but replaces the hero content with Taste's personal-site philosophy. I added outgoing links to GitHub and LinkedIn as Chips making the theme more portfolio oriented. The content is dynamic and uses Ghost's admin UI to set hero_name, hero_tagline, github_url andlinkedin_url
Particles
The most visible addition is an animated particle background in the hero section, built with particles.js. Dots float slowly across the background and scatter away from your cursor or finger (repulse mode).
A few things made this trickier than expected:
Color in dark mode. The particle color is read at runtime to match the theme. The obvious approach — a CSS custom property — ran into a subtle bug: cssnano (the CSS minifier in the build pipeline) converts #c0c0c0 to the named color silver. particles.js only parses hex strings, so hexToRgb("silver") returns null and particles silently fail to render. The fix was to skip CSS variables entirely and detect dark mode directly in JavaScript.
Touch and z-index. The canvas sits at z-index: 0 behind the hero content. That means touch events on the name, tagline, or social chips never reach the canvas — the overlay intercepts them. The fix was to attach all pointer listeners (mouse and touch) to the .cover wrapper instead, then manually push coordinates into particles.js's internal state.
Featured post cards
The featured posts section on the home page got a card treatment: a subtle border (matching the style of the social chip buttons), slightly rounded corners, a hover effect, and equal-height cards across a row regardless of title length. The equal height is handled with flexbox on the owl carousel's stage element.
Feature images
On the post page, feature images now use object-fit: contain instead of cover — meaning the full image is always visible without cropping, which matters a lot for PNGs and diagrams. The width was also narrowed from kg-width-wide (extending beyond the text column) to match the text width exactly.
The gray placeholder background that showed through transparent PNGs — a loading shimmer baked into the shared Ghost theme assets — is overridden to transparent for both the post page and the featured post cards.
Tables
Ghost's default table styles use hardcoded rgba(255,255,255) scroll-shadow gradients on the first and last cells. In dark mode these render as stark white blobs. Kiwi replaces the whole table style: no borders, alternating row backgrounds using existing dark-mode-aware CSS variables, and a slightly more prominent header row.
Theme on GitHub
All the code is open source: github.com/lor-enz/kiwi-ghost-theme