Example
The template's handbook: seven chapters, every component designed, every contrast measured.
Example is a template for QuartzControl. A template here is a design: colours, typefaces,
spacing, the page grid, the arrangement of the components, the settings of the plugins —
everything that makes a website, except its content. It travels as one file with the extension
.qtpl and is imported into a project of your own in QuartzControl under Templates. After that your
own site looks like this one, with your own notes in it.
The template is meant to be used and to be rebuilt. Nothing about it is hidden: every colour and every measure is a variable you can change in QuartzControl. Every stylesheet — the file in which the look of a component is described — belongs to exactly one component and is named after it.
Why this site exists
A design cannot be looked at in the abstract. An explorer needs folders, a table of contents needs headings, a graph needs links, a table needs rows. This site supplies all of it: it is the template’s handbook and its demonstration at the same time. Every element Obsidian can write appears here at least once — and whoever wants to check an adaptation of their own finds here the page on which the difference shows.
The three decisions
Everything else follows from these three.
1. The width decides the grid
The page grid — called a frame in Quartz — divides the page into twelve equally wide columns and gives the text six of them. How wide a line of text gets is thereby decided, in exactly one place. There is no second limit on the paragraph that could fall out with the first. What that means for tablet and smartphone stands under 6.5 – Page grids.
2. Colours are measured
Contrast is the ratio of brightness between text and ground. The guideline for accessible websites, WCAG, demands at least 4.5:1 for normal text. This template measures 93 colour pairs in light and dark on every run — every text-on-ground combination, the thirteen callout colours, the colours of the code syntax theme — and stops if one of them falls below. Quartz’s own colours do not pass this check: of its thirteen callout types, eleven fail in light mode. That is why they are restated here. More under 6.2 – Colours and contrast.
3. No stylesheet sets a colour or a measure itself
Colours and measures stand in 66 variables, not in the stylesheets. That is not cosmetics: after the import, variables stay editable in QuartzControl under Styles → Variables; a value that stands in a stylesheet file does not. What does stand as a number in a file is named and justified — the callout and syntax colours, because they mean a status and are not a palette; the hairlines; and the breakpoints, because a media query cannot read a variable. More under 6.4 – Variables.
What the template does not contain
No content. The notes of this site do not travel with the template; they live in an Obsidian vault of their own. Whoever imports the template gets the design and writes their own pages into it.
No configuration of somebody else’s. A template package carries the design — stylesheets, typefaces, images, snippets, plugin entries — and leaves standing everything a project has decided for itself. Which part carries what stands under 6.8 – The template package.
What this is and how it fits together.
note
The default type.
abstract
Aliases:
summary,tldr.
info
A factual addition.
todo
What is still to be done.
tip
Aliases:
hint,important.
success
Aliases:
check,done.
question
Aliases:
help,faq.
warning
Aliases:
caution,attention.
failure
Aliases:
fail,missing.
danger
Alias:
error.
bug
A fault in the program.
example
An example.
quote
Alias:
cite. Takes the accent colour of the page rather than one of its own.
What this template does differently
Out of the box Quartz sets twelve colours for these thirteen types and lets note keep the
base colour. Measured against the light page ground of this template, eleven of the thirteen
fail the WCAG threshold of 4.5:1 — note reaches 3.23:1, question only 2.14:1. Only example
and quote get through. In dark mode the picture nearly inverts: four fail there, and example is
one of them (2.81:1). The red of danger, failure and bug fails in both modes, at 4.20 and
4.16:1.
In this template all thirteen are set anew, once per mode, and measured on every run: against the page ground and against the callout’s own tinted surface. The hue is kept — blue stays blue — so that the type is still recognisable by its colour.
The numbers are under Colours and contrast.
Every element as source and rendered result.
A .base file has up to three sections:
filters:
and:
- file.hasTag("formatting")
- file.ext == "md"
formulas:
Area: file.folder
views:
- type: table
name: All formatting pages
order:
- file.name
- description
- formula.Area
sort:
- property: file.name
direction: ASCfilters
Which notes are taken in. Joined with and, or and not. Common expressions:
| Expression | Meaning |
|---|---|
file.hasTag("x") | carries this tag |
file.inFolder("x") | sits in this folder |
file.ext == "md" | is a Markdown file |
property != null | field is set |
formulas
Calculated columns. They are available afterwards as formula.Name — handy for pulling two fields
together or shortening a path.
views
A base can have several views of the same data. The Quartz plugin knows table, list, cards,
board and gallery; the default is table.
| Key | Effect |
|---|---|
type | kind of view |
name | label of the view |
order | which columns, in which order |
sort | ordering, with property and direction |
groupBy | grouping by a field |
limit | maximum number of entries |
Where this is edited
Obsidian has an interface for it — the YAML file is only the store. Anyone writing it by hand should open it in Obsidian afterwards and check it.
Linking to one
A wikilink to such a file needs the extension:
[[en/3-obsidian-formats/01-bases/Formatting-Pages.base|Formatting pages]]
[[en/3-obsidian-formats/02-canvas/Structure of the template.canvas|Structure of the template]]Quartz keeps the extension in the address of the page (…/formatting-pages.base), unlike with
Markdown files. Without it the link leads nowhere — and that does not stand out, because an
unresolved wikilink gets no special marking here.
Bases, Canvas, Excalidraw — three page types of their own.
draft: true — the page is not built
A draft stays in the vault and in Obsidian, but Quartz skips it when building. There is no file for it in the output, no entry in the navigation, no search result; a link to it leads nowhere. That is the way for everything not finished yet.
The plugin Remove draft takes care of it. If it is switched off, drafts are built like every other page.
unlisted: true — built, but invisible
An unlisted page exists on the site and can be reached through its address, but it appears in no folder overview, no tag page and no search result. Whoever knows the address can read it; whoever does not will not find it. That is the way for a page you only pass on by link.
The two test pages
Two pages in this chapter demonstrate exactly that:
- A draft — this link leads nowhere, because the page is not built. If it does appear, the plugin is off or the field is misspelt.
- An unlisted page — this link works, but the page cannot be found in any list on this site.
Unlisted does not mean secret
The page lies on the web space like any other. A search engine can find it as soon as a link to it stands anywhere. What really is not meant to be public stays a draft.
Frontmatter: draft, date, alias, cover image.
Switched off in this template
Since 2026-09-20 the navigation leads through this handbook, no longer the explorer. It is still designed, and its stylesheet travels in the package: whoever switches it on under Plugins finds it as this page describes it.
Switched on, the tree over all the notes stands on the left of the window. It is the component that took the most design work in this template, because depth is the actual problem.
Out of the box
Quartz indents every level and colours the active entry. On level three or four it is no longer possible to tell which parent folder a file belongs to — the indent alone does not carry the structure.
In this template
- A guide line per level. Every nested list gets a hairline to its parent folder. At four levels that is four lines for the eye to find its way back along. It stands under the tip of the arrow that opened the level, not beside it — and files begin on the same edge as the folders of their level, not in front of them. Both were a few pixels off for a while, for three different reasons, and three small errors in the same direction look like intent.
- Weight falls with depth. Level 1 is bold, level 2 semibold, from level 3 on normal and smaller. The head of a branch can therefore be found.
- From level five on there is no further indent, only the line — five indents leave no room for a word in a bar three columns wide.
- A symbol in front of every row. Folders carry a folder icon that looks different open and closed, files a file icon — both from the Lucide set. The row says what it is before it says what it is called.
- The active entry gets colour, semibold and a bar on the left — including when it is a
folder page. The plugin itself cannot do that: it sets its class on file rows only, a folder row
carries nothing but a
data-folderpath, and the current page stands in<body data-slug>. No selector can compare two attributes with each other — so the rule is built rather than looked for, by a layout box that outputs nothing but a<style>with this one page’s path in it. - The whole row is clickable, for files as for folders. On folders only the name was the link for a long time, while the hover surface ran the full width — it promised a target across the whole row and answered on a third of it. The arrow beside it still does the folding.
- The whole path to it is marked. Every folder the open page sits under colours its name and its symbol in the accent, and the guide line of the direct parent colours with it. Before, an unfolded folder looked exactly like one somebody had unfolded by hand ten minutes earlier — the trail from the root to the current page was invisible.
- Every disclosure arrow is the same Lucide chevron. Before, the head of the tree, every folder, the table of contents and the collapsible layout box drew four different ones.
- Long names are truncated rather than wrapped; three lines for a file name tear the tree apart.
- A long tree scrolls inside itself (60 % of the window height) rather than pushing the footer away, and fades out softly at the top and the bottom. The box carries exactly as much padding as the edge is deep: at the very top the first row therefore sits below the gradient rather than inside it, at the very bottom the last row above it. Both ends are read at full strength; only what is passing through goes soft.
- On a smartphone it is a drawer. The button sits in the app bar, becomes a cross when opened, the drawer travels in from the left over a darkened page, and the page itself does not scroll along meanwhile.
It was best seen under Callouts — the basic form — three folders deep, and a fully unfolded branch in the explorer.
Two languages, one tree
The explorer knows nothing about languages: it builds its tree from every page of the build, so
without help it would show the German and the English tree at once. The template solves that in
CSS. On a German page the en folder is hidden; on an English page everything except it is hidden,
and the folder’s own row is taken out so that the English tree stands at the top level rather than
one indent in.
The plugin’s own answer — a filterFn for the explorer — cannot be reached from here: it is taken
only from quartz.ts, and this project builds its layout from quartz.config.yaml. See
Where the two languages stop.
Which element actually scrolls
That had to be settled before the mask and the edge could sit in the right place at all. Quartz
gives the inner list max-height: 100% — and the two browsers resolve that differently:
| scrolling element | box | list | |
|---|---|---|---|
| Chrome | the inner ul | 540 px, no overflow | 540 px, 148 px of overflow |
| Firefox | the box .explorer-content | 540 px, 148 px of overflow | 688 px, no overflow |
Measured on the same page: 148 px of overflow in both, but on two different elements. Everything
this template hangs on the scrolling — the soft edge, the contained overscroll, the thin scrollbar
— sits on .explorer-content. In Chrome that was an element which does not scroll.
The list therefore gives up its cap (max-height: none; overflow: visible), and the box is the one
scroller in both browsers.
Two findings while building
The class names are misleading. Only the root list is called explorer-ul; every nested one is
called ul.tree-item-children. And .nav-file-title is the link itself, not its parent. The
first version of this file therefore styled into nothing — which was not visible, because Quartz’s
own indent looked similar enough.
When folded, Quartz caps the whole component at 1.2em. Anyone giving their heading a target size
of 44 px loses it entirely. This template therefore moves the folding to the content and leaves the
head standing.
What the plugin cannot do
The folders start folded, and the option against it has no effect
@quartz-community/explorer0.1.0 knowsfolderDefaultState: 'open'and dutifully writes the value into the markup — its own script simply never reads it. It takes the fold state solely fromlocalStorageand assumes folded for everything that is not in there. Open, therefore, are only the folders on the way to the current page.A stylesheet cannot cure that: “open” and “never touched” carry the same class, and CSS cannot tell the two apart.
The explorer itself — the whole component — is unfolded and stays that way; only on a smartphone does it start closed, since otherwise half the navigation would stand in front of the text.
And one that was self-inflicted
Exactly that went wrong for a while: a rule of this template gave the drawer button
display: gridwithout restricting it to narrow windows. Because everything here is unlayered, that beat Quartz’s owndisplay: none— the button was there at 1600 px too. And the plugin’s script ends with “if this button is visible, fold the tree”. So the tree was folded on every desktop view, although it had been rendered.
Which variables apply here
These 38 variables are read by the section nav-explorer in components-navigation.scss. They can be changed in QuartzControl under Styles → Variables — without a line of CSS.
| Variable | Value | also applies to |
|---|---|---|
--dark | #17171A · dark #FCFCFA | 13 other components |
--darkgray | #333333 · dark #DDDDDD | 18 other components |
--explorer-ring-reach | set in the stylesheet (components-navigation.scss) | only here |
--gray | #5F5F5F · dark #A1A1A1 | 22 other components |
--headerFont | "Noto Sans", system-ui, "Segoe UI", Robot… | 15 other components |
--icon-close | set in the stylesheet (foundation.scss) | only here |
--icon-file | set in the stylesheet (foundation.scss) | only here |
--icon-folder | set in the stylesheet (foundation.scss) | only here |
--icon-folder-open | set in the stylesheet (foundation.scss) | only here |
--light | #FCFCFA · dark #16171A | 14 other components |
--secondary | #1463A3 · dark #699DC3 | 19 other components |
--tpl-accent-bar | 3px | 6 other components |
--tpl-backdrop | rgba(0, 0, 0, 0.3) | The navigation |
--tpl-backdrop-blur | 4px | The navigation |
--tpl-drawer-width | min(86vw, 340px) | The navigation |
--tpl-focus-offset | 2px | Callout colours, 6.4 – Variables |
--tpl-focus-width | 2px | Callout colours, 6.4 – Variables |
--tpl-header-h | set in the stylesheet (layout.scss) | 6.4 – Variables, The navigation |
--tpl-icon | 1.1rem | 3 other components |
--tpl-icon-sm | 0.95rem | 3 other components |
--tpl-indent | 0.85rem | Table of contents, The navigation |
--tpl-motion | 150ms ease | 13 other components |
--tpl-radius-lg | 14px | 3 other components |
--tpl-radius-md | 8px | 17 other components |
--tpl-radius-sm | 4px | 10 other components |
--tpl-rule | var(--lightgray) = #DDDDDD | 19 other components |
--tpl-rule-width | 1px | 25 other components |
--tpl-shadow | 0 6px 24px rgba(23, 23, 26, 0.10) · dark 0 6px 24px rgba(0, 0, 0, 0.55) | 5 other components |
--tpl-space-2xs | 0.25rem | 16 other components |
--tpl-space-lg | 1.5rem | 12 other components |
--tpl-space-md | 1rem | 18 other components |
--tpl-space-xs | 0.5rem | 20 other components |
--tpl-surface | var(--lightgray) = #DDDDDD | 6 other components |
--tpl-surface-tint | var(--highlight) = rgba(42, 78, 108, 0.10) | 14 other components |
--tpl-target | 44px | 12 other components |
--tpl-text-sm | 0.875rem | 20 other components |
--tpl-text-xs | 0.78rem | 10 other components |
--tpl-tracking-label | 0.08em | 9 other components |
This table is generated: it is read out of the stylesheets rather than kept by hand.
Per component: out of the box against this template.
This template is built to be changed. There are three levels for it, and the first one is enough more often than you would think.
1 · A variable
The normal case. 66 variables carry every colour and every measure of this template; not a line of CSS is needed for them. In QuartzControl under Styles → Variables, separated into light and dark.
Which variable applies where stands at the bottom of every page in chapter 5 — including the
column that says what else moves with it. Touching --tpl-space-md moves the whole site;
--tpl-fade moves two soft edges.
2 · A rule in an existing stylesheet
When a variable is not enough because it is not the value but the shape: the twelve stylesheets sit under Styles → Custom CSS, in sections by component. Which file and which section a component stands in is said by the table at the bottom of its page.
That changes the template itself. Importing a template package again can overwrite it — for a change meant to last, the third way is the safer one.
3 · A stylesheet of your own
Styles → Custom CSS → New file. A newly created file is appended to the end of the load order, and that is the point: at equal specificity the rule loaded last wins. What stands here beats the whole template without having to take anything away from it.
a11y.scssis then no longer last eitherThat one file sits last on purpose: its rules have to win over decisions made further up — an affordance that would only appear on hover, a state that would hang on a colour alone. A file of your own lands behind it. If something to do with keyboard, contrast or print disappears after a change, that is the first place to look.
The exception: diagrams
With Mermaid none of the three ways works without !important. Quartz hands the diagram renderer
nine of its roughly hundred theme variables (primaryColor, lineColor, secondaryColor and
six more, hard-wired in Quartz’s script). Everything else Mermaid colours itself — and writes as a
<style> carrying that SVG’s own id into the graphic, an ID selector no class rule can beat.
That is why the body-mermaid section of content.scss holds 129 !important — and why your own
rule needs one too. For comparison: all the other sections together hold 21, each with its reason
beside it.
What not to touch
The load order, the fonts and the variables stand as blocks between markers in _quartzcontrol.scss,
a file of their own next to custom.scss. QuartzControl rewrites those on every save — whatever you enter
there by hand is gone with the next click. custom.scss is yours; QuartzControl only puts the one line
@use "./quartzcontrol"; at its top, which loads the blocks.
Colours, typefaces, measures, grid, languages.
Three decisions carry the whole template; they stand in 1.1 – What this template is. Below them, smaller patterns repeat in every component. Whoever changes a component or designs a new one keeps to these, so that it fits the rest.
What is the same everywhere
- Sidebar headings are small, capitalised and muted — they are labelling, not content. The same form stands in nine stylesheets, and that is intended: every page in chapter 5 is to show its own table of variables in full.
- A state is never shown by colour alone. The active entry in the explorer has colour, weight and a bar; a completed task item is muted and struck through; an external link is coloured differently and carries an arrow.
- Borders of controls draw their colour from
--tpl-rule-control, not from--lightgray. The faint tone is enough for a divider, but not for the border of something you operate — WCAG demands 3:1 there. - Everything you tap is at least 44 px (
--tpl-target). WCAG demands 24; 44 is the size at which nobody has to aim. - Two line heights, not one. 1.65 for body text, 1.45 for everything in small type — the sidebars and the footer. The same spacing at a smaller size tears a paragraph into single lines.
- A hovered link turns accent-coloured and underlined, with the underline set lower. The same gesture in eight stylesheets, the same value from one variable.
- All fold-out chevrons are the same chevron from the Lucide set — in the explorer, in the table of contents, in the collapsible layout box.
- What overflows scrolls within itself and fades out softly at the edge instead of making the page longer: the explorer, the table of contents, a wide table, a code block.
- An area that really has no business at a given width is hidden; a column that merely happens to be empty stays, so that the text column does not move.
- Nothing moves when the system does not want it to.
prefers-reduced-motionsets a single variable to zero, and every transition of the template follows it.
What deliberately is not unified
The nine panel headings and the eight hover rules could be pulled into one shared file. That would
be wrong here: the stylesheets are cut by component, and the documentation hangs on that — every
page in chapter 5 shows the variables its component reads. What is shared are the values, and
they are: --tpl-tracking-label and --tpl-underline-offset stand once each. In full in
6.4 – Variables.
Glossary, patterns, limits, all pages.