MaterialDocs.jl GitHub

Theming#

A MaterialDocs theme is a ThemeConfig: a seed color, three font choices, a corner-radius preset, and optional per-role overrides. Everything else — all 34 MD3 color roles, in both light and dark — is derived from the seed.

Built-in themes#

Pass any of these by name:

format = Material3(theme = :ocean_depth)
Theme Seed Display / Body Code
:default #9558B2 Inter JetBrains Mono
:ocean_depth #006B5E Fira Sans / Source Sans 3 Fira Code
:solar_flare #B23C17 Space Grotesk / DM Sans JetBrains Mono
:midnight #1A237E Plus Jakarta Sans JetBrains Mono
:forest #2E7D32 Literata / Source Serif 4 Roboto Mono
:arctic #0277BD Inter Tight / Inter JetBrains Mono
:rose_garden #AD1457 Playfair Display / Lora Roboto Mono
:amber_workshop #E65100 JetBrains Mono / IBM Plex Sans JetBrains Mono
:lavender #7B1FA2 DM Serif Display / DM Sans JetBrains Mono
:sandstone #8D6E63 Bitter / Libre Baskerville Roboto Mono
:neon_lab #00BFA5 Outfit Fira Code
:slate #455A64 Atkinson Hyperlegible Roboto Mono

They are available programmatically as BUILTIN_THEMES, and resolve_theme turns a name into a config.

Custom themes#

Build one from a seed color:

format = Material3(theme = ThemeConfig(
    seed = "#2E7D32",
    display_font = "Literata",
    body_font = "Source Serif 4",
    code_font = "JetBrains Mono",
    corner_radius = :rounded,
))

Fonts are Google Fonts family names. Every page links the Google Fonts stylesheet for them, so they load in the reader's browser; each font stack ends in system fonts, which readers without network access see instead.

Corner radius#

corner_radius selects a shape scale. Values are the extra-small through full radii in pixels:

Preset Radii (px)
:sharp 0, 2, 4, 8, 12, 16
:default 4, 8, 12, 16, 28, full
:rounded 8, 12, 20, 28, 36, full
:pill 12, 16, 28, 36, 44, full

Secondary and tertiary seeds#

By default the secondary and tertiary palettes are derived from the primary seed — secondary at reduced chroma, tertiary rotated 60° around the hue circle. To control them directly:

ThemeConfig(
    seed = "#1565C0",
    secondary_seed = "#00897B",
    tertiary_seed = "#E65100",
)

Overriding individual roles#

When you need one specific color to be exact, override the role by name. These are applied after generation, so they win over the derived value:

ThemeConfig(
    seed = "#1565C0",
    custom_colors = Dict(
        "surface" => "#FAFAFA",
        "on-primary-container" => "#0D47A1",
    ),
)

Role names use the CSS token spelling, with hyphens — on-primary-container overrides --md-sys-color-on-primary-container. A name that matches no role, including one written with underscores, is silently ignored.

Overrides bypass contrast checks

Generated roles sit at tones chosen to meet WCAG AA against their pairings. An override is used verbatim, so check it yourself with MaterialDesignColors.meets_aa.

Configuration files#

A theme can live in docs/.materialdocs.toml instead of your make.jl:

[theme]
name = "my-theme"
seed = "#1565C0"
secondary_seed = "#00897B"

[theme.fonts]
display = "Fira Sans"
body = "Source Sans 3"
code = "JetBrains Mono"

[theme.shape]
corner_radius = "default"    # sharp | default | rounded | pill

[theme.custom_colors]
surface = "#FAFAFA"

This file is picked up automatically. When Material3() is called without a theme argument, or with theme = :default, MaterialDocs looks for docs/.materialdocs.toml (or docs/materialdocs.toml) and loads it if present. The path is relative to the directory the build runs from, so run julia --project=docs docs/make.jl from your package root. Passing any other theme — a built-in name or a ThemeConfig — takes precedence over the file.

This is the format the Theme Editor produces, so the usual workflow is to design a theme visually, save the file, and never touch make.jl at all.

Read and write these files directly with load_theme and save_theme; find_theme_toml performs the search.