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 installPlugin Structure
entrypoint:
- typical_file:
src/index.ts - exports:
- component constructor
- returns
QuartzComponent - accepts user options
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 MyComponentProps
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 slugfileData.frontmatter: parsed frontmatter
cfg:configurationfield fromquartz.config.yaml
tree:- processed/transformed HTML AST
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 = stylesWarning
css_scope:
- Quartz does not use CSS modules
- declared styles apply globally
- component-only styling requires specific class names and selectors
Internationalization
i18n:
- use the i18n pattern for user-facing strings
- full guide: making plugins > 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-USfallback - 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
.afterDOMLoadedlogic:- listen for
"nav"when target elements can change between navigations
- listen for
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
inlineScriptPluginintsup.config.ts .inline.tsimports become text containing browser-compatible JavaScript
import script from "./script.inline.ts"
const Component: QuartzComponent = (props) => {
return <button id="btn">Click me</button>
}
Component.afterDOMLoaded = scriptinline_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-componentconfigure:
- add plugin to
quartz.config.yaml:
plugins:
- source: github:your-username/my-component
enabled: true
options:
favouriteNumber: 42
layout:
position: left
priority: 60advanced_usage:
- 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 })],
},
},
})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
- manifest declares only
component_only_options:
- export
initfrom entry point to receive YAML options:
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-loadercallsinit()after module import- argument:
- merged manifest
defaultOptions - user
optionsfromquartz.config.yaml
- merged manifest
- merge_pattern:
{ ...defaultOptions, ...userOptions }- user values take precedence
defaults:
- declare in
package.json:
{
"quartz": {
"category": ["component"],
"defaultOptions": {
"myFlag": false
}
}
}backward_compatibility:
- plugins without
initcontinue 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>
- renders
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