Compose DOCX documents from small typed components, using the JSX you already write. Write and preview them on your machine, then generate finished documents through the engine.
Documentation · docxcelerate.com
Node.js 20 or newer. Nothing else — authoring, preview and DOCX packing all run locally.
npx docxcelerate init my-documents
cd my-documents
npm run devThis creates a workspace and opens the preview app, where you can write documents, edit and add nodes, and pack the result into a finished DOCX.
To get the dxcl binary on your path:
npm install -g docxcelerateA document is a small project of its own, under documents/<name>/. The tree
lives in document.tsx, and document.project.ts beside it is the
entrypoint that pairs the template with the data, style and derivers it is built
with.
// documents/tenancy-renewal/document.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 title="Tenancy Renewal">
<Section id="opening" title="Opening">
<Nodes.Greeting />
<Nodes.Balance />
</Section>
</Document>,
);If you are familiar with frontend frameworks, the idea of having an entrypoint for your application maps well onto the shape of how documents are structured with Docxcelerate. The document's id is taken from its title when you do not write one.
We build components which represent the contents of a document: a paragraph, a
table, an image, a graph, a page break, a table of contents. Each one declares
what it yields, and reads data through useState — the only way data enters a
component.
// documents/tenancy-renewal/nodes/balance.node.tsx
import { Paragraph, useState } from "docxcelerate/template";
import type { DocumentData } from "../types.ts";
export const Balance: Paragraph = () => {
const [state] = useState((data: DocumentData) => ({
name: data.recipientName,
settled: data.balanceDue === 0,
}));
if (state.settled) {
return <Paragraph id="settled">Nothing outstanding, {state.name}.</Paragraph>;
}
return <Paragraph id="arrears">There is a balance left to settle, {state.name}.</Paragraph>;
};Some nodes are written per document rather than at build time. useAi is what
says so — one call carrying what to ask for, and what stands in the node's place
until something has written it.
import { Paragraph, useAi, useState } from "docxcelerate/template";
import type { DocumentData } from "../types.ts";
export const Balance: Paragraph = () => {
const [state] = useState((data: DocumentData) => data.account);
useAi({
ask: "Explain the balance on this account and what to do about it.",
placeholder: "A short note about the balance left on this account.",
voice: "A housing officer writing to a tenant. Plain, no sales tone.",
from: { balanceDue: state.balanceDue, dueBy: state.dueBy },
avoid: "Do not invent a payment method or a phone number.",
example:
"Your account is £248.50 in arrears, and the balance is due by 14 March. " +
"Please get in touch before then so we can agree how to clear it.",
});
return <Paragraph id="balance" />;
};You can imagine that if the account is a long way into arrears the engine may
choose to write something firmer than it would at a few pounds. The placeholder
is required, and deliberately so: a preview is how a document gets proofread, and
a document proofread with a blank in it is a document nobody read.
example is the one that does the most for the least. Describing a format is
something a model has to interpret; showing it one is something it can match, so
an example holds still the parts of a node that were never meant to vary — the
opening, the order, the length, the register — and leaves only what genuinely
differs per document. Write it as finished text rather than a form with blanks in
it, and pass an array of them when the shape legitimately varies by case.
Jump into our documentation to understand how you can structure documents with AI and create more complex documents for your own needs.
The registry ships nodes that are already written — a letterhead, a recipient
block, a signature block, a payment summary that branches on whether an account is
in credit, clear or owing. dxcl add copies one into your document project as
source you own and can edit, and wires it into nodes/index.ts.
dxcl list # every component and theme
dxcl show payment-summary # what it renders, and the fields it reads
dxcl add payment-summary # copy it into documents/<name>/nodes/Themes install the same way and write the project's document-style.ts:
dxcl add slate-reportBrowse them all at docxcelerate.com/registry.
Everything is on docxcelerate.com:
- Start here — install the CLI and scaffold a workspace
- Writing nodes — the pieces a document is made of, and where AI fits
- Document projects — the files in a document, and what configures them
- Documents and nodes — the model
- Templates — composing with JSX
- The node reference — every node type, rendered
- The registry — components and themes to install
- CLI commands — every
dxclcommand - The engine — generating at scale
- Package entrypoints — what each import gives you
npm install # install dependencies
npm run build # compile src/ to dist/ with type declarations
npm test # build, then run the Node test suite
npm run typecheck # type-check sources and tests
npm run jsr:doc # check every entrypoint and exported symbol is documentedjsr:doc is what keeps the package's JSR score at nine out of nine. It needs
Deno on your path, and runs on every pull request that
touches src/.
The published package ships compiled output from dist/ plus the dxcl binary.
Pushing a change to src/ on main publishes a new version.
MIT
