A modular kit for books, lecture notes and slides that share one design language.
Every component is its own im- function, sizes come from roles (body, table, code,
inline code), and fonts and colors are applied as groups you pick from palettes.

- 📄
im-doc— books and notes with a wide left column for side notes - 🖥️
im-slides— presentations built on Touying - 🎨 15 color palettes × 4 font palettes, applied as a whole
- 🧩
im-code,im-table,im-grid,im-note,im-explain,im-steps, … - 🌊 10 generative cover styles, shared by book covers and title slides
- 🇹🇷 / 🇬🇧 labels in Turkish (
lang: "tr", default) or English (lang: "en")
Quick start
typst init @preview/ilkman:0.1.0 my-notes
or import it into an existing file:
#import "@preview/ilkman:0.1.0": *
#show: im-doc.with(
title: [SIP and VoIP Basics],
author: "ilker",
lang: "en",
fonts: "plex",
colors: "okyanus",
)
#im-cover(style: "waveform")
#im-toc()
= Introduction
Hello, *ilkman*.
Fonts
All default fonts are SIL OFL licensed and available in the Typst web app. For local use install them from Google Fonts. If a font is missing, Typst falls back to its embedded fonts and the document still compiles (with a warning).
fonts: |
Body / headings | Tables | Code |
|---|---|---|---|
mulish |
Mulish | Mulish | JetBrains Mono |
plex |
IBM Plex Sans | IBM Plex Sans Condensed | IBM Plex Mono |
source |
Source Serif 4 / Source Sans 3 | Source Sans 3 | Source Code Pro |
fira |
Fira Sans | Fira Sans Condensed | Fira Mono |
Override single roles and keep the rest of a palette:
#show: im-doc.with(fonts: (base: "plex", body: "Avenir Next"))
Colors

okyanus, fiyort, gece, çelik, okabe, orman, nane, zeytin,
kiremit, kehribar, gül, bordo, lavanta, mürdüm, grafit
(ASCII aliases: celik, gul, murdum).
Each palette has five roles — ink (text), primary (headings, header rows,
solid backgrounds), accent (links, highlights), tint and wash (zebra rows,
boxes, code background). okabe is derived from the colorblind-safe Okabe–Ito
set. A custom palette is a dictionary:
#show: im-doc.with(colors: (primary: rgb("#005f73"), accent: rgb("#ee9b00")))
Named fills such as "blue", "green", "red", "yellow", "orange",
"purple", "teal", "pink", "gray" are bent toward the active palette and
are always light, so text on them stays readable.
Sizes
Every role is relative to the body size, so slide density changes everything at once.
| Role | Doc | Slides |
|---|---|---|
| Body | 11pt | 22pt |
| H1 / H2 / H3 | 20 / 15 / 12.5pt | 40 / 30 / 24pt |
| Table | 9.5pt | 18pt |
| Code block | 9pt | 16pt |
| Inline code | 0.9em | 0.9em |
| Caption / note | 9pt | 16pt |
Documents
#show: im-doc.with(
lang: "tr", // "tr" | "en"
fonts: "mulish",
colors: "okyanus",
paper: "a4",
note-column: 1/4, // left note column; 0 = off
chapter-label: true, // "BÖLÜM 1" above H1
chapter-start: "any", // "odd" = chapters start on right-hand pages
par-indent: true, // indented paragraphs, automatic block spacing
title: none, subtitle: none, author: none, date: auto, version: none,
header: (left: none, center: none, right: "chapter"),
footer: (left: none, center: none, right: "page"),
header-rule: true,
footer-rule: false,
mirror: false, // swap left/right on even pages (print)
print: false, // true = link URLs also as footnotes
)

Header and footer slots take "chapter", "section", "title", "author",
"date", "version", "page", "page-total", any string or content, a
function c => ..., or none. header: none removes the whole header.
Numbering: H1–H3 are numbered (1, 1.1, 1.1.1), H4 is not. Figures,
tables and listings are numbered per chapter (Şekil 2.3). Reference with
<label> and @label.
| Function | Purpose |
|---|---|
im-cover(style: ..) |
Full-page cover without margins (see Covers) |
im-toc(depth: 2) |
Table of contents, printed without the note column |
im-figures-list() / im-tables-list() |
Lists of figures / tables |
im-appendix[...] |
Chapters inside are numbered A, B, C |
im-explain("term", fill: "yellow")[note] |
Highlighted term + arrow to a note in the left column |
Components
im-code
#im-code(
numbering: true, // line numbers
title: "server.py", // file-name bar
hl: (3, 5), // highlighted lines (int, array or range)
width: 100%, // or 60%, or auto = as wide as the longest line
align: "left", // where the box sits when narrower: left | center | right
caption: none, // numbered caption ("Kod 1.2")
)[```py
print("test")
```]
Code is always left-aligned inside the box, so indentation is preserved.
On slides, hl-steps: ((1, 2), (3, 4)) highlights a different set of lines at
each animation step. Inline code uses normal backticks.
Why the extra backticks? A
[...]block is Typst markup — quotes turn into typographic quotes and#,*,_are interpreted. Code must arrive as a raw block.
im-table
Two input forms, one function:
// Text form — fast to type. Rows = lines, cells = "|".
#im-table(fill: "blue", zebra: true)[```
Protocol | Port | Transport
SIP | 5060 | UDP/TCP
SIPS | 5061 | TLS
```]
// Cell form — any formatting inside cells.
#im-table(cols: 3)[*Protocol*][Port][Transport][SIP][`5060`][UDP]
| Parameter | Default | |
|---|---|---|
cols |
auto |
Column count (text form: widest row) |
header |
true |
First row is the header |
fill |
"primary" |
Color family for header and zebra rows; none = plain |
zebra |
true |
Two alternating light tones |
align |
auto |
Numeric columns right, text left; or an alignment / array |
widths |
auto |
e.g. (1fr, 2fr, auto) |
caption |
none |
Numbered caption |
sep |
"|" |
Separator for the text form; escape with | |
markup |
false |
Text form: evaluate cells as Typst markup |
Lines made only of -, | and : are ignored, so Markdown tables can be pasted.
im-grid
#im-grid("1/1")[left][right] // two equal columns
#im-grid("1/2")[narrow][wide] // 1/3 – 2/3
#im-grid("1/1/1", fill: "blue")[A][B][C][D][E][F] // extra bodies wrap
#im-grid("1/1", fill: ("green", "red"), stroke: true)[Do][Don't]
The ratio is a string — (1/2) would be evaluated to 0.5 by Typst.
Options: gutter: 1em, stroke: none | true | "green" | color,
fill: none | "green" | color | array, radius: 4pt, inset: 0.8em, align: top.
Fills are always kept light.
Other components
#im-note(kind: "info")[...] // info | tip | warn | danger; title: auto | none | [..]
#im-term("SIP")[Session Initiation Protocol]
#im-steps[
+ Install
+ Configure
]
#im-kbd("Ctrl+Shift+P")
#im-img(image("topology.png"), width: 80%, caption: [Topology])
#im-diagram(width: 70%, caption: [Flow])[ /* any drawing: mermaid, cetz, fletcher … */ ]
#im-plot(width: 100%)[ /* e.g. a lilaq plot */ ]
im-img needs image("file.png") or path("file.png"): a bare string would be
resolved relative to the package, not to your document. im-diagram / im-plot
scale whatever you draw with your favourite package to the given width.
Slides
#import "@preview/ilkman:0.1.0": *
#show: im-slides.with(
title: [SIP Basics], author: "ilker",
fonts: "fira", colors: "okyanus",
aspect: "16-9", // or "4-3"
dense: "mid", // default density: "low" | "mid" | "high" | number
mode: "light", // or "dark"
progress: true,
footer: (left: "title", center: none, right: "page"),
)
#im-title-slide(style: "neural")
= Section // section slide
== First slide // content slide
- point one
#im-pause
- point two
#im-speaker[Notes for the presenter view.]

| Function | Purpose |
|---|---|
im-slide(title: .., dense: ..)[..] |
Explicit slide; == headings also create slides |
im-title-slide(style: ..) |
Title slide in any of the 10 cover styles |
im-section[..] |
New section (same as =) |
im-focus[..] |
One big message on a colored slide |
im-pause |
Next animation step (Touying pause) |
im-speaker[..] |
Speaker note |
im-dense("high") |
Density from this slide on (put it inside a slide); auto resets |
Density
Text-heavy slide? Shrink everything — body, tables, code and spacing — at once:
dense: |
Body | Slide title | Table | Code |
|---|---|---|---|---|
"low" |
25pt | 33pt | 21pt | 18pt |
"mid" |
22pt | 30pt | 18pt | 16pt |
"high" |
17pt | 26pt | 14pt | 12pt |
A number such as dense: 0.65 is used as a factor. Headings shrink less than body text.
Covers

Ten styles: waveform, pcm, packet, bits, gitgraph, minimap,
neural, solid, topo, halftone. The same options work for im-cover
and im-title-slide.
#im-cover(
style: "neural",
series: "Lecture notes", volume: 1, // small line above the title
subtitle: [From protocol to production],
seed: auto, // auto = derived from the title; a number = another variation
art-height: auto, // share of the page used by the artwork, e.g. 65%
text-pos: "bottom", // or "top"
title-size: auto,
bg: auto, // "white" | "primary" | color name | color
art-color: auto, // main color of the artwork
logo: none, // e.g. image("logo.svg")
neural: (layers: (8, 12, 12, 4), highlight: none),
)
Generated artwork is deterministic: the same title always gives the same cover.
| Style | Options (defaults) |
|---|---|
waveform |
bars: 40, shape: "round", mirror: true, accent-every: 5 |
pcm |
samples: 24, levels: 8, show-wave: true, stems: true |
packet |
fields: ("V=2", "PT", "seq", "SSRC"), bits: 32, ruler: true, footer: auto |
bits |
text: auto (title), group: 8, highlight: 9 |
gitgraph |
branch: "main", lanes: 4, commits: 18, merges: 0.3 |
minimap |
lines: 46, max-indent: 3, viewport: (30%, 50%) |
neural |
layers: (12, 6, 8, 6, 3), highlight: 2, edges: 0.7 |
solid |
rule: false |
topo |
rings: 9, center: (70%, 30%), wobble: 0.15 |
halftone |
grid: 20, origin: "top-left", falloff: 1.0 |
Options of styles that are not selected are ignored, so switching styles never breaks.
Development
# Link the working copy as a local package, then compile everything
ln -s "$PWD" ~/.local/share/typst/packages/preview/ilkman/0.1.0
tests/run.sh --font-path path/to/fonts
License
MIT. Fonts are not bundled; the default families are licensed under the SIL OFL. Slides depend on Touying (MIT).