Skip to main content
zerotal

Forms

form() returns fields (and layout components). An empty form() disables Create/Edit for the resource.

import {
  textInput, textarea, select, toggle, datePicker, fileUpload, richEditor,
  formSection, formTabs, formTab, wizard, wizardStep,
} from "@zerotal/admin";

static form() {
  return [
    formSection("Content").columns(2).schema([
      textInput("title").required().maxLength(160).columnSpan(2),
      textInput("slug").required()
        .live().afterStateUpdated((v) => ({ slug: slugify(String(v)) })),
      select("status").options({ draft: "Draft", published: "Published" }).required(),
      datePicker("published_at").visible((d) => d.status === "published"),
    ]),
    formSection("Body").schema([
      richEditor("body"),
      fileUpload("cover").image().disk("covers"),
    ]),
  ];
}

Field catalogue

FactoryRenders
textInput(k)text — plus .email()/.password()/.numeric()/.url()/.tel()
textarea(k)multi-line text (.rows(n))
select(k)select — .options()/.optionsUsing()/.multiple()/.searchable()/.createOption()
checkbox(k), toggle(k)boolean
radio(k), checkboxList(k)single / multi choice
datePicker, dateTimePicker, timePicker, colorPickernative pickers
tagsInput(k)tag/token array
keyValue(k)key/value object editor
fileUpload(k)upload — .image()/.disk(dir)/.accept(mime)/.multiple()
slider(k)range → number (.min()/.max()/.step())
toggleButtons(k)segmented buttons (.multiple())
codeEditor, markdownEditor, richEditorcode / markdown / WYSIWYG editors
hidden(k)retained-but-hidden value
repeater(k), builder(k)nested object-arrays (see below)

A control the catalogue lacks is customField(k).render(fn) — see Extending the UI.

Common modifiers

.label(), .placeholder(), .helperText(), .default(v), .required(), .minLength()/.maxLength()/.min()/.max(), .confirmed(), .rule(fn), .columnSpan(n), .disabled(), .visibleOn("create"|"edit") / .hiddenOn(...), and the reactive .visible((data) => bool) / .disabledWhen((data) => bool).

.live() re-evaluates dependent fields on change; .afterStateUpdated((v, data) => patch) runs server-side and merges a patch into the form (e.g. derive a slug).

Layout components

ComponentPurpose
formSection(heading).columns(n).schema([])Titled, multi-column card.
formTabs([formTab("X").schema([])])Tabbed groups (client-side switching).
wizard([wizardStep("X").schema([])])Stepped form with per-step validation.
fieldset(legend)Bordered <fieldset>/<legend> group.
split([sectionA, sectionB])Side-by-side sections.
callout(text).tone().icon().heading()A toned notice block.
prime / primeHtml / primeImageStatic text / HTML / image display.

Repeater & Builder

repeater edits an array of objects sharing one sub-schema; builder edits an array of typed blocks. Rows can be added, removed, and reordered.

import { repeater, builder, builderBlock, textInput, textarea, fileUpload } from "@zerotal/admin";

repeater("contacts").minItems(1).addActionLabel("Add contact").schema([
  textInput("name").required(),
  textInput("email").email(),
]),

builder("content").blocks([
  builderBlock("paragraph").schema([textarea("text")]),
  builderBlock("image").icon("photo").schema([fileUpload("src").image()]),
]),

Repeaters serialize to [{ ...fields }]; builders to [{ type, data }].

Infolists (View page)

infolist() describes the read-only detail page. Omit it to fall back to a section derived from columns().

import { section, textEntry, iconEntry } from "@zerotal/admin";

static infolist() {
  return [
    section("Overview").columns(2).schema([
      textEntry("title").weight("semibold"),
      textEntry("status").badge().color((v) => (v === "published" ? "success" : "muted")),
      textEntry("email").copyable(),
      textEntry("created_at").since(),
      textEntry("price").money("USD"),
      iconEntry("featured").boolean(),
    ]),
  ];
}

Entry kinds

textEntry covers most of a view page. The rest exist because some values are not usefully read as text:

FactoryRenders
textEntry(key)A value, with .badge(), .date(), .money(), .copyable(), .url().
iconEntry(key)A check or a cross for a boolean.
imageEntry(key)An image from a URL — .circular(), .height(px).
colorEntry(key)A swatch beside the value.
codeEntry(key)A monospace block — .language("json").
keyValueEntry(key)An object as a two-column table of its pairs.
repeatableEntry(key)A nested .schema([…]) rendered once per array item.

repeatableEntry is the read side of repeater: whatever the form wrote into a JSON column, this reads back in the same shape.

section("Line items").schema([
  repeatableEntry("lines")
    .placeholder("This order has no line items.")
    .schema([
      textEntry("sku").label("SKU"),
      textEntry("description"),
      textEntry("quantity").label("Qty"),
      textEntry("unitPrice").label("Unit price").money("USD"),
    ]),
]),

Next steps

  • Admin overview — the guide's front page and the rest of the sections.
  • Reference — the full API surface in one table.