Skip to content
mdlake

Site types

The four site types, their slots, the page modules and reading rules, the case-study layout, the end-of-page block, cards and layout primitives. For developers who plan or change a site's pages.

On this page

A site type is a starting design for a kind of site. It names the slots that the site fills with lake content, and a recommended list of pages and modules. A module is one block on a page, such as a hero or a feature grid. The plan chooses the modules. The kit draws them.

The site type files live in the plugin, in ruleset/site-types/. They are not copied into the site. plan-site and check-lake read them.

The site types

TypeForSlots
corporateA company siteabout, contact, privacy (documents, required); services (collection, required); home, faq (documents); cases (collection); logo, ogImage (assets)
landing-pageOne page about one offeroffer (document, required); benefits (collection)
campaignA campaign message with supporting sectionsmessage (document, required); sections (collection)
docsDocumentation or a handbookdocs (collection, required, needs a folder); home (document); logo, ogImage (assets)

A slot is filled by a binding in lake.config.ts. The binding says which document, collection or asset. The lake is never asked to carry tags or folders for a slot, and two sites can bind the same lake differently. check-lake suggests candidates by looking at titles, folders, tags and summaries.

For assets, logo and ogImage need alt text. The header shows the site name as text when no logo is bound. og:image falls back to the page’s own image.

The site name (site.name) is not a slot. It comes from the plan, usually from the About document or the brand assets, and it is never the workspace slug.

Modules

Every module has a module name, a from source, and two optional site-chrome fields: heading (the heading text) and id. Headings and button labels are chrome and may be written in src/pages.config.ts. All other words come from the lake.

from says where the module reads:

fromReads
{ document: 'about' }The document bound to that key or slot. A list such as ['home', 'about'] uses the first that exists.
{ collection: 'services' }The documents of that collection, in its order.
{ current: true }The document of the detail page the module is on.
ModuleShowsOptionsKind
heroA headline, a subline, an image and buttonsheadline, subline (reading rules or generated copy), image: false, actions: [{ label, href, style: primary or secondary }]Shaped or generated
featureGridA grid of cards, one per documentimages: false, limit, link: { label, href }, level: 1 or 2Shaped
stepsA numbered list of stepsrules (sections, list), max (default 4)Shaped
testimonialA quote with its attributionrules: ['attributedQuote'] by default, add 'quote' to accept one without a nameShaped
statsFigure tilesmax (default 4), min (default 2)Shaped
faqQuestions and answersmaxShaped
ctaA text, a link and a buttonlabel (default “Read more”), text (generated copy)Shaped or generated
richTextThe whole body of a documentheader: false, exclude, readingTime: trueRendered
caseHeaderTitle, summary, small logo, facts and hero image of a casenoneShaped
docsPageA complete documentation pagetoc, search, lastUpdated, pager (each false to switch off), collectionRendered

What each one needs from the lake:

  • hero reads the from document. The headline defaults to the lead’s first sentence (up to 90 characters), then the summary. A bare title is only a headline when the rule list says title.
  • featureGrid gives a card its title, a summary (else the first sentence of the lead) and its image. A card whose document has a page is one link. A card without an image has no media area.
  • steps needs a document with two or more sections that start with a paragraph, or a list.
  • testimonial needs a quote with an attribution. On a collection it takes the first one it finds.
  • stats needs two or more figures in one document, such as a results list with a number or bold text per item. One figure alone is hidden and reported.
  • faq needs question headings (level 2 to 4, ending in a question mark) followed by their answer, or a paragraph that starts with a bold question that ends in a question mark.
  • cta needs a document that has a page. Its text is that document’s summary, else its lead sentence. The label is yours.
  • richText with header: false shows no title, summary or lead image. exclude drops what other modules lift out of the body: quote (the testimonial), stats-section (the stats), images and facts (the case header) and related (a closing “Related” line that the end block shows). The plan refuses an exclude without the module that shows the content, so that nothing disappears. readingTime: true shows a reading time computed from the text that is rendered. It is hidden under 40 words and when the text states its own.
  • caseHeader is for detail pages (from: { current: true }). Facts come from a Customer: x list or a two-column table under a heading such as “At a glance” or “Facts” (at most 6, only what the document has).

Do not put two modules with the same content on one page, such as a Steps module over the document that feeds the hero. Leave a module out, and record a content gap, when the lake cannot fill it.

Reading rules

A shaped module reads a document through reading rules. A rule list is tried in order and the first rule that finds something wins. If none finds anything, the module renders nothing and records a “Content fit” warning. A missing piece never fails the build.

RuleFinds
titleThe document title.
summaryThe summary.
leadThe first paragraph or lead text.
leadSentenceThe first sentence of the lead, if it is at most 90 characters.
quoteThe first quote.
attributedQuoteThe first quote with an attribution.
sectionsThe sections (headings with their text).
listThe items of the first top-level list.
qaQuestion and answer pairs.
statsFigures.
figureThe first image that has alt text.
imageThe document’s lead image.
section:<text>A section whose heading or slug matches the text, for example section:call to action.

lake-shape.mjs <site> <docId|path> shows what one document offers to each rule. plan-site uses it to choose modules.

Pages

src/pages.config.ts holds a pages object and a nav list. A page has:

FieldMeaning
pathThe route. It ends with /.
eachThe key of a collection. The page becomes a detail page, once per document, and its path has [slug].
titleOverrides the title the page would derive.
endThe end-of-page block for detail pages. See below.
modulesThe modules, in order.

Navigation links have label and href. Add group to put a link under a heading in the footer, and footer: false to keep one out of the footer. The footer lists each address once, in columns. The contact page and the privacy page have their own places in it.

Page titles and meta descriptions come from the document: its title, and its summary as plain text. sitemap.xml, robots.txt, Open Graph tags, a 404 page and redirects from redirects.json come from the kit.

Note

src/pages.config.ts is in the generated zone. plan-to-config writes it from plan.md. To change it by hand you either keep plan.md in step or adopt the file. See Ownership zones.

Collection order

A collection’s sort is one of these:

SortOrder
titleBy title. The default, and stable.
manualThe document IDs in order: [...] first, then the rest by title.
orderBy the documents’ order frontmatter number, lower first. Documents without one follow, by title. Only documents count here: a folder’s _folder.yaml plays no part in a collection.
updated-desc, updated-ascBy update date. Use only for news and blog, because the order changes whenever someone edits a document.

The case-study layout

A collection of customer stories gets a layout of its own on its detail pages. You write a plain richText page over { current: true } and plan-to-config expands it, and prints a note, when the collection’s key, folder or tags say case, customer, story, reference or similar.

  1. caseHeader: title, summary, a small logo, a facts block and the hero image.
  2. stats with heading “Results”: the figures of the results list, as tiles.
  3. testimonial: the first quote with a name.
  4. richText with header: false and exclude: ['quote', 'stats-section', 'images', 'facts', 'related']: the body without what the modules above lifted out.

A part that the document lacks is left out and reported under “Content fit”. Set layout: plain on the page in plan.md to keep a plain body.

News and blog collections get readingTime: true on their richText detail pages.

The end of a detail page

Every detail page ends with an end block: a call-to-action panel and two or three more documents of the collection. It is on by default.

  • The call to action uses the brief’s primaryCta by default. plan-to-config writes it into the defineLake call of lake.config.ts as site: { name, cta: { label, href } } (site chrome, generated zone; site.config.ts stays yours). No site.cta is written when the brief names a content gap for the primary call to action. The label and the target come in this order: the page’s end.cta; the site’s site.cta; the contact document as the target, with the title of the page the button leads to as the label. The default’s label goes with the default’s target only, so a page that sends the button elsewhere shows that page’s title and not another destination’s label. The text under the heading is always the summary of the target page.
  • The “more” cards are, in order, the documents that the current one points to in a closing Related: [..](lake://..) line, then documents with the most shared tags (not the tags every document has), then the documents that follow it. Only documents that have a page appear. The heading is “More” and the list heading, or “Related” when the cards go beyond the collection.
Setting in pages.config.tsEffect
end: falseNo end block.
end: { cta: false }No call-to-action panel.
end: { more: false }No more cards.
end: { cta: { label, href }, more: { heading, count } }Set them. count is from 1 to 6.

A page that already has a cta module shows no second call to action. A docs page has no end block, because the pager leads on.

Documentation sites

The docs type turns a lake folder into a handbook. This guide is built that way. The lake’s folders are the sidebar’s sections and its documents are the pages.

The plan has two pages and one collection:

YAML
slots:
  docs: { folder: Docs, sort: order }
YAML
key: home
path: /
nav: Docs
modules:
  - { module: docsPage, kind: rendered, from: { collection: docs }, heading: Documentation }
YAML
key: docs
path: /[...slug]/
each: docs
modules:
  - { module: docsPage, kind: rendered, from: { current: true }, collection: docs }

[...slug] is only allowed for docsPage. It puts the folders in the address: Docs/Getting started/Installation.md becomes /getting-started/installation/. Use /docs/[...slug]/ when the documentation lives under a prefix and the site has other pages. A site has one documentation page. docsPage is the whole page, with no other module on it.

The docs collection needs a folder. The folders below it are the sidebar’s sections. Two documents at one address stop the build, and check-bindings says which.

What the kit draws

  • Sidebar of the whole collection, with collapsible sections. The current page is highlighted and its section is open. Below the lg breakpoint it is a drawer behind a Menu button.
  • Breadcrumbs, title, summary and the rendered body, then “last updated” (a UTC date from the lake) and previous and next links.
  • On this page, a table of contents from ## and ### headings. It is sticky from the xl breakpoint and a collapsible block above the text on smaller screens.
  • Heading permalinks, scrolling tables, and code blocks with highlighting, a language label and a Copy button.
  • Callouts. A block quote that starts with a bold label becomes a panel with an icon.
  • Search. astro build runs Pagefind over the docs pages and writes dist/pagefind. Ctrl or Cmd plus K opens the search dialog. It works in astro preview and on any static host. astro dev has no index and says search needs a build.

Order

Every item in a folder has its own order, a number where lower comes first. A page has it in its frontmatter. A section has it in a _folder.yaml inside its folder (order: 20). Pages and sections are sorted together, in one list per folder:

  1. Items with an order, lowest first.
  2. Then the items without one, by title (pages) or name (sections).
  3. Items with the same number, or the same title or name, are sorted by title or name.

A section never takes a number from the pages inside it. A section without its own _folder.yaml sorts after the numbered items of its folder, even when all its pages have numbers. check-lake reports such a folder when its siblings have numbers. Previous and next follow the sidebar.

Editors do not write these numbers. They drag documents and folders in the CMS tree, and the CMS writes the numbers to the lake (Reorder documents and folders). The format is in Lake format. In preview builds the numbers come from the CMS, so a new position shows before it is published. If the docs collection’s folder is a subfolder of the lake (Docs), the sections are the folders below it, and their numbers are found by their full path in the lake.

Sites built before kit 0.20.0 sorted a section by the lowest order of any page inside it. See Upgrade and migrate.

Writing conventions to give your editors

ConventionRule
StructureA folder is a section. Nest at most two levels. A document in the docs folder itself is a top-level page.
OrderDrag pages and folders into place in the CMS. A new folder has no place until someone does, and sorts last.
Title and summaryEvery page has a title and a one-sentence summary. It shows under the title and in search results.
HeadingsUse ## and ### below the title. Keep heading text unique on a page. Duplicates get -1 ids, and links still work.
Callouts> **Note:** text, > **Tip:**, > **Important:** or > **Warning:**. Case does not matter and the colon may be inside or outside the bold text. Anything else is an ordinary quote.
CodeFenced blocks with a language, for example ts, bash or csharp.
LinksBetween pages with lake:// links, which the editor inserts. A link to a heading adds #heading-text.

Callouts are not limited to docs. Every rendered body shows them. They stay ordinary quotes in the CMS and in the lake.

The docs home serves the brief’s primary call to action itself (“Read the docs”), so the plan needs no hero. A docs site has no generated copy, so live mode costs nothing. Recommend live when editors publish docs continuously and snapshot when releases pin the docs. Other pages, such as a landing page, go in a folder outside the docs folder so that they are not in the sidebar.

The kit parts are overridable: modules.docsPage, parts.DocsSidebar, parts.DocsSearch and parts.CodeCopy. See Custom components.

Layout and spacing

All modules share one layout system, made of design tokens. Do not hard-code sizes in your own components. Use the primitives.

PrimitiveMeaning
.mdl-wideA centred column at container-site width, 72rem by default. For grids, heroes, header and footer.
.mdl-columnA centred column at container-column width, 42rem by default. For running text, headers and quotes.
.mdl-cardsA card grid. A last row with fewer cards is centred. Four cards make a 2 by 2 grid.
.mdl-tilesTiles for figures and facts that fill their row, whatever their number.

Vertical rhythm comes from the spacing tokens. Each one has a Tailwind utility such as mt-section or gap-block.

TokenUse
--spacing-sectionBetween modules of a page.
--spacing-blockBetween the parts of a module.
--spacing-pageAbove the first module.
--spacing-tightInside a part.
--spacing-gutterAt the sides.

Images are capped. An image in running text is centred and at most --spacing-image-body (20rem) tall, or --spacing-image-square (16rem) when it is square or nearly so. A logo is at most --spacing-image-logo (5rem). The case hero is at most --spacing-image-hero (28rem). Images are scaled and not cropped to get there. An image with crop: false is shown whole.

The tokens live in the @theme block of src/styles/global.css. They mirror the tables of design.md. Change them in design.md, then use the regenerate skill. See Custom components.

What’s next