# Field

One labelled value at a path into the artifact, printed by the artifact's serializers.

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

`Field` is the leaf of every composition: the smallest thing that reads from
the artifact and prints something a person can read. It carries no layout of
its own beyond its label, and it is always a `KeepTogether`, so a page break
never separates a label from the value underneath it.

## Preview


```tsx
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 FieldDemo() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="field-demo">
      <div className="flex flex-col gap-4">
        <Field path="provider" />
        <Field path="customer" />
        <Field path="issuedOn" />
      </div>
    </Document>
  );
}
```


## Installation


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


```bash
asterdoc add field
```


Installs to components/asterdoc/field.tsx.


## Usage


```tsx
import { Field } from "@/components/asterdoc/field";

<Field path="provider" />
```

| Prop | Type | Optional | Default | Description |
| --- | --- | --- | --- | --- |
| `path` | `string` | No |  | Path into the artifact this field reads its label and value from. `as="image"` names an annex slot as `annexes.<slot>`. |
| `label` | `string \| false` | Yes |  | Overrides the field's heading: a string replaces it, `false` hides it, omitted uses the artifact's own label. |
| `as` | `"text" \| "image"` | Yes | "text" | How the value is printed. `"image"` draws the attachment at `annexes.<slot>`
as a picture, and prints its file name when its MIME type is not one. |
| `width` | `number` | Yes |  | Rendered width in CSS pixels. Required by `as="image"`, whatever the slot holds; ignored by `as="text"`. |
| `height` | `number` | Yes |  | Rendered height in CSS pixels. Required by `as="image"`, whatever the slot holds; ignored by `as="text"`. |
| `src` | `string` | Yes |  | Where a browser preview loads an `as="image"` picture from. Defaults to the attachment's own file name, which is the key the PDF path supplies its bytes under. Ignored by `as="text"`. |
| `paragraphs` | `boolean` | Yes | false | Splits the value at its blank lines and paginates each paragraph on its own, keyed `field:<path>:<index>`. Text only: ignored by `as="image"`, which is always one keep. |
| `rule` | `boolean` | Yes | false | Draws a fill line in place of the blank placeholder when the artifact has no value at this path. Text only: ignored by `as="image"`, which prints the slot's own placeholder. |
| `className` | `string` | Yes |  | Classes for the field's wrapping element: the keep itself, or the element holding the paragraphs. |


## Composition

`Field` always sits inside a [`Document`](/components/document): it reads the
current artifact and its data through context that only `Document` provides,
and rendering one outside a `Document` throws. Most fields sit inside a
[`Section`](/components/section) that groups them under a heading, but
nothing requires that — the Preview above renders three `Field`s directly
inside `Document`, with no `Section` around them.

The `path` prop follows Asterdoc's shared path grammar: a top-level field name
like `customer`, or a path into a list item like `lineItems[0].unitPrice`.
Numeric indexes can also use dot notation (`lineItems.0.unitPrice`). Empty
segments and the unsafe object members `__proto__`, `prototype`, and
`constructor` are rejected. `Field` does not validate or format anything
itself — it asks the artifact for the field at that path and the value's
serializer for the text to print.

Every `Field` is a [`Keep Together`](/components/keep-together),
which it brings along automatically, and that is also its whole pagination
story: a field is never split across a page break, and it renders only on
the page the plan assigned it to. A [`Table`](/components/table)'s cells
reuse the same serializers a `Field` does, just as flex cells rather than as
`Field` instances.

A newline inside a value is a line break in both outputs — the value is drawn
with `whitespace-pre-line`, so a value typed on three lines prints on three
lines. A newline never paginates: a blank line only splits the value into
paragraphs when `paragraphs` is set, and then each paragraph is its own
pagination unit. Without it, a value longer than a page is one keep, and a
keep that does not fit the page opened for it overflows and is reported as
oversize.

The label's size follows the document's [typography](/components/typography)
token; the value inherits the root's.

`as="image"` prints the other thing a document binds by path: an attachment.
An attachment is not a field value — the artifact declares annex slots and the
filled data carries one per slot — so the path is `annexes.<slot>`, the
heading comes from the slot's own title, and `width` and `height` say how big
to draw it. An attachment whose MIME type is not a picture prints its file
name instead, and `asterdoc check` reports the mismatch. See
[`Image`](/components/image).

## Variants

Beyond `path` and `className`, `Field` takes `label`, which can be omitted
(uses the artifact's own label), set to `false` (no label), or overridden with
a string; `paragraphs`, which splits a long value into one pagination unit per
paragraph; and `rule`, which draws a fill line where a value is missing.
`paragraphs` and `rule` apply to text only. `as`, `width`, `height` and `src`
belong to the attachment form above, and are shown on
[`Image`](/components/image).

### Default label

With no `label` prop, the heading comes from the artifact's own field
definition.


```tsx
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 FieldVariantDefaultLabel() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="field-variant-default-label">
      <Field path="customer" />
    </Document>
  );
}
```


### No label

`label={false}` renders the value alone, with no heading above it.


```tsx
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 FieldVariantNoLabel() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="field-variant-no-label">
      <Field path="customer" label={false} />
    </Document>
  );
}
```


### Custom label

A string `label` overrides the artifact's own.


```tsx
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 FieldVariantCustomLabel() {
  return (
    <Document artifact={proposalForm} data={shortProposalData} id="field-variant-custom-label">
      <Field path="customer" label="Billed to" />
    </Document>
  );
}
```


### Paragraphs

`paragraphs` splits the value at its blank lines and gives each paragraph its
own keep, `field:<path>:<index>`, so long prose breaks between paragraphs at a
page boundary rather than overflowing as one oversize unit. The label stays
with the first paragraph. A single paragraph taller than a page is still
oversize: a pagination unit is never split inside itself.


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

/** The sample's terms, as a filler who pressed return twice would supply them. */
const data = {
  ...shortProposalData,
  fields: {
    ...shortProposalData.fields,
    terms:
      "Payment is due 30 days from invoice. Work begins once both parties sign.\n\nEither party may end the engagement with 14 days written notice; work completed to that date remains payable.",
  },
};

export function FieldVariantParagraphs() {
  return (
    <Document artifact={proposalForm} data={data} id="field-variant-paragraphs">
      <Field path="terms" paragraphs />
    </Document>
  );
}
```


### Fill line

`rule` draws an underscore run where the artifact has no value, instead of the
document's blank placeholder — a document printed for somebody to write on
rather than one nobody will.


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

/** The sample with the two fields a countersigning customer fills in by hand taken back out. */
const data = {
  ...shortProposalData,
  fields: { ...shortProposalData.fields, customerContact: undefined, validUntil: undefined },
};

export function FieldVariantRule() {
  return (
    <Document artifact={proposalForm} data={data} format={{ partial: true }} id="field-variant-rule">
      <div className="flex flex-col gap-4">
        <Field path="customer" />
        <Field path="customerContact" rule />
        <Field path="validUntil" rule />
      </div>
    </Document>
  );
}
```
