Scribble themes
1 How it works
2 Installation
3 Setting Up Your Theme
4 Rendering HTML
5 Adding a site navigation bar
6 Reference
theme/  provide-doc
6.1 Under the hood
scribble/  manual-custom-css
nav-link/  c
nav-item/  c
theme/  nav-block
css-imports
css->html-defaults
9.3

Scribble themes🔗

Joel Dueck

 (require scribble-theme) package: scribble-theme

Scribble documents published as HTML already look great (at least, the ones written in #lang scribble/manual). If you want to thoroughly customize the look of the rendered HTML, though, it can be tricky, so I made this package to make it a bit easier.

In my case, I wanted Scribble docs for my packages to render normally when rendered as part of a local install or the main site https://docs.racket-lang.org, but I wanted to easily substitute my own CSS when publishing to my website.

In order to attempt this, you have to be handy with CSS.

1 How it works🔗

A theme is a CSS file (and optionally additional CSS files it references via @import directives). Theming, for our purposes, involves substituting your own CSS file(s) for the ones supplied by scribble/manual, and doing so in a way that does not affect the output when rendered using "normal" methods like raco setup.

By default, documents written in #lang scribble/manual that are rendered to HTML link out to three files stored in the scribble collection: "manual-style.css" (which in turn brings in "manual-fonts.css" via a CSS @import declaration), and "manual-racket.css". This is all documented in Manual Rendering Style.

It’s possible to overrride "manual-style.css" by adding a #:style argument to title inside your Scribble doc; but this would affect the styling of your document every time it is rendered, which you might not want. Also, scribble/manual links "manual-racket.css" as a css-style-addition after your code runs, so there’s no way to add code to your document that can suppress that file from being included.

This module makes it easy to create a separate “themed” version of your document that imports its doc value (provided by all Scribble modules), strips out the default CSS and adds in your own. The normal/original document will still render normally, but you can also render the themed version for customized output. When you supply your main CSS file, any additional CSS files referenced via @import directives (one level deep) are automatically discovered and included.

2 Installation🔗

Install this package from the command line:

raco pkg install scribble-theme

You can also install it from the GitHub repository if you prefer.

3 Setting Up Your Theme🔗

A theme is simply a CSS file (and any additional CSS files it references via @import). You can create and store your theme’s CSS files anywhere you like—typically in the same directory as your Scribble source files, or in a project subdirectory.

To get started, you can use this package’s command-line utility to generate a CSS file containing the default styles from scribble/manual:

racket -l- scribble-theme my-theme.css

Concatenated into my-theme.css:

/Applications/Racket v8.18/share/pkgs/scribble-lib/scribble/manual-style.css

/Applications/Racket v8.18/share/pkgs/scribble-lib/scribble/manual-racket.css

This gives you a starting point that you can customize.

4 Rendering HTML🔗

In the same folder as your Scribble sources, create a new file:

"my-themed-scribblings.scrbl"

#lang racket/base
 
(require scribble-theme)
 
(theme/provide-doc "my-package.scrbl" "my-theme.css")

This new file acts like a custom overlay over your original Scribble doc. The first argument is the path to your original Scribble source file, and the second is the path to your main CSS file. If the CSS path is relative, it’s resolved relative to this themed file’s location.

You can render this file with scribble like so:

scribble --html +m \
          --redirect https://docs.racket-lang.org/local-redirect/ \
          --dest docs/ \
          --dest-name index.html \
          my-themed-scribblings.scrbl

This will place the output in the "docs/" subfolder with "index.html" as the main HTML file. Your CSS files are copied into the output under content-hashed names, such as "my-theme-3f2a9c1b.css", so browsers and CDNs fetch a fresh copy whenever the CSS changes (see css->html-defaults).

5 Adding a site navigation bar🔗

Themed docs published on your own website usually need a way back to the rest of that site. Pass a list of navigation items to theme/provide-doc with the #:nav keyword, and a bar of links is inserted at the top of every HTML page:

"my-themed-scribblings.scrbl"

#lang racket/base
 
(require scribble-theme)
 
(define site-nav
  (list (cons "My Site" "https://example.com/")
        (cons "Projects" "https://example.com/projects.html")
        (cons "Other docs"
              (list (cons "Guide" "https://example.com/guide/")
                    (cons "Reference" "https://example.com/reference/")))))
 
(theme/provide-doc "my-package.scrbl" "my-theme.css" #:nav site-nav)

Each item is either a nav link (a label paired with a URL) or a nav menu (a label paired with a list of nav links). A menu renders as a <details> element, so it opens and closes without JavaScript.

The bar appears exactly once on every page: on the single page produced by scribble --html, and on every page produced by scribble --htmls. Internally, a block is added to the front of every part in the document; at render time this block produces the bar only in parts that begin a new HTML page, and an empty hidden <span> everywhere else.

This package supplies no CSS for the bar. It generates the markup below, and you style it in your theme:

<nav class="theme-nav">

  <span class="theme-nav-item"><a href="https://example.com/">My Site</a></span>

  <span class="theme-nav-item"><a href="https://example.com/projects.html">Projects</a></span>

  <details class="theme-nav-menu theme-nav-item">

    <summary>Other docs</summary>

    <ul>

      <li><span><a href="https://example.com/guide/">Guide</a></span></li>

      <li><span><a href="https://example.com/reference/">Reference</a></span></li>

    </ul>

  </details>

</nav>

The bar is placed inside the main column, just after the page heading, so a fixed position is the simplest way to keep it at the top of the window. The CSS below is a starting point. It also moves Scribble’s own fixed elements (table of contents, page navigation, version box) down to make room:

:root { --site-nav-height: 2.4rem; }

 

.theme-nav {

    position: fixed; top: 0; left: 0; right: 0; z-index: 12000;

    height: var(--site-nav-height);

    display: flex; align-items: center;

    background: #fff; border-bottom: 1px solid #eaeaea;

}

.theme-nav-item:first-child { margin-right: auto; }

.theme-nav-menu { position: relative; }

.theme-nav-menu > summary { list-style: none; cursor: pointer; }

.theme-nav-menu ul {

    position: absolute; right: 0; top: 100%;

    list-style: none; background: #fff; border: 1px solid #eaeaea;

}

 

.tocset, .navsettop { top: var(--site-nav-height); }

.maincolumn { margin-top: calc(4rem + var(--site-nav-height)); }

.versionbox { top: calc(0.25rem + var(--site-nav-height)); }

@media print { .theme-nav { display: none; } }

6 Reference🔗

syntax

(theme/provide-doc scrbl-filename css-path keyword-option ...)

 
keyword-option = #:nav nav-items-expr
  | #:fingerprint? fingerprint-expr
 
  nav-items-expr : (or/c #f (listof nav-item/c))
  fingerprint-expr : boolean?
The main macro for creating a themed version of a Scribble document.

Dynamically requires the doc value from scrbl-filename, replaces its default CSS with the CSS specified by css-path, and provides the updated doc.

The scrbl-filename should be a module path (typically a string naming a ".scrbl" file).

The css-path can be either an absolute path or a relative path. If relative, it is resolved relative to the location of the file containing the theme/provide-doc call.

Any additional CSS files referenced via @import directives in the main CSS file (one level deep) are automatically discovered and included in the output.

If #:nav is given, a site navigation bar built from the nav links and nav menus in nav-items-expr is added to the top of every HTML page (see Adding a site navigation bar).

The CSS files are copied into the output under content-hashed names unless #:fingerprint? is #f (see css->html-defaults).

This macro expands to a call to scribble/manual-custom-css wrapped in a provide that exports the doc binding.

Changed in version 2.1 of package scribble-theme: Added the #:nav and #:fingerprint? arguments.

6.1 Under the hood🔗

You probably won’t need these functions unless you want to dynamically construct Scribble parts that use your themes.

procedure

(scribble/manual-custom-css scrbl-file    
  new-html-defaults    
  [#:nav nav-items])  part?
  scrbl-file : module-path?
  new-html-defaults : html-defaults?
  nav-items : (or/c #f (listof nav-item/c)) = #f
Dynamically requires the doc value from scrbl-file, replaces its HTML styling properties with new-html-defaults, and returns the updated part. If nav-items is a list, a block produced by theme/nav-block is added to the front of every part in the document.

This is the function used internally by theme/provide-doc.

Changed in version 2.1 of package scribble-theme: Added the #:nav argument.

value

nav-link/c : contract? = (cons/c string? string?)

value

nav-item/c : contract?

 = (or/c nav-link/c (cons/c string? (listof nav-link/c)))
Contracts for nav links and nav menus: a link is a label paired with a URL, and a menu is a label paired with a list of links.

Added in version 2.1 of package scribble-theme.

procedure

(theme/nav-block items)  block?

  items : (listof nav-item/c)
Returns a delayed-block that renders as a <nav class="theme-nav"> element when it appears in a part that begins a whole HTML page, and as an empty hidden <span> otherwise. Each part that should carry the bar needs its own block from this function, because the renderer caches the resolved block per object.

Added in version 2.1 of package scribble-theme.

procedure

(css-imports css-file-path)  (listof absolute-path?)

  css-file-path : absolute-path?
Returns a list of absolute paths to CSS files referenced via @import directives in css-file-path.

Only @import directives that reference files existing in the same directory as css-file-path are included. The function does not recursively search for @import directives in the imported files.

procedure

(css->html-defaults abs-css-path 
  [#:fingerprint? fingerprint?]) 
  html-defaults?
  abs-css-path : absolute-path?
  fingerprint? : boolean? = #t
Constructs an html-defaults struct suitable for use with scribble/manual documents.

The html-defaults uses the default Scribble prefix file and sets abs-css-path as the main style file. The extra-files field is populated with any additional CSS files discovered via css-imports.

When fingerprint? is true, the main CSS file and its imports are first copied to a "scribble-theme" folder inside the system temporary directory, each under a name that includes the first eight hex digits of the SHA-1 hash of its contents (for example, "my-theme.css" becomes "my-theme-3f2a9c1b.css"). The @import references in the copy of the main file are rewritten to the hashed names, and the copies are what Scribble installs and links in the rendered HTML. A change to any of the files produces new names, so caches never serve stale CSS; unchanged files keep the same names from build to build.

Changed in version 2.1 of package scribble-theme: Added the #:fingerprint? argument.