Warning

prerequisites:

  • JavaScript authoring experience
  • TypeScript familiarity

component_model:

  • HTML defines page structure:
<article>
  <h1>An article header</h1>
  <p>Some content</p>
</article>
  • snippet_meaning:
    • <article> wrapper
    • leading <h1>: "An article header"
    • <p> content: "Some content"
  • web_stack:
    • HTML: structure
    • CSS: styling
    • JavaScript: interactivity
  • limitation:
    • HTML lacks reusable templates
    • repeated layouts require copy-paste edits
  • component_concept:
    • JavaScript function
    • input: data
    • output: HTML/JSX
    • purpose: eliminate layout duplication
  • quartz:
    • does not use React
    • uses the same component model for Quartz layout templates

Community Component Plugins

overview:

  • v5 components mostly ship as community plugins
  • plugin_form:
    • standalone repository
    • exports a QuartzComponent
  • core_decoupling:
    • easier maintenance
    • easier sharing

Getting Started

template_setup:

git clone https://github.com/quartz-community/plugin-template.git my-component
cd my-component
npm install

Plugin Structure

entrypoint:

  • typical_file: src/index.ts
  • exports:
    • component constructor
    • returns QuartzComponent
    • accepts user options
src/index.ts
import {
  QuartzComponent,
  QuartzComponentConstructor,
  QuartzComponentProps,
} from "@quartz-community/types"
 
interface Options {
  favouriteNumber: number
}
 
const defaultOptions: Options = {
  favouriteNumber: 42,
}
 
const MyComponent: QuartzComponentConstructor<Options> = (userOpts?: Options) => {
  const opts = { ...defaultOptions, ...userOpts }
 
  const Component: QuartzComponent = (props: QuartzComponentProps) => {
    if (opts.favouriteNumber < 0) return null
    return <p>My favourite number is {opts.favouriteNumber}</p>
  }
 
  return Component
}
 
export default MyComponent

Props

contract:

  • every Quartz component receives QuartzComponentProps
export type QuartzComponentProps = {
  fileData: QuartzPluginData
  cfg: GlobalConfiguration
  tree: Node<QuartzPluginData>
  allFiles: QuartzPluginData[]
  displayClass?: "mobile-only" | "desktop-only"
}

props:

  • fileData:
    • metadata added by plugins for the current page
    • fileData.slug: current page slug
    • fileData.frontmatter: parsed frontmatter
  • cfg:
    • configuration field from quartz.config.yaml
  • tree:
  • allFiles:
    • metadata for all parsed files
    • useful for:
      • page listings
      • site-structure analysis
  • displayClass:
    • optional render preference utility class
    • values:
      • "mobile-only"
      • "desktop-only"

Styling

plugin_styles:

  • bundle styles with the plugin
  • assign CSS through component .css:
Component.css = `
  .my-component { color: red; }
`

scss:

  • import SCSS
  • assign imported output to .css
  • build system transforms it
import styles from "./styles.scss"
Component.css = styles

Warning

css_scope:

  • Quartz does not use CSS modules
  • declared styles apply globally
  • component-only styling requires specific class names and selectors

Internationalization

i18n:

quick_reference:

import { i18n } from "../i18n"
 
const MyComponent: QuartzComponent = ({ cfg }) => {
  const t = i18n(cfg.locale ?? "en-US").components.myComponent
  return <h2>{t.title}</h2>
}

locale_requirements:

  • always provide en-US fallback
  • optional additional locales improve international reach

Scripts and Interactivity

client_scripts:

  • define browser JavaScript as string properties on the component
  • properties:
    • .beforeDOMLoaded:
      • runs before page load completion
      • use for:
        • prefetching
        • early initialization
    • .afterDOMLoaded:
      • runs after full page load

spa_navigation:

  • page-specific .afterDOMLoaded logic:
    • listen for "nav" when target elements can change between navigations
document.addEventListener("nav", () => {
  // do page specific logic here
  const toggleSwitch = document.querySelector("#switch") as HTMLInputElement
  if (toggleSwitch) {
    toggleSwitch.addEventListener("change", switchTheme)
    window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme))
  }
})

events:

  • "prenav":
    • fires before page replacement during SPA navigation
  • "nav":
    • fires on navigation
    • use for page-specific setup
  • "render":
    • fires after in-place DOM updates without full navigation
    • examples:
      • content decryption
      • dynamic DOM changes by other plugins
    • use with "nav" when attaching listeners to content elements
function setupMyComponent() {
  const elements = document.querySelectorAll(".my-interactive")
  for (const el of elements) {
    el.addEventListener("click", handleClick)
    window.addCleanup(() => el.removeEventListener("click", handleClick))
  }
}
 
document.addEventListener("nav", setupMyComponent)
document.addEventListener("render", setupMyComponent)

cleanup:

  • track event handlers with window.addCleanup
  • prevents memory leaks during SPA navigation

Importing Code

typescript_scripts:

  • transpile community-plugin client scripts at build time
  • plugin template includes inlineScriptPlugin in tsup.config.ts
  • .inline.ts imports become text containing browser-compatible JavaScript
src/index.ts
import script from "./script.inline.ts"
 
const Component: QuartzComponent = (props) => {
  return <button id="btn">Click me</button>
}
Component.afterDOMLoaded = script

inline_script_plugin:

  • transpiles TypeScript during build
  • enables type-safe client-side code

Installing Your Component

install:

  • after publishing to GitHub or npm, users install with Quartz CLI:
npx quartz plugin add github:your-username/my-component

configure:

  • add plugin to quartz.config.yaml:
quartz.config.yaml
plugins:
  - source: github:your-username/my-component
    enabled: true
    options:
      favouriteNumber: 42
    layout:
      position: left
      priority: 60

advanced_usage:

  • TS override in quartz.ts:
quartz.ts (override)
import { loadQuartzConfig, loadQuartzLayout } from "./quartz/plugins/loader/config-loader"
import Plugin from "./.quartz/plugins"
 
const config = await loadQuartzConfig()
export default config
export const layout = await loadQuartzLayout({
  byPageType: {
    content: {
      left: [Plugin.MyComponent({ favouriteNumber: 42 })],
    },
  },
})

Receiving YAML Options in Component-Only Plugins

option_flow:

  • processing-category plugins receive options through their factory function automatically
  • categories:
    • transformer
    • filter
    • emitter
    • page type
    • component
  • component-only plugins:
    • manifest declares only "category": ["component"]
    • load through side-effect import
    • skip factory path

component_only_options:

  • export init from entry point to receive YAML options:
src/index.ts
export function init(options?: Record<string, unknown>): void {
  // options contains merged defaultOptions + user's YAML options
  const myFlag = (options?.myFlag as boolean) ?? false
  // Use options to configure registrations, global state, etc.
}

loader_behavior:

  • config-loader calls init() after module import
  • argument:
    • merged manifest defaultOptions
    • user options from quartz.config.yaml
  • merge_pattern:
    • { ...defaultOptions, ...userOptions }
    • user values take precedence

defaults:

  • declare in package.json:
package.json
{
  "quartz": {
    "category": ["component"],
    "defaultOptions": {
      "myFlag": false
    }
  }
}

backward_compatibility:

  • plugins without init continue as pure side-effect imports
  • fully backward compatible

Internal Components

internal_components:

  • location: quartz/components/
  • purpose:
    • layout utilities
    • structural rendering
  • utilities:
    • Component.Head():
      • renders <head>
    • Component.Spacer():
      • adds flexible space
    • Component.Flex():
      • flexible layout container
    • Component.MobileOnly():
      • shows component only on mobile
    • Component.DesktopOnly():
      • shows component only on desktop
    • Component.ConditionalRender():
      • conditionally renders from page data
  • details:

Hint

examples: