Skip to content

textarea field

A textarea() field stores multi-line string values — anything longer than a single-line text() field is comfortable holding. It shares text()’s min/max length semantics and adds a rows option controlling the rendered height.

| Option | Type | Default | Description | |--------|------|---------|-------------| | label | string | "" | Display label in the admin panel. Inferred from the field key by defineCollection when left empty. | | required | boolean | false | When false, the field is wrapped in v.optional(…) in the schema and the form allows leaving it empty. | | defaultValue | string | "" | Pre-filled value shown in the admin form when creating a new document. | | min | number \| [value: number, error: string] | — | Minimum character length constraint. | | max | number \| [value: number, error: string] | — | Maximum character length constraint. The character counter below the textarea shows n/max when set. | | rows | number | 4 | Visible textarea rows in the admin form. | | index | string \| { name, additionalFields?, draftStatus? } | — | Convex index name, or the object form for a compound index. | | searchIndex | { name: string, filterFields: string[] } | — | Full-text search index on this field. The field becomes the searchField. | | description | string | — | Helper text shown below the field input. | | validate | (props) => Promise<void> \| void | — | Server-only async validation, receives the resolved string value. | | admin.hidden | boolean | false | Hides the field from the admin edit form entirely. | | admin.readOnly | boolean | false | Renders the input as non-interactive — value is visible but cannot be changed. | | admin.position | "main" \| "sidebar" | "main" | Column in the admin edit layout where this field appears. | | admin.width | string (CSS width) | "100%" | Width of the field inside a row(), e.g. "50%" or "240px". | | admin.cellAlignment | "left" \| "center" \| "right" | "left" | Horizontal alignment of the value in the list-view table cell. | | admin.placeholder | string | — | Placeholder text shown when the input is empty. | | admin.description | string | — | Helper text displayed below the field input. | | admin.condition | ({ data, siblingData }) => boolean | — | Pure predicate deciding whether the field is shown and validated. A throwing condition counts as met. |

// Optional (required: false — default)
bio: v.optional(v.string())
// Required with length constraints
notes: v.string()
// With a search index
bio: v.string(),
.searchIndex("search_bio", { searchField: "bio", filterFields: [] })
import { defineCollection, textarea } from "@vexcms/core";
const authors = defineCollection({
slug: "authors",
fields: {
// Minimal — label inferred from key ("Bio")
bio: textarea(),
// Required, length-limited, taller textarea
bio: textarea({ required: true, min: 10, max: 500, rows: 8 }),
// Min/max with a custom error message via the [value, error] tuple
notes: textarea({ max: [280, "Keep it under 280 characters."] }),
},
});

The textarea field renders a <Textarea> sized by rows (default 4). A right-aligned character counter below the textarea updates as the user types: n/max when max is configured, the bare count n otherwise. placeholder and description display inline. In the list-view table, the value is truncated to one line.