An article header
Some content
---
title: Creating your own Quartz components
---
> [!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:
```html
Some contentAn article header
My favourite number is {opts.favouriteNumber}
} return YourComponent }) satisfies QuartzComponentConstructor ``` ### Props The Quartz component itself (lines 11-17 highlighted above) looks like a React component. It takes in properties (sometimes called [props](https://react.dev/learn/passing-props-to-a-component)) and returns JSX. All Quartz components accept the same set of props: ```tsx title="quartz/components/types.ts" // simplified for sake of demonstration export type QuartzComponentProps = { fileData: QuartzPluginData cfg: GlobalConfiguration tree: NodeExample Component
} YourComponent.css = ` p.red-text { color: red; } ` return YourComponent }) satisfies QuartzComponentConstructor ``` Imported styles, however, can be from SCSS files: ```tsx {1-2,9} title="quartz/components/YourComponent.tsx" // assuming your stylesheet is in quartz/components/styles/YourComponent.scss import styles from "./styles/YourComponent.scss" export default (() => { function YourComponent() { returnExample Component
} YourComponent.css = styles return YourComponent }) satisfies QuartzComponentConstructor ``` > [!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 What about interactivity? Suppose you want to add an-click handler for example. Like the `.css` property on the component, you can also declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties that are strings that contain the script. ```tsx title="quartz/components/YourComponent.tsx" export default (() => { function YourComponent() { return } YourComponent.beforeDOMLoaded = ` console.log("hello from before the page loads!") ` YourComponent.afterDOMLoaded = ` document.getElementById('btn').onclick = () => { alert('button clicked!') } ` return YourComponent }) satisfies QuartzComponentConstructor ``` > [!hint] > For those coming from React, Quartz components are different from React components in that it only uses JSX for templating and layout. Hooks like `useEffect`, `useState`, etc. are not rendered and other properties that accept functions like `onClick` handlers will not work. Instead, do it using a regular JS script that modifies the DOM element directly. As the names suggest, the `.beforeDOMLoaded` scripts are executed _before_ the page is done loading so it doesn't have access to any elements on the page. This is mostly used to prefetch any critical data. The `.afterDOMLoaded` script executes once the page has been completely loaded. This is a good place to setup anything that should last for the duration of a site visit (e.g. getting something saved from local storage). If you need to create an `afterDOMLoaded` script that depends on _page specific_ elements that may change when navigating to a new page, you can listen for the `"nav"` event that gets fired whenever a page loads (which may happen on navigation if [[SPA Routing]] is enabled). ```ts document.addEventListener("nav", () => { // do page specific logic here // e.g. attach event listeners const toggleSwitch = document.querySelector("#switch") as HTMLInputElement toggleSwitch.addEventListener("change", switchTheme) window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) }) ``` It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. This will get called on page navigation. #### Importing Code Of course, it isn't always practical (nor desired!) to write your code as a string literal in the component. Quartz supports importing component code through `.inline.ts` files. ```tsx title="quartz/components/YourComponent.tsx" // @ts-ignore: typescript doesn't know about our inline bundling system // so we need to silence the error import script from "./scripts/graph.inline" export default (() => { function YourComponent() { return } YourComponent.afterDOMLoaded = script return YourComponent }) satisfies QuartzComponentConstructor ``` ```ts title="quartz/components/scripts/graph.inline.ts" // any imports here are bundled for the browser import * as d3 from "d3" document.getElementById("btn").onclick = () => { alert("button clicked!") } ``` Additionally, like what is shown in the example above, you can import packages in `.inline.ts` files. This will be bundled by Quartz and included in the actual script. ### Using a Component After creating your custom component, re-export it in `quartz/components/index.ts`: ```ts title="quartz/components/index.ts" {4,10} import ArticleTitle from "./ArticleTitle" import Content from "./pages/Content" import Darkmode from "./Darkmode" import YourComponent from "./YourComponent" export { ArticleTitle, Content, Darkmode, YourComponent } ``` Then, you can use it like any other component in `quartz.layout.ts` via `Component.YourComponent()`. See the [[configuration#Layout|layout]] section for more details. As Quartz components are just functions that return React components, you can compositionally use them in other Quartz components. ```tsx title="quartz/components/AnotherComponent.tsx" import YourComponent from "./YourComponent" export default (() => { function AnotherComponent(props: QuartzComponentProps) { return (It's nested!