This guide is for people who want to change Attegi's source: fix a bug, add a feature, or maintain a fork. If you only want to adjust colors or fonts, Customizing Attegi shows how to do that without a build step.
What you need
- Bun 1.3 or newer. It installs dependencies, runs the build, and serves the preview.
- Git, and a fork of bunizao/Attegi.
- A Ghost site to borrow content from. Any Ghost 5.118+ or Ghost 6 site works, including this demo.
Five commands
git clone https://github.com/<you>/Attegi.git
cd Attegi
bun install
cp .env.example .env.local # then fill in the two values
bun run dev:preview # http://localhost:3020
dev:preview is the loop you will use most. It renders Attegi's Handlebars templates locally, fills them with real posts fetched from your Ghost site through the Content API, and reloads the browser whenever you save a template, stylesheet, or script.
To get the two values for .env.local, open Ghost Admin → Settings → Integrations → Add custom integration. Copy the Content API key and the API URL:
GHOST_CONTENT_API_URL=https://your-site.example
GHOST_CONTENT_API_KEY=your-content-api-key
Three ways to run it
| Command | What it does | Use it when |
|---|---|---|
bun run dev:preview |
Local template renderer + remote content | Almost always. Fastest loop for templates, CSS, and JS. |
bun run dev:hot |
Proxies a real Ghost site and injects your local assets | You need Ghost's exact output: members, comments, search. |
bun run dev |
Only watches and rebuilds CSS and JS | You run your own Ghost locally with the theme symlinked. |
The preview renderer is an approximation. Portal, comments, search, and Ghost's image resizing are stubbed out. When something only breaks on a real Ghost, switch to dev:hot (set GHOST_DEV_URL to the site you are proxying).
Where things live
Attegi/
├── default.hbs # page shell: <head>, header, footer
├── index.hbs post.hbs page.hbs tag.hbs author.hbs
├── page-tags.hbs # layout for the page with URL /tags/
├── page-links.hbs # layout for the page with URL /links/
├── partials/ # shared template pieces and inline SVG icons
├── locales/ # interface translations, one JSON per language
├── src/
│ ├── sass/ # style.scss + color, font, TOC and poem modules
│ └── js/
│ ├── entries/ # site.js, post.js, page.js
│ ├── features/ # one file per feature, each exports init*()
│ └── core/ # tiny helpers: qs, qsa, onReady, throttle
├── scripts/ # build, preview server, deploy, version bump
└── package.json # also defines the Ghost Admin theme settings
Two rules keep the repository sane:
- Edit sources, never build output.
assets/css/style.cssandassets/js/*.jsare generated and ignored by Git. - No frameworks at runtime. JavaScript is plain ES modules bundled by esbuild. No jQuery, no React.
Common tasks
Change a color
Colors are CSS custom properties in src/sass/_colors.scss. Each one has a light value and a dark value. If you change one, change both, then check the page in both modes.
Add a theme setting
Theme settings live in package.json under config.custom. Add an entry, then read it in a template as @custom.your_key. The preview renderer reads the default value from the same place, so it works locally right away.
"show_reading_time": {
"type": "boolean",
"default": true,
"group": "post"
}
Add a translated string
Wrap the text with {{t "Your text"}} in a template, then add the key to the files in locales/. A missing translation falls back to the English key, so nothing breaks while a language catches up.
Add a JavaScript feature
Create src/js/features/your-feature.js that exports an initYourFeature() function, and call it from the right entry in src/js/entries/. post.js only loads on posts, page.js on pages, and site.js everywhere.
Before you open a pull request
bun run validate
This builds production CSS and JS, packages dist/attegi.zip, and runs GScan, Ghost's official theme checker. A pull request that fails GScan cannot be released, and CI runs the same check on every push.
Then look at your change the way readers will:
- Light and dark mode.
- Widths of 420, 480, 640, and 960 pixels. Those are the theme's breakpoints.
- An English post and a Chinese post. Line breaks and punctuation behave differently.
Commit messages follow Conventional Commits with a scope, for example fix(toc): keep active heading in view.
Shipping a release
Releases are cut by the maintainer:
./scripts/bump-version.sh minor
git commit -am "chore(release): bump version to x.y.z"
git tag vx.y.z && git push --follow-tags
Pushing the tag makes GitHub Actions build the theme, run GScan, and attach attegi.zip to a new GitHub release. That zip is what everyone downloads.
Comments