@chaoticgoodcomputing/quartz-tags is a Quartz 5 plugin that gives a site one dictionary of tag colours and icons, and publishes it resolved for other plugins to read. It draws nothing itself: tag badges, tag explorers and graphs are other plugins, which read what it publishes. This site’s tag badges come from quartz-tag-list.
A tag you don’t list takes its colour and icon from its nearest listed ancestor, so the dictionary only names the tags that differ from their parent.
What it publishes
For every page, on the page’s fileData.cgcTags:
interface TagsData {
tags: Record<string, TagProperties> // the page's own tags, in frontmatter order
primary: (TagProperties & { tag: string }) | null // the tag that stands for the page
ancestors: Record<string, TagProperties> // every tag the page is under, its own included
}
interface TagProperties {
color: string // the tag's colour property, e.g. "--cgc-tag-engineering--ai"
icon: string | null // its icon id, e.g. "mdi:robot", its own or its nearest ancestor's
}For the browser, static/cgcTags.json: every tag any page carries, and every ancestor of one, mapped to its TagProperties.
{ "engineering": { "color": "--cgc-tag-engineering", "icon": "mdi:wrench" },
"engineering/ai": { "color": "--cgc-tag-engineering--ai", "icon": "mdi:robot" } }And a stylesheet, static/cgcTags.css, linked from every page. It defines one custom property per tag on :root, in the cgc.tags cascade layer:
@layer cgc.tags {
:root {
--cgc-tags-default: var(--darkgray);
--cgc-tag-engineering: light-dark(#0070cc, #008CFF);
--cgc-tag-engineering--ai: var(--cgc-tag-engineering);
--cgc-tag-misc: var(--cgc-tags-default);
}
}- A tag’s property is
--cgc-tag-and the tag, with each/written--. A character a CSS name can’t hold is escaped, and two tags that would share a name fail the build. - A tag with a colour of its own gets that colour, exactly as written. A tag without one gets
var()of its parent’s property, and a top-level tag getsvar(--cgc-tags-default). - So colours inherit through the cascade. Override one tag’s property in your own CSS, and its descendants follow.
These three shapes are the plugin’s contract, and its specs pin them.
The primary tag is the page’s most specific tag, the deepest one. When two are equally deep, the first in the frontmatter wins. A page can name a different one with primaryTag, which must be one of its tags:
tags: [engineering/ai, economics/finance]
primaryTag: economics/financeInstall
The plugin is the npm package @chaoticgoodcomputing/quartz-tags, built, with its type declarations, and loaded by name, as Quartz’s own @quartz-community/* plugins are. It needs quartz-styles, which gives the family its cascade layer, so install that too. Install it into your Quartz site:
npm install @chaoticgoodcomputing/quartz-tagsand list it in quartz.config.yaml by its package name:
plugins:
- source: "@chaoticgoodcomputing/quartz-tags"
enabled: trueIts peers, @quartz-community/types, @quartz-community/utils and lightningcss, are your site’s own copies. The package isn’t on npm yet: it is published after this site moves to Quartz 5 (#90).
Configure
plugins:
- source: "@chaoticgoodcomputing/quartz-tags"
enabled: true
options:
tags:
engineering: { color: "light-dark(#0070cc, #008CFF)", icon: mdi:wrench }
engineering/ai: { icon: mdi:robot }
writing: { color: "var(--secondary)", icon: mdi:pencil }
defaultColor: var(--darkgray)| Option | Default | |
|---|---|---|
tags | {} | The dictionary. Each key is a tag, written as in frontmatter, and each value has an optional color and an optional icon. |
defaultColor | var(--darkgray) | The colour of a tag that neither it nor any ancestor gives one. |
A colour is anything CSS accepts as a colour: a hex, a named colour, oklch(), color-mix(), a theme’s var(--secondary) or a light-dark() pair. A reference to a theme property follows the theme, and a light-dark() pair follows the colour scheme. An icon is an icon id, prefix:name, such as mdi:robot. The plugin only publishes it, and the plugin that draws icons resolves it: quartz-tag-list fails the build on an id that no icon collection has.
The build fails on any mistake in the dictionary: a colour CSS can’t read, an icon id that isn’t one, or a field a tag doesn’t have. It can’t check that a property a colour refers to exists, since the theme defines that when the page loads. A page’s primaryTag that isn’t one of its tags fails the build too.
A tag colour is decorative. It paints the rim of a tag’s bubble, the circle that holds its icon wherever the site draws one, and never the bubble’s circle or icon, which are the theme’s --lightgray and --dark, and never text, so no contrast check applies. The tag explorer’s icons, which are not bubbles, are the one mark it paints whole.
What a site needs for light-dark()
The stylesheet reaches the browser as written. Quartz doesn’t rewrite it for older browsers, as it does the CSS it bundles itself. So a light-dark() colour needs two things:
- a browser that supports
light-dark(), as every major browser has since 2024; - a
color-schemeon the page, which Quartz’s darkmode plugin sets. Without one, the browser always picks the light half.
The reasoning is in the package’s ADR-0001.
Writing a plugin that reads it
A plugin that uses tag colours or icons is a consumer of quartz-tags:
- It declares the engine by its full package name in its
package.json, with anorderof 20 or more:"quartz": { "dependencies": ["@chaoticgoodcomputing/quartz-tags"] }. Quartz matches a dependency against the whole package name, soquartz-tagsalone, or the manifest namecgc-tags, finds nothing. - It reads
fileData.cgcTagson the server, orstatic/cgcTags.jsonin the browser, and never the dictionary. The types, and the resolution rule, are in the library@chaoticgoodcomputing/tags-core. - It paints with the tag’s property,
var(--cgc-tag-…), so the colour follows the scheme with no script. A canvas can’t use CSS, so it resolves the property with the library’sresolveTagColour()and resolves it again onthemechange.
A component that renders tags a page doesn’t carry, such as a tag page’s subtags, finds their properties in the ancestors of every page (allFiles). The reasoning is in the package’s ADR-0002.
Develop
This package is the Nx project quartz-tags, in quartz-v5/plugins/quartz-tags/. Its manifest name is still cgc-tags, which names its CSS, so the class names and properties above keep it. Its specs live in e2e/ and run against the shared fixture site (ADR-0004), with the shared suite’s proof that its consumers receive what it publishes:
pnpm nx run quartz-tags:e2e
pnpm nx run quartz-tags:typecheck