Skip to Content
For DevelopersField reference

Field reference

Every field supports these base properties:

interface BaseField { label?: string; description?: string; icon?: ReactNode; required?: boolean; // Shows a red asterisk, flagged in publish gates tab?: string; // Groups fields under a `tabs` entry on the component }

text

Single-line input.

{ type: "text", label: "Title", placeholder: "…", maxLength: 80 }

Value: string

textarea

Multi-line input.

{ type: "textarea", label: "Body", rows: 4, placeholder: "…" }

Value: string

richtext

Toolbar + textarea. Markdown-flavoured output (bold, italic, links, lists, headings).

{ type: "richtext", label: "Content" }

Value: string (markdown-like)

number

Number input with increment / decrement.

{ type: "number", label: "Columns", min: 1, max: 6, step: 1 }

Value: number

boolean

Toggle switch.

{ type: "boolean", label: "Full bleed" }

Value: boolean

select

Dropdown; single choice.

{ type: "select", label: "Alignment", options: [ { label: "Left", value: "left" }, { label: "Center", value: "center" }, { label: "Right", value: "right" }, ], }

Value: whatever the chosen option’s value is (unknown generic).

radio

Segmented pill group; single choice. Same options shape as select.

{ type: "radio", label: "Size", options: [ { label: "S", value: "sm" }, { label: "M", value: "md" }, { label: "L", value: "lg" }, ]}

Value: same as select.

URL + visible label + target.

{ type: "link", label: "Button" }

Value:

{ url: string; label?: string; target?: "_self" | "_blank" }

asset

URL + preview + alt text, with an “Open library” button that opens the asset picker.

{ type: "asset", label: "Hero image", accept: "image" }

accept is "image" | "video" | "file" or any custom MIME-style string you want your picker to respect.

Value:

{ url: string; alt?: string }

array

A repeating list of sub-forms. Each item is a plain object.

{ type: "array", label: "Features", arrayFields: { title: { type: "text", label: "Title" }, description: { type: "textarea", label: "Description" }, }, defaultItemProps: { title: "", description: "" }, getItemSummary: (item) => item.title as string, min: 1, max: 6, }

Value: Array<Record<string, unknown>>

object

A grouped nested form — always one object, not a list.

{ type: "object", label: "Address", objectFields: { street: { type: "text", label: "Street" }, city: { type: "text", label: "City" }, }, }

Value: Record<string, unknown>

slot

A canvas drop-zone for nesting other blocks. Not rendered in the inspector — it shows on the canvas as a drop target.

{ type: "slot", label: "Contents", allow: ["Hero", "Features"], // optional: only these block types disallow: ["Grid"], // optional: exclude these block types }

Value: handled by Blok — stored in data.zones[`${blockId}:${fieldName}`].

custom

A developer-defined input. Render anything.

{ type: "custom", label: "Brand color", render: ({ value, onChange }) => ( <ColorPicker value={value as string} onChange={onChange} /> ), }

Props passed to render:

interface CustomFieldRenderProps<Value> { value: Value; onChange: (value: Value) => void; id: string; name: string; readOnly?: boolean; }

Tabs

Group many fields under tabs by declaring a tabs array on the component, then setting tab: "<id>" on each field:

Hero: { tabs: [ { id: "content", label: "Content" }, { id: "style", label: "Style" }, ], fields: { title: { type: "text", label: "Title", tab: "content" }, align: { type: "select", label: "Alignment", tab: "style", options: [...] }, }, }

Fields without a tab go under the first tab.

Custom field types via plugins

If you want a reusable field type (e.g. a date picker shared across blocks), register it via a plugin. See Plugin API.

Last updated on