# Custom snippets
A **custom snippet** is a real React component you drop in `snippets/*.jsx`. Reach for
one when you need *actual React* — state, effects, hooks, a third-party React library,
or a browser API — that you can't express by composing existing components. Unlike a
[custom component](/authoring/custom-components/), which is a build-time macro that
ships **no** JavaScript, a snippet **is** JavaScript: it's bundled and mounts as an
[island](/authoring/components-and-snippets/) in the browser.
## Write one
A snippet is a `.jsx` file under `snippets/` that default-exports a React component.
Its props arrive as the component's props, and anything you wrap becomes `children`:
```jsx
// snippets/Stepper.jsx
import { useState } from 'react';
import { Button } from '@mantine/core';
export default function Stepper({ label = 'Clicks', start = 0 }) {
const [n, setN] = useState(start);
return ;
}
```
That `useState` is the tell — it holds live client state, which a `.md` macro can't.
The file name (`Stepper`) is the component name.
## Two ways to use it
A snippet is reachable both as a **call** and as a **tag**; both mount the same island,
so use whichever reads better. This site ships `snippets/ProductCard.jsx`:
```aardvark
{% component('ProductCard', product='Aardvark Pro', badge='New') %} {# call form #}
{% ProductCard product="Aardvark Pro" badge="New" %} {# tag form #}
```
both render the same island, live:
- **Tag form** — `{% ProductCard … %}`. Defining the snippet
*gives* you the tag, the same way a custom component does. Scalar attributes become
props; a wrapped body (closed with `{% endProductCard %}`, first
letter capitalized) becomes `children`. The `attr={…}` escape
hatch from [Components & snippets](/authoring/components-and-snippets/) works here too.
- **Call form** — `{% component('ProductCard', …) %}`. Needed for
the two things a tag can't do: pass a **non-scalar prop** (a list or dict — tag
attributes are scalar, like every directive), and emit from inside a **`for` loop** (a
`{% Name %}` tag is expanded when the page is *scanned*, before any
loop runs, so loops use the call form — see the
[storefront example](/components/extras/component-libraries/)).
## Snippet or custom component?
Both let you define your own building block and call it as a `{% Tag %}`.
The difference is **what's behind the tag**:
| | [Custom component](/authoring/custom-components/) (`.md`) | Custom snippet (`.jsx`) |
|---|---|---|
| What it is | A build-time **macro** | A real **React component** |
| Ships JavaScript? | No — expands into `component()` calls at build | Yes — bundled, mounts as an island |
| Body written in | A `{% %}` template | JSX |
| Reach for it when | **Composing existing** components into a named, parameterized tag | You need **new React**: state, effects, hooks, a third-party React lib, browser APIs |
| Tag form | `{% Name %}` | `{% Name %}` |
| Call form | — | `{% component('Name', …) %}` |
**Rule of thumb:** composing what already exists → custom component; writing new React →
snippet. Both earn a `{% Name %}` tag, so the *call site* reads the
same — the choice is only about whether you're wiring existing pieces together or writing
genuine React.
## A case that has to be a snippet
The Stripe provider on the [Component libraries](/components/extras/component-libraries/) page is
the canonical "must be a snippet." Mounting Stripe's card fields needs `loadStripe('pk_…')`
— which returns a Promise and injects Stripe.js — wrapped in Stripe's `` React
context provider. None of that is expressible as a build-time macro (there's no JavaScript
to run, no React context to hold), so `StripeProvider` is a `snippets/*.jsx` file. Because
it's a snippet, you still get the natural `{% StripeProvider %}` tag —
that's exactly this behavior at work.