# Writing nodes

> A node is a small component that takes its data and returns what it wants to say.

Source: https://docxcelerate.com/docs/writing-nodes/

A document is a tree of **components**. Each one returns a **node** — a
paragraph, an image, a graph — and each lives in its own file under `nodes/`.

Why one file each? Because documents get long. A template with all its text
written inline stops being readable about halfway down.

## Generate one

```sh
dxcl document node documents/welcome next-steps --type paragraph
```

That writes `nodes/next-steps.node.tsx` and adds it to `nodes/index.ts`.
`--type` takes `paragraph`, `image` or `graph`. Run the command with no
arguments and it will ask you instead.

It deliberately doesn't place the node for you — only you know where it belongs.
Open `document.tsx` and add the component at the right point:

```tsx
import { Document, Section, template } from "docxcelerate/template";
import * as Nodes from "./nodes/index.ts";
import type { DocumentData } from "./types.ts";

export const documentTemplate = template<DocumentData>(
  <Document id="welcome" title="Welcome">
    <Section id="opening" title="Opening">
      <Nodes.Greeting />
    </Section>
    <Section id="closing" title="Closing">
      <Nodes.NextSteps />
    </Section>
  </Document>,
);
```

Save, and the preview reloads with the new node in place.

## Data comes in through `useState`

```tsx
import { Paragraph, useState } from "docxcelerate/template";
import type { DocumentData } from "../types.ts";

export const Greeting: Paragraph = () => {
  const [state] = useState((data: DocumentData) => ({
    name: data.recipientName,
  }));

  return <Paragraph id="greeting">Hello {state.name},</Paragraph>;
};
```

`useState` is where your data enters the component, and the only place it can.

The payoff is that everything a node depends on is written down in one
declaration, instead of being scattered through the code that reads it.

The initializer is ordinary TypeScript. There's no template language to learn, so
conditionals, formatting and imports all just work — and an `if` that returns a
different paragraph does exactly what it looks like.

One thing that might look odd: `Paragraph` is both the element and the component
type. So `const Greeting: Paragraph` declares what this node returns, and
returning a `<Section>` from it is a compile error.

## Text you want generated

Some paragraphs can't be written in advance, because what they should say
depends on who is receiving them. For those, set **prompts** instead of text —
plus a **placeholder**, so your preview stays readable:

```tsx
export const TutorNote: Paragraph = () => {
  const [state] = useState((data: DocumentData) => ({
    applicantName: data.applicantName,
    interviewer: data.interviewer,
  }));

  useSetPrompts({
    generalPrompt: `Write two warm sentences about ${state.applicantName}'s interview.`,
  });
  useSetPlaceholders(`A note from ${state.interviewer}.`);

  return <Paragraph id="tutor-note" />;
};
```

There's nothing to declare and no mode to pick.

A node that has its text can be produced on your machine. A node that has only
prompts needs the engine. Your component decides which it is simply by what it
supplies, the build works the rest out, and the package tells the engine which
nodes it has to resolve.

Supplying both on one element is an error, not a coin toss.

You can also pass prompts as props, which reads better when they're short. Props
win over the hook, so a caller can override whatever a shared hook set around it.

Five prompt slots are available. Only `generalPrompt` is required:

| Slot | Purpose |
| --- | --- |
| `generalPrompt` | What the node should say |
| `infoPrompt` | Context the model should have but not restate |
| `negativePrompt` | What to avoid |
| `systemPrompt` | Role and tone instructions |
| `examplePrompt` | What a good answer looks like, written out |

`examplePrompt` is the one worth reaching for first. Telling a model what shape
you want leaves it guessing; showing it one gives it something to copy. An
example locks down the parts you don't want changing — how it opens, what order
things come in, how long it is, how formal it sounds — so the model only fills in
what genuinely differs between documents. Write it as finished text, not as a
fill-in-the-blanks form, or it just describes the shape again.

`Image` and `Graph` work the same way — give one `src` or `data` and it resolves
locally; give it prompts and it does not.

## What the preview shows

Building for preview resolves prompted nodes to their **placeholders**, never to
generated text. Previews come out the same every time and cost nothing: you can
work on structure and styling without a single request leaving your machine.

Placeholders are a useful habit, too. If a document reads badly with
placeholders in place, that's usually a sign its structure is doing too little
work.

`usePlaceholderData` gives you stand-in names, dates and figures, seeded from
where the component sits — so the same node shows the same values every time. A
preview that reshuffles itself on every build is one nobody can proofread.

## Ids

An id is how an engine addresses a node, and how two build artifacts line up when
you diff them. Treat renaming one as a breaking change.

**You can usually leave it out.** A node without an id is named after what it is,
which is what stops branches and lists from forcing you to invent names for
nodes nobody refers to.

The one thing you can't do is use an id twice. Two nodes claiming the same id is
an error, reported with both positions, rather than a race the later one wins.

## Where to go next

- [Document projects](/docs/document-projects/) — the files around your nodes
- [Documents and nodes](/docs/essentials/documents-and-nodes/) — the component model in full, including hooks
- [The node model](/docs/nodes/overview/) — every node type, with previews of each
