Add A Custom Theme to mdBook
What is mdBook?
mdBook is the open-source project that this website was built with. It's hosted on Github and it's project maintainers are the maintainers of Rust, my favorite programming language when I'm forced to write code. The project compiles to a single binary that one can use to compile markdown files and related assets to html. The location of the files is specified by convention and the project supports plugins that can modify the output using preprocessing. This website makes use of a few of these preprocessing tools at the moment. They are also compiled to a single binary, all of their filenames must start with the prefix mdbook-
. mdBook is made aware of your intention to use these preprocessors by a simple specification in your website's book.toml
. This website is currently using the following preprocessors:
This website also uses the following external tools. These are binaries that you will need to run in addition to the mdbook build
command:
Why do you need a custom theme?
I need a custom theme because I don't particularly enjoy writing code. I enjoy writing stories and I'd like these stories to not hurt your or my eyes when reading them. I noticed that the built in theme's colors strained my eyes and made words blur together even though I'm wearing glasses. Books, for the most part, don't affect me like this unless the light is too low in my extremely dark apartment. It took a little bit of experimenting and a helpful blog post on non-violent fonts to find something that works for me. I have an elementary undersatnding of French so when I read that serif means "declorative flourish" I was astonished that I've gone all these years ignoring "without decorative flourish" while continuing to build apps and websites. I landed on Garamond which actually has a cool backstory, I had no idea fonts could be hundreds of years old. It's available for free at freefonts.com. As per mdBook convention the font will need to be converted to woff2
because the only available file type I could find was ttf
. There are libraries available to do this on Github but because I am me, I would have ended up reading their source code, along with the source code of any important dependencies, so I just used this free online tool which works swimingly. Alternatively, if you like the font on this website you can just download it.
Here are the other fonts that came in the package that I have already converted from ttf
to woff2
. I can't answer with any specificity what they are to be used for but I didn't specify their use anywhere, so I'm assuming they're not being used anywhere. Browsers are massive projects so I could be wrong, but I'm definitely not looking it up tonight.
If you try my custom theme to read the stories contained in this website, and find that it strains your eyes, please send me an email! I want you to read these and I do not want you to hurt yourself doing so!! Check out my contact page for ways to contact me.
I've also found a dearth, neigh, a void, of theme options that don't involve replacing the built in themes, or that seem to only replace the code block themes. The two I played with were:
- Catpuccin's repo - Really pretty themes for many different applications, but requires replacing the built in themes.
- mdbook-theme - Actually seems really cool and gives you all of themes from ace editor, but involves a more intensive setup, only allows "theming" by modifying the css variables in a preprocessor which affects all themes, and only supplies all the really flowery theming to the live Rust code editor built into mdBook.
Is it hard to do?
Not really. A general overview is that you need recursively copy the fonts
directory from your compiled book
directory into your src
directory and then add your font the newly copied directoy (src/fonts
). You then recursively copy the book/theme
directory to your src
directory. Following that, you modify a couple of the .css
files. Finally you add the menu option of your new theme to src/theme/index.hbs
.
For convenience, here are the unix commands to initially build the required files and copy them to the correct locations.
mdbook build
cp -r book/fonts src
cp -r book/theme src
You can see the additions I made by diffing the commit that made the changes to the previous commit on my Github. Just scroll down to the files in question.
Will you explain the changes?
Sure, the src/theme/css/variables.css
file contains variables that get imported into the other css files. They are defined as so:
.theme-name-css-class-selector {
--variable-name: "value"
}
They are then used in the files they are imported into as so:
.css-class-selector {
style-property: var(--variable-name)
}
This way, the values of those style properties dynamically change when a theme is selected from your website's theme menu options.
The src/theme/css/general.css
file contains the base styles for the major div
elements you see when you look at your website. For instance the html
, body
, code
, and content
tags. The content
tags contain essentially everything that isn't navigation or interactivity. So basically, the pages in your book. We modify this file to use the variables we defined in src/theme/css/variables.css
.
The src/theme/css/chrome.css
contains extra styles of unspecified purpose, they are styles that the maintainers did not deem worthy of the "Base Style" mantle.
Using this knowledge we need only make a few changes to get the desired effect, a skinable custom theme that doesn't require replacing any of the existing themes.
First we add the font-family
property to src/theme/css/chrome.css
, giving it the value "Open Sans", sans-serif;
, which is the default font used by the themes. This prevents us from interfering with the other themes' font, and has the positive side effect of allowing us to only use our pretty Serif font on the pages of the book. This means that the sidebar navigation will retain its "Open Sans" or fallback sans-serif font. I did this so that the website bits look like a website and the book bits look like a page in a book.
To do this, locate the .sidebar
css class selector and add the property definition font-family: "Open Sans", sans-serif;
, so that the final property definition looks like this:
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: var(--sidebar-width);
font-size: 0.875em;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
font-family: "Open Sans", sans-serif;
}
Next, in src/theme/css/general.css
, we add add a css class selector definition with the title of our custom theme, and fill it with the values of any of the other themes definined in the file. I just copied the values contained in the .rust
definition because it contained the closest color scheme to what I desired. You then, modify the values of each property to match what you're looking for. I found it's some guess and check work but mdbook serve
recompiles your site every time you save a change, and VScode or other editor plugins make it easier by adding in-line color pickers and such. You then, add a variable named --font
to the existing css class selector definitions of each theme, .ayu
,.coal
,.light
,.navy
, and .rust
, and set its value to "Open Sans". Make sure to also include a --font
variable in your custom theme. As mentioned before, I went with "Garamond Regular".
For brevity, we will only look at an example of a single modified theme located above our new custom theme definition in src/theme/css/general.css
. Also, I found that the Garamond font I downloaded appeared much smaller than the "Open Sans" font, so to remedy this I defined another variable, --content-font-size
and set it to 1.5em
.
.rust {
--bg: hsl(60, 9%, 87%);
--fg: #262625;
--sidebar-bg: #3b2e2a;
--sidebar-fg: #c8c9db;
--sidebar-non-existant: #505254;
--sidebar-active: #e69f67;
--sidebar-spacer: #45373a;
--scrollbar: var(--sidebar-fg);
--icons: #737480;
--icons-hover: #262625;
--links: #2b79a2;
--inline-code-color: #6e6b5e;
--theme-popup-bg: #e1e1db;
--theme-popup-border: #b38f6b;
--theme-hover: #99908a;
--quote-bg: hsl(60, 5%, 75%);
--quote-border: hsl(60, 5%, 70%);
--table-border-color: hsl(60, 9%, 82%);
--table-header-bg: #b3a497;
--table-alternate-bg: hsl(60, 9%, 84%);
--searchbar-border-color: #aaa;
--searchbar-bg: #fafafa;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #666;
--searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67;
--font: "Open Sans"
}
.papier {
--bg: hsl(45, 46%, 85%);
--fg: #262625;
--sidebar-bg: #3b2e2a;
--sidebar-fg: #c8c9db;
--sidebar-non-existant: #505254;
--sidebar-active: #e69f67;
--sidebar-spacer: #45373a;
--scrollbar: var(--sidebar-fg);
--icons: #737480;
--icons-hover: #262625;
--links: #434341;
--inline-code-color: #6e6b5e;
--theme-popup-bg: #e1e1db;
--theme-popup-border: #b38f6b;
--theme-hover: #99908a;
--quote-bg: hsl(60, 5%, 75%);
--quote-border: hsl(60, 5%, 70%);
--table-border-color: hsl(60, 9%, 82%);
--table-header-bg: #b3a497;
--table-alternate-bg: hsl(60, 9%, 84%);
--searchbar-border-color: #aaa;
--searchbar-bg: #fafafa;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #666;
--searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67;
--font: "Garamond Regular";
--content-font-size: 1.5em
}
We then modify the font-family
property in src/theme/css/general.css
under the html
element selector to use our --font
variable we created previously in src/theme/css/variables.css
. We also modify the value of the property font-family
contained in the .body
class selector definition.
To do this locate the html
element selector and modify the font-family
value so that final definition looks as follows:
html {
font-family: var(--font), sans-serif;
color: var(--fg);
background-color: var(--bg);
text-size-adjust: none;
}
Then locate the body
element selector and add a font-family
definition that uses the --font
variable we defined previously:
body {
margin: 0;
font-size: 1.6rem;
font-family: var(--font);
overflow-x: hidden;
}
Finally, locate the .content
class selector and modify the font-size
value so that the final definition uses the --content-font-size
variable we defined previously:
.content {
overflow-y: auto;
padding: 0 15px;
padding-bottom: 50px;
font-size: var(--content-font-size);
}
The last file you will need to modify is src/theme/index.hbs
. Add a list item for your custom theme to the unordered list that defines the theme menu items.
To do this locate the ul
element with the id
"theme-list" and add an li
element containing a button with a handlebars definition that passes off the hard work to mdbook
. The new menu definition should look like this:
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
<li role="none"><button role="menuitem" class="theme" id="papier">{{ theme_option "Papier" }}</button></li>
</ul>
Above, you can see the line I added for my custom theme, "Papier". The two parts that need to change if you just copy one of the lines of html for another theme are the button
id
and the variable passed to theme_option
.
Here are all of the completed changes, sans font file, if you would like to just copy my files and make your adjustments after: