quartz/docs/advanced/creating components.md
2026-02-25 16:04:21 +01:00

7.3 KiB

title
Creating Component Plugins

Warning

This guide assumes you have experience writing JavaScript and are familiar with TypeScript.

Normally on the web, we write layout code using HTML which looks something like the following:

<article>
  <h1>An article header</h1>
  <p>Some content</p>
</article>

This piece of HTML represents an article with a leading header that says "An article header" and a paragraph that contains the text "Some content". This is combined with CSS to style the page and JavaScript to add interactivity.

However, HTML doesn't let you create reusable templates. If you wanted to create a new page, you would need to copy and paste the above snippet and edit the header and content yourself. This isn't great if we have a lot of content on our site that shares a lot of similar layout. The smart people who created React also had similar complaints and invented the concept of Components -- JavaScript functions that return JSX -- to solve the code duplication problem.

In effect, components allow you to write a JavaScript function that takes some data and produces HTML as an output. While Quartz doesn't use React, it uses the same component concept to allow you to easily express layout templates in your Quartz site.

Community Component Plugins

In v5, most components are community plugins — standalone repositories that export a QuartzComponent. These plugins are decoupled from the core Quartz repository, allowing for easier maintenance and sharing.

Getting Started

To create a new component plugin, you can use the official plugin template:

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

Plugin Structure

A component plugin's src/index.ts typically exports a function (a constructor) that returns a QuartzComponent. This allows users to pass configuration options to your component.

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

All Quartz components accept the same set of props:

export type QuartzComponentProps = {
  fileData: QuartzPluginData
  cfg: GlobalConfiguration
  tree: Node<QuartzPluginData>
  allFiles: QuartzPluginData[]
  displayClass?: "mobile-only" | "desktop-only"
}
  • fileData: Any metadata plugins may have added to the current page.
    • fileData.slug: slug of the current page.
    • fileData.frontmatter: any frontmatter parsed.
  • cfg: The configuration field in quartz.config.yaml.
  • tree: the resulting HTML AST after processing and transforming the file.
  • allFiles: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure.
  • displayClass: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting.

Styling

In community plugins, styles are bundled with the plugin. You can define styles using the .css property on the component:

Component.css = `
  .my-component { color: red; }
`

For SCSS, you can import it and assign it to the .css property. The build system will handle the transformation:

import styles from "./styles.scss"
Component.css = styles

Warning

Quartz does not use CSS modules so any styles you declare here apply globally. If you only want it to apply to your component, make sure you use specific class names and selectors.

Scripts and Interactivity

For interactivity, you can declare .beforeDOMLoaded and .afterDOMLoaded properties on the component. These should be strings containing the JavaScript to be executed in the browser.

  • .beforeDOMLoaded: Executed before the page is done loading. Used for prefetching or early initialization.
  • .afterDOMLoaded: Executed once the page has been completely loaded.

If you need to create an afterDOMLoaded script that depends on page-specific elements that may change when navigating, listen for the "nav" event:

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))
  }
})

You can also use the "prenav" event, which fires before the page is replaced during SPA navigation.

It is best practice to track any event handlers via window.addCleanup to prevent memory leaks during SPA navigation.

Importing Code

In community plugins, TypeScript scripts should be transpiled at build time. The plugin template includes an inlineScriptPlugin in tsup.config.ts that automatically transpiles .inline.ts files imported as text:

import script from "./script.inline.ts"

const Component: QuartzComponent = (props) => {
  return <button id="btn">Click me</button>
}
Component.afterDOMLoaded = script

The inlineScriptPlugin handles transpiling TypeScript to browser-compatible JavaScript during the build step, allowing you to write type-safe client-side code.

Installing Your Component

Once your component is published (e.g., to GitHub or npm), users can install it using the Quartz CLI:

npx quartz plugin add github:your-username/my-component

Then, they can add it to their quartz.config.yaml:

plugins:
  - source: github:your-username/my-component
    enabled: true
    options:
      favouriteNumber: 42
    layout:
      position: left
      priority: 60

For advanced usage via the TS override in quartz.ts:

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 })],
    },
  },
})

Internal Components

Quartz also has internal components that provide layout utilities. These live in quartz/components/ and are primarily used for structural purposes:

  • Component.Head() — renders the <head> tag
  • 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 based on page data

See layout-components for more details on these utilities.

[!hint] Look at existing community plugins like Explorer or Darkmode for real-world examples.