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.
link
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.