Using a theme
On this page
The config names its theme, and that is all a theme needs:
import { defineConfig } from "@kilim-dev/core";
export default defineConfig({
title: "My Blog",
url: "https://example.com",
theme: "default",
});theme: "terminal" is the folder themes/terminal/ when the site has one, else the package @kilim-dev/theme-terminal, then kilim-theme-terminal, then a package called terminal; a full package name such as "@acme/kilim-theme-paper" works too. A theme that is not installed stops the build with what was tried and the install command for your package manager:
error: kilim.config.ts: "theme": theme "terminal" not found (looked in themes/terminal/, @kilim-dev/theme-terminal, kilim-theme-terminal, terminal); install it with `npm install @kilim-dev/theme-terminal`.A theme object, the default export of a theme package, still works as theme for code that builds its theme on the fly.
Every theme understands the same content: pages, every page in content/ such as content/index.md and content/about.md, and posts, the dated posts in content/posts/, listed at /posts/ with a feed. A theme may add collections of its own, such as Terminal's projects, which stay empty while their folder does not exist. So trying another theme is one install and one word:
npm install @kilim-dev/theme-terminalexport default defineConfig({ title: "My Blog", theme: "terminal" });Your settings for the old theme stay in settings/default.yaml, ready for when you switch back. What does not carry over yet are blocks and Markdown components only one theme has, such as the default theme's hero or callout: a page that uses them stops the build under a theme without them.
The starters use @kilim-dev/theme-default: page, post, list and doc layouts, a header with a menu, the callout component and nine blocks for landing pages:
| Block | What it shows |
|---|---|
hero | Title, lead text, links, an eyebrow label (a link with eyebrowUrl), a command and a code window or image. |
stats | A row of big numbers with labels. |
features | Short descriptions with an optional icon; layout: bento puts them on cards, wide ones over two columns, with an optional code snippet. |
logos | A row of names or logo images, each optionally a link. |
split | Text and points beside a code window or an image; reverse swaps the sides. |
steps | Numbered steps, each with optional text and code. |
cards | Cards side by side, for plans or templates, with an image, a badge, points and a link. |
faq | Questions that open to their answers, without JavaScript. |
cta | A call to action with one link and an optional command. |
Every block also takes id, an anchor for links such as #pricing, and tone: plain (the default), soft, accent or contrast, a background that reaches the edges of the screen. features, split, steps, cards and faq take an eyebrow, a short label above the title. In a code window, lines starting with $ are shown as commands. features icons are bolt, book, check, code, gauge, globe, layers, pencil, search, shield, sparkles and terminal.
Besides pages and posts it has docs (content/docs/), the pages of the doc layout with a sidebar built from the folders; the docs starter uses it.
Kilim also publishes three themes that look nothing like the default one. Each brings its own collections, settings and blocks, and works in English and Turkish; see them with screenshots and live demos at themes.kilim.dev.
| Package | For |
|---|---|
@kilim-dev/theme-terminal | A dark developer blog and portfolio: profile, social links, tags, projects. |
@kilim-dev/theme-gazette | An editorial magazine: a front page of blocks, sections, writers, series. |
@kilim-dev/theme-notebook | A serif notebook: posts, untitled notes, an archive by year, a now page. |
Install one and name it in the config, e.g. theme: "gazette" after npm install @kilim-dev/theme-gazette. They bring the collections they need, such as Gazette's authors and categories, so the config needs no collections of its own. They take overrides like the default theme, and a local theme can build on one with defineTheme({ extends: "gazette" }).
Every setting of a theme has a default, so a site builds without settings. To change some, put only those in settings/<theme>.yaml, named after the theme: settings/default.yaml, settings/terminal.yaml. The file is checked against the theme's schema on every build, with the file and line of a mistake; a setting the theme does not know, e.g. one left from another theme, is a warning and is left out. The admin edits the active theme's file at /admin/settings/, shows the theme's default for each setting the file leaves out and keeps your comments. The default theme's settings:
# Header links, in order.
menu:
- label: Blog
url: /blog/
- label: About
url: /about/
# "header" puts the search() plugin's box in the header of every page.
search: none
# A # link after each ## and ### heading, shown on hover and focus.
headingAnchors: true
# Interface text, e.g. a different "Newer posts".
labels:
newer: NewerLayouts read them as site.settings, typed from the theme. A multilingual site gives other languages their own top-level settings in settings/<theme>.<locale>.yaml, e.g. settings/default.tr.yaml; see multiple languages.
The config's collections go over the theme's, option by option, so it only says what differs:
import { defineConfig, z } from "@kilim-dev/core";
export default defineConfig({
theme: "terminal",
collections: {
// Posts in content/blog/ at /blog/, five to a list page; the rest stays the theme's.
posts: { directory: "blog", list: { pageSize: 5 } },
// A field of your own, kept when you switch themes.
pages: { fields: { subtitle: z.string().optional() } },
// A theme collection you do not use.
tags: false,
},
});list merges option by option too. schema replaces the theme's schema; fields adds fields to it (it must be a Zod object, as the official themes' are) and is the way to stay theme independent. Collections the theme does not have are added as before.
A .tsx file in layouts/ adds a layout by its name, for a collection's layout or a page's frontmatter layout. It cannot reuse a theme layout's name; that is what overrides are for.
// layouts/changelog.tsx
import type { Props } from "@kilim-dev/theme-default";
import { Content, Document } from "@kilim-dev/theme-default";
export default function Changelog(props: Props) {
return (
<Document {...props}>
<h1>{props.page.title}</h1>
<Content {...props} />
</Document>
);
}<Content> is page.content with the heading links of headingAnchors; use page.content itself for the body as written.
overrides/ replaces a part of the theme by name, so you change one piece and keep updates for the rest:
| Path | Replaces |
|---|---|
overrides/<layout>.tsx | A layout, e.g. post.tsx. |
overrides/partials/<name>.tsx | A partial, e.g. header.tsx. |
overrides/components/<name>.tsx | A Markdown component. |
overrides/blocks/<name>.tsx | A block. |
overrides/og-image.tsx | The Open Graph image template. |
An override with a name the theme does not have is an error, so a typo never goes unnoticed.
styles/critical.cssis inlined after the theme's critical CSS; keep it to what the first screen needs.styles/main.cssjoins the theme's stylesheet at the end of the page.public/is copied over the theme's ownpublic/files, so a site can replace a theme's image or font with a file of the same path.
When this is not enough, write a theme that extends the one you use.