# Bundle

Groups the documents of one composition.

Canonical URL: https://docs.asterdoc.com/components/bundle/

`Bundle` is the outermost wrapper for a composition of more than one
document — a purchase order and the packet around it, a proposal and its
attachments. It carries no pagination of its own; grouping documents under
one bundle is what lets a shared set of tenant tokens, like `accentColor`,
reach every one of them at once.

## Preview


```tsx
import { Bundle } from "@/components/asterdoc/bundle";
import { Document } from "@/components/asterdoc/document";
import { Field } from "@/components/asterdoc/field";
import { proposalForm } from "@/examples/proposal";
import { shortProposalData } from "@/examples/proposal-data";

export function BundleDemo() {
  return (
    <Bundle id="bundle-demo">
      <Document artifact={proposalForm} data={shortProposalData} id="bundle-demo-cover">
        <Field path="provider" />
      </Document>
      <Document artifact={proposalForm} data={shortProposalData} id="bundle-demo-terms">
        <Field path="terms" label={false} />
      </Document>
    </Bundle>
  );
}
```


## Installation


```bash
npx shadcn@4 add @asterdoc/bundle
```


```bash
asterdoc add bundle
```


Installs to components/asterdoc/bundle.tsx.


## Usage


```tsx
import { Bundle } from "@/components/asterdoc/bundle";

<Bundle>
  <Document artifact={proposalForm} data={shortProposalData}>
    <Field path="customer" />
  </Document>
</Bundle>
```

| Prop | Type | Optional | Default | Description |
| --- | --- | --- | --- | --- |
| `id` | `string` | Yes | "bundle" | Groups this bundle's documents under one id, distinguishing bundles
rendered side by side. |
| `tokens` | `DocumentTokensInput` | Yes |  | Tenant branding cascaded to every document and component nested inside the bundle. |
| `className` | `string` | Yes |  | Classes for the wrapping element around the bundle's documents. |
| `children` | `ReactNode` | No |  | The bundle's documents. |


## Composition

A `Bundle` isn't required around a single [`Document`](/components/document) —
a lone `Document` is its own root — but a composition of more than one
document always has exactly one `Bundle` at the top, and only ever one:
whichever of `Bundle` or `Document` renders first without an inherited token
set becomes the root, and every `Document` nested inside it inherits that set
instead of resolving its own.

Only the root may set `pageSize`, `marginPx`, `dir`, `lang`, or `typography` — a `Document`
nested inside a `Bundle` that tries to set one of these throws, because those
tokens decide the paper every document in the bundle shares. `accentColor`
and `logo` carry no such restriction: a nested `Document` may still override
either for itself.

`Bundle` renders as an ordinary `<div data-bundle-id>` in a column by
default, and it brings along no [`Keep Together`](/components/keep-together)
of its own. Pagination is not its job: rendering a bundle live means wrapping
it in [`Pages`](/components/pages), which measures and paginates the whole
tree beneath it.

The gap between the bundle's documents follows the
[typography](/components/typography) token's `flow`; a `className` you pass
replaces it and wins.

## Variants

`Bundle` takes an optional `id`, `tokens`, and a `className` that replaces
its default column layout.

### Single document

The common shape: a `Bundle` around exactly one `Document`.


```tsx
import { Bundle } from "@/components/asterdoc/bundle";
import { Document } from "@/components/asterdoc/document";
import { Field } from "@/components/asterdoc/field";
import { proposalForm } from "@/examples/proposal";
import { shortProposalData } from "@/examples/proposal-data";

export function BundleVariantSingleDocument() {
  return (
    <Bundle id="bundle-variant-single-document">
      <Document artifact={proposalForm} data={shortProposalData} id="bundle-variant-single-document-doc">
        <Field path="customer" />
      </Document>
    </Bundle>
  );
}
```


### Row layout

A custom `className` lays the bundle's documents out in a row instead of a
column.


```tsx
import { Bundle } from "@/components/asterdoc/bundle";
import { Document } from "@/components/asterdoc/document";
import { Field } from "@/components/asterdoc/field";
import { proposalForm } from "@/examples/proposal";
import { shortProposalData } from "@/examples/proposal-data";

export function BundleVariantRow() {
  return (
    <Bundle id="bundle-variant-row" className="flex flex-row gap-8">
      <Document artifact={proposalForm} data={shortProposalData} id="bundle-variant-row-provider">
        <Field path="provider" />
      </Document>
      <Document artifact={proposalForm} data={shortProposalData} id="bundle-variant-row-customer">
        <Field path="customer" />
      </Document>
    </Bundle>
  );
}
```


### Branded tokens

`tokens` cascades to every document nested inside — here, a titled section's
heading picks up the accent color.


```tsx
import { Bundle } from "@/components/asterdoc/bundle";
import { Document } from "@/components/asterdoc/document";
import { Field } from "@/components/asterdoc/field";
import { Section } from "@/components/asterdoc/section";
import { proposalForm } from "@/examples/proposal";
import { shortProposalData } from "@/examples/proposal-data";

export function BundleVariantBrandedTokens() {
  return (
    <Bundle id="bundle-variant-branded-tokens" tokens={{ accentColor: "#0f766e" }}>
      <Document artifact={proposalForm} data={shortProposalData} id="bundle-variant-branded-tokens-doc">
        <Section id="summary" title="Summary">
          <Field path="summary" label={false} />
        </Section>
      </Document>
    </Bundle>
  );
}
```
