Head and Component Partials

One partial supplies everything a Leed page’s <head> needs: the stylesheet, the metadata behind your social cards, the page-identity constants the trackers read, and every script the site runs on. You call it once, inside your own <head>, and you supply the <title> yourself.

<head>
    <title>{{ title }} | {{ siteTitle }}</title>
    <meta name="viewport" content="width=device-width">
    {{> leed/head }}
</head>

Alongside it, three components are meant to be called from your templates: a drop-in pagination control, a reading-progress bar, and a modal video player. Everything else under leed/ is internal — the full map of what Leed injects is the Leed partial index.

leed/head

The partial emits in file order, and that order is the load order in the browser. Every row below is conditional on something, so a missing tag is nearly always a missing input rather than a bug.

OutputConditionRead from
{{> header-includes }}header-includes.hbs exists in src/_includes/your file
<link rel="preload" as="style"> + <link rel="stylesheet">the site is built with Tailwindthe hashed stylesheet path
leed/metadata/siteMetadataalwayssite and page fields
leed/metadata/twitterCardalwayssite and page fields
leed/metadata/ogCardalwayssite and page fields
{{{ JSON-LD }}}always called; emits a script only for the blog page typepage type, page and author data
const pid / const ptalwaysuniquePageId, pageTypeId
searchIndexId, searchIndexHash, searchDocumentsHashLunr search only, and only when the docs set has a search index iddocumentationConfiguration.searchIndexId
Alpine, js-cookie, zoomable, fingerprint, privacy, timer, trackeralwayscdnjs and your site’s /static/js/
webmcpentitlements.mcp is onsite entitlements
leed-utils, documentationalways/static/js/
live-search or searchwhichever the site’s plan supportsthe docsLiveSearch feature
Turnstile’s api.jsdeployment.public.turnstilePublicKey is setdeployment config
{{> leed/docs/style }}documentationConfiguration.logo is setthe docs logo paths

Your own tags come first

Line 1 is a templateExists "header-includes.hbs" probe. If that file is present in src/_includes/, it is included before anything Leed emits — favicons, a web manifest, theme-color metas, verification tags, a font preconnect. It needs no flag and no CMS setting; creating the file is what turns it on. Its full treatment, including why it is the only hook that reaches both marketing pages and documentation pages, is at Customizing the <head>.

Stylesheet

The Tailwind sheet is emitted as two tags — a <link rel="preload" as="style"> and a plain <link rel="stylesheet"> — not the combined rel="preload stylesheet" you may be used to.

Metadata

Three partials run in order: leed/metadata/siteMetadata, then leed/metadata/twitterCard, then leed/metadata/ogCard. If a social card comes out wrong, these tables are how you trace the tag back to the field that fed it.

leed/metadata/siteMetadata

Tag emittedValue read fromFallback
<link rel="sitemap"> and the JSON/RSS/Atom feed linkssiteTitle, siteUrlnone
<link rel="icon">faviconomitted when unset
<meta name="title">titlenone
<meta name="keywords">keywordsomitted when unset
<meta name="description">summarysiteDescription
<link rel="canonical">canonicalUrlthe page’s own URL; suppressed entirely on stub pages
<meta property="published">publishedAt, ISO-formattednone

leed/metadata/ogCard

Tag emittedValue read fromFallback
og:locale, og:site_name, og:sitesiteLocale, siteTitle, siteUrlnone
og:titletitleogCard.title
og:descriptionsummaryogCard.description
og:imagefeatureImageogCard.image
og:image:width / og:image:heightogCard.imageWidth / ogCard.imageHeight800 / 413
og:urlthe page URLnone
og:type plus the article:* tagsarticle when the page type is in feedswebsite

leed/metadata/twitterCard

Tag emittedValue read fromFallback
twitter:cardogCard.cardTypesummary
twitter:urlthe page URLnone
twitter:sitetwitterIdomitted when unset
twitter:imagefeatureImageogCard.image, then logo
twitter:titletitleogCard.title
twitter:descriptionsummaryomitted when unset
twitter:label1 / twitter:data1reading time, when wordCount is setomitted when unset

The og:image and twitter:image values go through the Cloudflare Images social variant, which is why the default 800 × 413 dimensions are stated: they describe that variant. A site supplying its own ogCard.image is not going through it and should set its own width and height, or the two tags describe someone else’s file.

Immediately after the three partials, leed/head calls the JSON-LD shortcode. It emits a real <script type="application/ld+json"> block only for pages whose page type slug is blog — a BlogPosting, or Blog for a paginated list page, with the visible authors attached. Every other page type gets an HTML comment saying structured data was not generated for it. What those tags and that block mean to a crawler is covered at Social Cards and Structured Data; the site title, description, logo and social handles they all read are set on Site Identity and Branding.

Inline page identity

Two constants are written into an inline <script> on every page:

<script>
    const pid = "list-bd9be3d0";
    const pt = "u05nxr";
</script>

pid is the page’s uniquePageId and pt is its page type id. The analytics tracker reads them to attribute a pageview, and the search client reads pt to scope a query to the right documentation set. On the Lunr search path only, three more constants join them — searchIndexId, searchIndexHash and searchDocumentsHash — because a prebuilt index is a build-time artifact that has to be matched to the page that loads it.

Scripts

Alpine and js-cookie come from cdnjs at pinned versions (currently Alpine 3.16.3 and js-cookie 3.0.8); everything else is served from your own site under /static/js/ with a content hash in the filename. The order matters: leed-utils and documentation register the Alpine components that the markup further down the page binds to, so nothing you write should load Alpine a second time.

webmcp.js is the reader-facing site agent and loads only when the workspace has MCP enabled. MCP is free on every plan — the flag is a workspace switch, not a tier gate. See Site AI Agent.

And finally the docs logo variables

The last thing leed/head emits is {{> leed/docs/style }}, a small <style> block that turns documentationConfiguration.logo into a CSS custom property:

<style>
:root {
  --nav-logo-url: url('/static/images/logo/leed-logo-p-light.svg');
  @media (prefers-color-scheme: dark) {
    --nav-logo-url: url('/static/images/logo/leed-logo-p-dark.svg');
  }
}
</style>

.nav-logo in the header and .logo-footer in the footer both resolve bg-[image:var(--nav-logo-url)] against it.

The emitted <head> of a real documentation page
<link href="/static/css/tailwind.css?v=8ebca811…" rel="preload" as="style">
<link href="/static/css/tailwind.css?v=8ebca811…" rel="stylesheet">
<link rel="sitemap" title="Leed - Sitemap" type="application/xml" href="https://leed.ai/sitemap.xml">
<link rel="alternate" title="Leed - RSS Feed" type="application/rss+xml" href="https://leed.ai/rss.xml">
<link href="/static/images/favicon-…​.svg" rel="icon" as="image">
<meta charset="UTF-8">
<meta name="generator" content="Leed Eleventy">
<meta name="title" content="Documentation List">
<meta name="description" content="…">
<link rel="canonical" href="https://leed.ai/docs/">
<meta property="published" content="2026-09-01T12:00:00.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Documentation List">
<meta property="og:locale" content="en-us">
<meta property="og:site_name" content="Leed">
<meta property="og:url" content="https://leed.ai/docs/">
<meta property="og:type" content="article">
<!-- JSON+LD not generated for docs type of page -->
<script>
    const pid = "list-bd9be3d0";
    const pt = "u05nxr";
</script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.16.3/cdn.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-cookie/3.0.8/js.cookie.min.js"></script>
<script src="/static/js/l.zoomable.5.min.js"></script>
<script src="/static/js/l.fp.1.min.js"></script>
<script src="/static/js/l.priv.2.min.js"></script>
<script src="/static/js/l.timer.2.min.js"></script>
<script src="/static/js/l.whisper.24.min.js"></script>
<script defer src="/static/js/l.webmcp.3.min.js"></script>
<script src="/static/js/l.utils.31.min.js"></script>
<script src="/static/js/l.docs.22.min.js"></script>
<script src="/static/js/l.livesearch.2.min.js"></script>
<script defer src="https://challenges.cloudflare.com/turnstile/v0/api.js"></script>
<style>:root { --nav-logo-url: url('…'); }</style>

leed/components/pagination

The drop-in list control: a range line, numbered links with ellipsis, and two arrows.

{{> leed/components/pagination }}               
{{> leed/components/pagination data=myAlias }}  

It renders nothing at all when pageCount <= 1, so a list that fits on one page needs no guard around the call. The markup it produces gives you these hooks:

HookWhat it is
.pagination-page-rangethe “Showing 1 to 10 of 42 results” line
nav.pagination-listthe numbered-link container
a.pagination-activethe current page — an <a> with no href and aria-current="page"
a.pagination-inactiveevery other page number, and both arrows
a.pagination-ellipsis > i.ellipsisthe gap marker in a long run
span.arrow > i.back-arrow / i.next-arrowthe two arrow glyphs

The list object this reads, and every collection it can come from, is documented at Collections and Pagination Data; the three helpers underneath it have full signatures at Pagination Helpers.

leed/components/scroll-status-indicator-horizontal

A reading-progress bar for one scrollable element.

{{> leed/components/scroll-status-indicator-horizontal scrollBarId="blog-copy" }}

The id names the element whose scroll is tracked, and it is handed straight to the Alpine handler scrollStatusIndicatorHorizontalHandler(id). The outer <div> is the track and the inner one is the fill, and the fill is scaled rather than widened — transform: scaleX() on an origin-left element, recomputed every animation frame. Setting width that often would re-lay-out the bar on every frame of a long article; a transform is composited and stays off the main thread. Do not write CSS that sets a width on the inner element, and leave origin-left alone: those two are what make scaleX(0…1) read as 0–100%.

Its three colors come from --scroll-status-indicator-horizontal-start-bg, -partial-bg and -complete-bg, which are ordinary site tokens — see the Site Token Contract.

leed/popup-video

A button that opens a modal Cloudflare Stream player, dismissed by the backdrop or the Escape key.

{{> leed/popup-video
    buttonLabel="Watch the demo"
    buttonClass="btn btn-primary"
    videoAssetId="abc123"
    videoSrc="https://customer-xxxx.cloudflarestream.com/VIDEO_ID/iframe"
    data-id="hero-video" }}
PartialParameterRequiredEffect
components/paginationdatanothe pagination object to render; defaults to @root/list
components/scroll-status-indicator-horizontalscrollBarIdyesthe id of the element to track; without it nothing renders
popup-videobuttonLabelyesbutton text, emitted raw so an icon or other HTML is allowed
popup-videovideoAssetIdyesthe asset id, used for the play and pause calls
popup-videovideoSrcyesthe Stream iframe URL
popup-videobuttonClassnoclasses on the trigger button
popup-videodata-idnotracking attribute on the trigger button

The <iframe> src is bound, not static: it stays empty until the modal opens and is then built by buildUrl videoSrc "autoplay=true", so no page pays for a video nobody clicked. Closing the modal — backdrop, Escape or a click outside the player — calls leedPopupVideoPause(videoAssetId), which is why the asset id is required rather than cosmetic. Stream ids come from the asset library; see Video, Audio and Document Assets.

ESC