# Keep Together

The pagination unit. Content inside one is never split across a page break.

Canonical URL: https://docs.asterdoc.com/components/keep-together/

`Keep Together` is what every other component here is built from: a `Field`
is already one, a titled `Section`'s heading is another, and each of a
`Table`'s header and rows is one more. It carries no layout of its own — `as`
decides the element it renders as, and any other prop, `className` included,
passes straight through to that element.

## Preview


```tsx
import { Document } from "@/components/asterdoc/document";
import { KeepTogether } from "@/components/asterdoc/keep-together";
import { proposalForm } from "@/examples/proposal";
import { shortProposalData } from "@/examples/proposal-data";

export function KeepTogetherDemo() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="keep-together-demo">
      <KeepTogether as="span" keepId="keep-together-demo-title" className="text-lg font-semibold text-neutral-900">
        {proposalForm.title}
      </KeepTogether>
    </Document>
  );
}
```


## Installation


```bash
npx shadcn@4 add @asterdoc/keep-together
```


```bash
asterdoc add keep-together
```


Installs to components/asterdoc/keep-together.tsx.


## Usage


```tsx
import { KeepTogether } from "@/components/asterdoc/keep-together";

<KeepTogether as="span" keepId="keep-together-demo-title" className="text-lg font-semibold text-neutral-900">
  {proposalForm.title}
</KeepTogether>
```

| Prop | Type | Optional | Default | Description |
| --- | --- | --- | --- | --- |
| `keepId` | `string` | No |  | Stable id the page plan tracks this content by; must be unique within the document. |
| `as` | `ElementType` | Yes | "div" | Element or component the keep renders as. |
| `children` | `ReactNode` | Yes |  | Content kept together as one pagination unit. |


## Composition

`Keep Together` renders fine with no [`Document`](/components/document)
above it — unpaginated, every keep just renders — but the id it carries only
means something once a [`Pages`](/components/pages) preview has measured the
tree and produced a plan: `keepId` must then be unique within the document,
the same requirement a [`Section`](/components/section)'s own `id` follows.

It wraps arbitrary content: a short piece of text, an image, or a handful of
other components grouped so they are never separated across a page break.
[`Field`](/components/field) wraps its own value in one automatically, and so
does a titled [`Section`](/components/section)'s heading and each of a
[`Table`](/components/table)'s rows — composing directly with `Keep Together`
is only needed for content none of those three already covers.

A repeated copy of a keep — the way a `Table`'s header reappears on a page
its rows continue onto — carries `data-keep-repeat`, so it's never confused
with the keep's one real place in the flow. A plain `Keep Together` never has
to know whether it's being repeated; only `Table` produces repeats today.

## Variants

`Keep Together` takes a required `keepId`, an optional `as` (default `"div"`),
and any other prop, which is spread onto the rendered element.

### Default element

With `as` omitted, `Keep Together` renders a `div` — here, grouping a couple
of fields so they are never separated across a page break.


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

export function KeepTogetherVariantDefaultElement() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="keep-together-variant-default-element">
      <KeepTogether keepId="keep-together-variant-default-element" className="flex flex-col gap-1 rounded border border-neutral-200 p-3">
        <Field path="customer" label={false} className="font-medium text-neutral-900" />
        <Field path="customerAddress" label={false} className="text-neutral-600" />
      </KeepTogether>
    </Document>
  );
}
```


### Custom element

`as` renders a different element — here, a `header` laying its two fields
out in a row.


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

export function KeepTogetherVariantCustomElement() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="keep-together-variant-custom-element">
      <KeepTogether as="header" keepId="keep-together-variant-custom-element" className="flex flex-row gap-6">
        <Field path="provider" />
        <Field path="customer" />
      </KeepTogether>
    </Document>
  );
}
```


### Passthrough attributes

Any prop beyond `keepId`, `as`, and `children` is spread onto the rendered
element, here an `aria-label`.


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

export function KeepTogetherVariantPassthroughAttributes() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="keep-together-variant-passthrough-attributes">
      <KeepTogether keepId="keep-together-variant-passthrough-attributes" aria-label="Customer summary" className="flex flex-col gap-1">
        <Field path="customer" label={false} />
      </KeepTogether>
    </Document>
  );
}
```
