Writing a theme
npx kilim theme new my-themewrites a working theme to themes/my-theme/: layouts, partials, a component, blocks, settings and CSS files. Use it by folder name:
export default defineConfig({ theme: "my-theme" });Kilim compiles a local theme's TSX on every build, and kilim dev rebuilds when you save it. Its public/ folder is copied to the site.
index.tsx exports the theme:
import { defineTheme, z } from "@kilim-dev/core";
import critical from "./styles/critical.css";
import main from "./styles/main.css";
export default defineTheme({
name: "my-theme",
// The Kilim versions the theme is made for; other versions fail with a clear error.
kilim: "^0.1",
settings: z.object({ menu: z.array(z.object({ label: z.string(), url: z.string() })) }),
layouts: { page: Page, post: Post, list: List },
partials: { header: Header, footer: Footer },
components: { callout },
blocks: { hero },
styles: { critical, main },
});- Layouts get
page(title,content,data,headings,meta),site(title, settings,collections),entriesandpaginationon list pages, andpartial(name). styles.criticalis inlined in every page;styles.mainbecomes a hashed stylesheet loaded at the end of the body..cssfiles are imported as text.settingsis a schema forsettings/<name>.yaml, named after the theme'sname. Give every setting a default (e.g..default([]), or.prefault({})for an object), so a site builds without the file; the type flows into layouts assite.settings.collectionschangespagesandposts, which every theme has, and adds the theme's own, e.g.{ posts: { layout: "post", list: { pageSize: 10 } }, projects: {...} }. A function gets the config, e.g. for list titles in the site's languages withlocalizedTitle. A site's config goes over these option by option. Lay outpagesandpostsso a plain post (title,date) and a plain page build without any field only your theme knows.ogImagerenders the Open Graph image from TSX with inline styles.pluginslets a theme bring plugins, such as one that checks references between entries.
<HeadTags page={page} site={site} /> from @kilim-dev/core writes the title, description, canonical link, Open Graph and JSON-LD tags; put it in every layout's <head>.
extends builds on another theme, a package or a local folder name, and replaces only what you give:
import { defineTheme } from "@kilim-dev/core";
import base from "@kilim-dev/theme-default";
import { Header } from "./header.js";
import critical from "./styles/critical.css";
export default defineTheme({
name: "my-brand",
extends: base,
partials: { header: Header },
styles: { critical },
});Layouts, partials, components and blocks replace the base's by name. Settings, image sizes and the Open Graph template are the base's unless you set them. Styles are added after the base's; styles: { inherit: false, ... } drops the base CSS entirely. This site's theme is exactly this: the default theme with the Kilim header and colors, in sites/docs/themes/kilim/ of the repository.
Publish the theme folder as a GitHub repository or an npm package. Others add it with:
npx kilim theme add github:user/repo
npx kilim theme add kilim-theme-paper@1.2.0
npx kilim theme add ./paper-1.2.0.tgztheme add copies the theme into themes/<name>/, where it can be read and changed, and tells you how to use or extend it. It checks npm packages against their integrity hash, reads .npmrc for private registries and GITHUB_TOKEN or GH_TOKEN for private repositories. A theme whose kilim range does not match the installed Kilim gets a warning.
A packaged theme ships static files with publicDir: new URL("../public/", import.meta.url). A local theme always uses its own public/ folder and ignores publicDir, so a packaged theme copied into themes/ works as it is.