There are eight places a Leed site takes your own CSS, JavaScript or <head> markup, and every one of them is a file in your repository. There is no code field in the CMS, no per-page CSS box and no <body> hook. This page is the catalog.
| Point | Path in your repo | How it activates | Cache-busted? | Tier |
|---|---|---|---|---|
| Head hook | src/_includes/header-includes.hbs | The file existing | n/a — inline in the page | Free |
| Tailwind CSS | tailwind/**/*.css, imported from tailwind/site.config.css | site.config.css existing | Yes — content hash on the stylesheet | Free |
| Hand-written CSS | src/static/css/*.css | You link it from a layout | No | Free |
| Hand-written JS | src/static/js/*.js | You link it from a layout | No | Free |
| Extra highlight.js language | src/static/js/hljs-<lang>.js | Naming the language in the CMS | Yes — content hash | Enterprise to add |
| Mermaid palette | src/static/js/mermaid.theme.json | The file existing | n/a — read at build time | Free |
| Layouts and partials | src/_layouts/*.hbs, src/_includes/*.hbs | Referencing them from a page or layout | n/a | Free |
| A Leed template you took over | leed site eject destinations under src/_includes/ | The file existing | n/a | Varies — see below |
src/_includes/header-includes.hbs — your tags in every page head
This is the supported hook for your own <link>, <style>, <meta> or <script>. It is rendered as the very first thing inside Leed’s head partial, before Leed’s own metadata, on every layout that includes it.
It is activated by a pure file-existence check. There is no CMS flag to set, no tier gate and nothing to enable — unlike every other custom template, which is gated on a useCustomTemplate flag. Creating the file is what turns it on; deleting it turns it off.
<link rel="preconnect" href="https://analytics.example.com" crossorigin>
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/favicon-32.png">
<link rel="manifest" href="/static/site.webmanifest">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#171717">Keep it to things that must be true on every page of the site, documentation included. That is the whole reason to put something here rather than in site-template.hbs: the documentation set uses the charlie layout, which site-template.hbs does not wrap, so anything declared there reaches your marketing pages alone. Page-specific tags belong in the page’s own layout.
The full head order, and how this partial sits inside it, are on customizing the <head>. The two pages describe the same file from opposite ends: this one says it is an injection point, that one says what else is in the head around it.
CSS through the Tailwind build
The primary route, and the one to prefer. Put your stylesheets under tailwind/, import them from tailwind/site.config.css, and the build compiles them with Tailwind, tree-shakes the utilities you did not use, and writes one hashed file that every page links.
/* tailwind/site.config.css */
@import "tailwindcss" source("../src");
@import "./site/theme.css" layer(base);
@import "./docs/color-theme-mybrand.css";
@import "./docs/mermaid.css";The compile, the four lines Leed injects into that file, and the cache-busting hash are all on the Tailwind build. Which of those imports carries a layer(...) and which deliberately does not is a cascade decision, covered on cascade layers and overriding Leed.
Hand-written static CSS and JS
Files under src/static/css/ and src/static/js/ are copied to /static/ untouched. Link them yourself from a layout:
<link rel="stylesheet" href="/static/css/print.css">
<script src="/static/js/widget.js" defer></script>This is permitted, and the trade-off is the whole reason to prefer the Tailwind route.
The full caching rules, including which paths are exempt, are on static files and caching.
Extra highlight.js languages
Leed’s code blocks ship with a large set of languages already highlighted. Adding one that is not in that set takes two steps, one in the CMS and one in your repository.
- Name the language in the CMS, under
Settings → General. It is stored on the company record, not on the documentation configuration. - Put the file in your repository at
src/static/js/hljs-<lang>.js, using exactly the name you entered.
At build time, Leed looks for that file, hashes its contents, and appends /static/js/hljs-<lang>.js?v=<hash> to the scripts that load with the highlighter. A missing file is a warning, not an error — the build carries on and logs:
Extra language nix not found: /path/to/repo/src/static/js/hljs-nix.jswhich is exactly what you will see if the name in the CMS and the filename in the repository have drifted apart.
Which languages your code blocks can already highlight without any of this is on code blocks; the CMS side of the setting sits beside the other appearance fields documented in themes, fonts and code themes. If the CMS returns a 402 when you add one, the upgrade contract is on when a feature is gated.
The Mermaid palette
src/static/js/mermaid.theme.json sits in the same directory as the highlighter’s language files, for the same reason: it is a build-time input that belongs beside the CSS it has to agree with, rather than a publish cycle away in a settings form.
Spell that directory carefully — static/js/, not static/javascript/. Both exist in a Leed site, and a palette in the wrong one is indistinguishable from a site that never wanted one: no warning, no error, diagrams in Mermaid’s own colors. The file’s shape and every rule about what may be a var() are on theming diagrams.
Layouts and partials
Anything in src/_layouts/ and src/_includes/ is yours. That is full control of the markup, and therefore full control of the classes your CSS has to match — which is usually a better lever than a more specific selector.
Two files in that directory carry a specific meaning worth knowing here: docs-header.hbs and docs-footer.hbs, which replace the documentation chrome. Like header-includes.hbs, both are detected by a plain file-existence probe. Unlike it, both are gated:
How templates and partials fit together is on how templates work. The CMS-side configuration that fills the documentation chrome — the menus, the header button, the light and dark logos — belongs to the documentation set rather than to this page.
Taking over a Leed template
leed site eject <name> copies a Leed-owned template into your repository, where the file’s existence is what makes the build use yours instead. Five templates are ejectable:
| Name | What it controls | Requires |
|---|---|---|
stub-template | Recommendation card markup, cloned per recommendation in the browser | Growth |
unsubscribe | The email unsubscribe page | — |
unsubscribed | The post-unsubscribe confirmation page | — |
docs-header | The documentation header, inside Leed’s positioned wrapper | Starter |
docs-footer | The documentation footer | Starter |
leed site eject with no argument lists them, marking each customized or Leed default, and it will not hand you a template your plan cannot render — a file the renderer ignores is worse than no file.
One asymmetry is worth carrying, because it changes how you treat the file you get. The footer source is what renders, so ejecting gives you a byte-identical starting point. The header source is a complete, working example that demonstrates the real API rather than a snapshot of the current output, because Leed’s own header wrapper fills itself with a per-layout variant. Diff the header eject against your rendered page before assuming they match.
The registry and what each template owns are on overriding Leed templates; the command itself, with its flags and its output, is leed site eject.
What is not an injection point
The honest list, because these are the things people try.
| What people look for | Does it exist? | What to use instead |
|---|---|---|
| A CSS field in Page Settings | No | A class on the element, styled from tailwind/ |
| Per-page custom CSS | No | A page-type-specific layout, or a class the page body sets |
<style> or <script> in a page body | No — bodies are Leed Markdown, and raw HTML other than <br> is rejected | header-includes.hbs, or a layout |
| A code editor in the CMS | No — template files are not editable in the CMS | Clone the repository and edit locally |
A <body> injection hook | No — only the head hook exists | header-includes.hbs with a deferred script |
A per-page <head> field | No | The page’s own layout |
Two of the stylesheet destinations in the table above are worth naming as concrete examples rather than as paths, because both are real files in leed.ai’s own repository and both are imported deliberately differently: tailwind/partials/alerts.css is imported into layer(components), for the reasons on theming alerts, and tailwind/docs/mermaid.css is imported unlayered, for the opposite reason on theming diagrams. This page says where things go; those pages say why.