WritedocsWritedocs

Snippets

A snippet is a file you write once and import into as many pages as you want - a piece of content, or an interactive React component. Keep them in a snippets/ folder at the project root:

my-docs/
├── writedocs.json
├── index.mdx
├── docs/
│   └── getting-started.mdx
└── snippets/
    ├── upgrade-note.mdx
    └── Counter.jsx

A snippet has no frontmatter and isn’t in the navigation, so it never becomes a page of its own. Snippets work in .mdx pages.

MDX snippets

An MDX snippet is an .mdx file with no frontmatter. Import it, and use it like a component. Props you pass are available inside it as props:

snippets/upgrade-note.mdx
<Callout type="note">
  Available since v{props.since}.
</Callout>
docs/getting-started.mdx
import UpgradeNote from '../snippets/upgrade-note.mdx';

<UpgradeNote since="2.0" />

Components (Callout, Card, Tabs, …) work in a snippet the same as in a page, with no import.

React component snippets

A snippet can also be a .jsx or .tsx file: a React component, interactive in the reader’s browser, hooks included:

snippets/Counter.jsx
import { useState } from 'react';

export default function Counter({ start = 0 }) {
  const [count, setCount] = useState(start);
  return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
}
docs/getting-started.mdx
import Counter from '../snippets/Counter.jsx';

<Counter start={5} />

The component is interactive as soon as the page loads - useState, event handlers and effects work with nothing else to write.

When it becomes interactive

To delay that, add a client: attribute where you use it:

<Counter client:visible start={5} />
AttributeInteractive
client:load (the default)As soon as the page loads
client:idleOnce the browser has nothing else to do
client:visibleWhen the component scrolls into view

It applies to that one use: the same Counter can be interactive at once in one place, and wait until it’s in view in another. client:visible suits a heavy component far down a page.

A use that passes a component as a prop - <Playground Renderer={MyBlock} /> - is shown, but isn’t interactive: a component can’t be sent to the browser as a prop.

Hooks without imports

useState, useEffect, useRef, useCallback, useMemo, useContext and useReducer work in a snippet or page without import { ... } from 'react' - the import is added for you.

Components defined in a page

A page can define a React component itself and use it right away:

docs/getting-started.mdx
export const Counter = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
};

<Counter />

A component like this that uses a hook is interactive, like a snippet. Inside it, Writedocs’ own components are limited: Card, Callout and the others show only their content, Icon shows nothing, and CodeBlock shows unhighlighted code. writedocs validate warns about each one, with its file and line. Use your own markup there, or move the component into a snippet file.

Import paths

Import a snippet by its path relative to the page, or from the project root with a leading / - the same from any page, however deep:

import UpgradeNote from '../../../snippets/upgrade-note.mdx';
import UpgradeNote from '/snippets/upgrade-note.mdx';

Both resolve to the exact same file.