Custom themes

Using one of the built-in themes is a great way to get started with Bookly, but you may want to create your own theme to match your branding or personal style. This section explains how Bookly’s theming system works, how to create a custom theme and how to apply it to your book.

Theming system

In Bookly, a theme is a dictionary of standardized functions that define the visual appearance of the key elements of a book. This common interface allows themes to be used interchangeably and to be combined.

Common interface

Every theme exposes the same interface, which is a dictionary defined as:

#let custom = (
  theme: custom-theme,
  part: custom-part,
  minitoc: custom-minitoc,
  custom-box: custom-custom-box,
  boxeq: custom-boxeq,
)

The elements of the dictionary are:

For instance, the fancy theme is defined as:

#let fancy = (
  theme: fancy-theme,
  part: fancy-part,
  minitoc: fancy-minitoc,
  custom-box: fancy-custom-box,
  boxeq: fancy-boxeq,
)

Thanks to this interface, high-level functions such as #part, #minitoc, #boxeq or #info-box work whatever the theme is. They simply call the corresponding function of the current theme, stored in states.theme:

#let part(title) = context (states.theme.get().part)(title)
#let boxeq(content) = context (states.theme.get().boxeq)(content)

Partial theme definitions

A custom theme does not need to implement the complete interface. The theme you provide is merged with the classic theme, so any key that is not defined in your theme falls back to the classic implementation. This makes it straightforward to customize individual elements without redefining an entire theme.

Setting up a custom theme

To implement a custom theme, you have to define a function that includes the show and set rules defining the style of the document (headings, footnotes, references, …). Basically, a theme should be structured as follows:

// my-theme.typ
#import "@preview/bookly:5.3.0": *

#let my-theme(colors: default-colors, it) = {
  // Heading level 1 style (chapters)
  show heading.where(level: 1): it => context {
    // Page break before each chapter
    if not states.open-right-chapter.get() { pagebreak(weak: true) }
    ...
  }

  // Heading level 2 style
  show heading.where(level: 2): it => { ... }

  // Heading level 3 style
  show heading.where(level: 3): it => { ... }

  // Outline entry style
  show outline.entry: it => { ... }

  // Other show and set rules
  ...

  it
}

// Custom part function
#let my-part(title) = { ... }

// Custom minitoc
#let my-minitoc = { ... }

// Custom box style
#let my-custom-box(title: none, icon: "info", color: rgb(29, 144, 208), body) = { ... }

// Custom equation box style
#let my-boxeq(body) = { ... }

// Theme dictionary
#let custom-theme = (
  theme: my-theme,
  part: my-part,
  minitoc: my-minitoc,
  custom-box: my-custom-box,
  boxeq: my-boxeq,
)

Then, you can initialize the template with your custom theme as follows:

#import "@preview/bookly:5.3.0": *
#import "my-theme.typ": custom-theme

#show: bookly.with(
  theme: custom-theme,
)
Note
You can explore the source code of the built-in themes to see how they are defined and how they rely on the template states to manage colors, numbering and page openings.

Theme composition

Because a theme is a plain dictionary, you can reuse and combine elements of the existing themes in the definition of your own theme. The functions of a theme are accessible using the pattern theme-name.key. For example:

#import "@preview/bookly:5.3.0": *

#let custom = (
  theme: pretty.theme,
  part: obook.part,
  minitoc: modern.minitoc,
  custom-box: orly.custom-box,
  boxeq: pretty.boxeq,
)

#show: bookly.with(
  theme: custom,
)

Combined with the fallback to classic, this also lets you change a single element of a theme. For instance, to use the classic theme with the boxes of the fancy theme:

#show: bookly.with(
  theme: (custom-box: fancy.custom-box),
)

Template states

Bookly provides some states that are useful when designing a custom theme. They store information about the current state of the document and are collected in a dictionary named states. The following states are available:

State
<alt-margins>: falsebool

Indicates whether the margins are alternated for odd and even pages when the tufte layout is enabled.

State
<author>string|content

Author of the document.

State
<colors>dictionary

Color scheme of the document (see Fonts and colors).

State
<counter-part>counter

Counter for parts.

State
<is-toc-part>: falsebool

Indicates whether the current entry is a part in the table of contents.

State
<isappendix>: falsebool

Indicates whether the current section is an appendix.

State
<isfrontmatter>: falsebool

Indicates whether the current section is front matter.

State
<justify-headings>: truebool

Indicates whether headings are justified.

State
<localization>dictionary

Dictionary of terms used in the document (e.g., “chapter”, “part”, …) in the selected language.

Note

To use a language that is not supported by default, you can modify the states.localization dictionary after initializing the template. For instance, to add support for Dutch:

#states.localization.update(json("path_to_file/dutch.json"))

For the English version, the JSON file is as follows:

    {
      "and": " and ",
      "appendix": "Appendix",
      "authored": "authored by",
      "chapter": "Chapter",
      "committee": "Defense committee",
      "cosupervisor": "Co-supervisor:",
      "cosupervisors": "Co-supervisors:",
      "defended": "defended on",
      "discipline": "Discipline:",
      "doctoral-school": "DOCTORAL SCHOOL",
      "habilitation": "French Habilitation to supervise research",
      "lof": "List of figures",
      "lot": "List of tables",
      "minitoc": "Content",
      "note": "Note",
      "part": "Part",
      "phd": "Doctoral thesis",
      "proof": "Proof",
      "specialty": "Specialty:",
      "sponsor": "Sponsor:",
      "sponsors": "Sponsors:",
      "supervisor": "Supervisor:",
      "supervisors": "Supervisors:",
      "tip": "Tip",
      "toc": "Table of contents",
      "version-usage": "This version can be viewed and downloaded free of charge for personal use only. It must not be redistributed, sold, or used in derivative works.",
      "warning": "Warning"
    }
State
<num-heading>: "1"string

Numbering pattern for headings.

State
<num-pattern>: "1.1."string

Numbering pattern for sections.

State
<num-pattern-eq>: "(1.1)"string

Numbering pattern for equations.

State
<num-pattern-fig>: "1.1"string

Numbering pattern for figures.

State
<num-pattern-subfig>: "1.1a"string

Numbering pattern for subfigures.

State
<open-right>: truebool

Indicates whether the document is laid out for two-sided printing (true unless the open-right option is false).

State
<open-right-chapter>: truebool

Indicates whether chapters start on a right-hand page.

State
<open-right-part>: truebool

Indicates whether parts start on a right-hand page.

State
<page-numbering>: "1/1"string

Numbering pattern for pages.

State
<paper-size>: "a4"string

Size of the paper.

State
<par-indent>: falsebool

Indicates whether paragraphs are indented.

State
<par-indent-amount>: 1.5emlength

Amount of the paragraph indentation.

State
<part-numbering>: "1"string

Numbering pattern for parts.

State
<sidenotecounter>counter

Counter for side notes.

State
<theme>dictionary

Current theme of the document (see Theming system).

State
<title>string|content

Title of the document.

State
<tufte>: falsebool

Indicates whether the current layout is the Tufte layout.