# Snippets

> For the complete documentation index, see [llms.txt](https://preview.writedocs.io/llms.txt).

Reusable MDX prose and real React components, shared across pages via a snippets/ folder.

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`:

```mdx title="snippets/upgrade-note.mdx"
<Callout type="note">
  Available since v{props.since}.
</Callout>
```

```mdx title="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:

```jsx title="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>;
}
```

```mdx title="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:

```mdx
<Counter client:visible start={5} />
```

| Attribute | Interactive |
|---|---|
| `client:load` (the default) | As soon as the page loads |
| `client:idle` | Once the browser has nothing else to do |
| `client:visible` | When 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:

```mdx title="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:

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

Both resolve to the exact same file.