Skip to content

Widgets

Widgets control how fields are displayed and edited.

When no explicit widget is specified in a view, the system automatically selects a default widget based on the field type:

Field TypeDefault Widget
CharTextInput
TextTextArea
IntegerNumberInput
FloatNumberInput
MonetaryMonetary
BooleanCheckbox
DateDatePickerInput
DatetimeDateTimePicker
SelectionSelectCombo
ManyToOneDataCombo
OneToOneDataCombo
ManyToManyMultiCombo
OneToManyList
FileFileInput
JSONJSONInput

You can override the default by specifying a widget property:

- type: field
name: status
properties:
widget: Badge

User relations always render with an avatar

Section titled “User relations always render with an avatar”

A relation to the User model (a ManyToOne/OneToOne, e.g. assigned_to, manager, salesperson) should show a face, not just a name. So it is resolved to the avatar widget variant automatically — AvatarCombo on Form and List views, Avatar on Kanban cards — and its avatarField defaults to the user’s image. You do not need to set widget or avatarField on a user field; it renders as an avatar with no configuration.

This applies whether the field specifies no widget or an explicit DataCombo/Text — a plain user picker is never the intent, so the avatar wins. A user with no image falls back to auto-coloured initials.

Opt out on the rare field where a plain text picker is genuinely wanted (e.g. a very dense grid) with avatar: false:

- type: field
name: assigned_to
properties:
avatar: false # keep the plain DataCombo, no avatar

Notes:

  • Only single-user relations are upgraded. A ManyToMany to User keeps MultiCombo (there is no avatar multi-select widget).
  • An explicit avatar widget or any other deliberate widget choice is left untouched.
  • This is scoped to the User model only.

Widgets support different view modes (Form, List, FormList, Kanban, Display). When a widget is used in an unsupported mode, it falls back to display mode if available, or shows “Unsupported widget”.

WidgetFormListFormListKanbanDisplayNotes
Dashboard Widgets
Card---KPI/metric display
CompactList--Summary table/list
Display Widgets
LinkedRecords---Related records with actions
Avatar
Badge
BarChart--Not suitable for inline table cells
AreaChart--Not suitable for inline table cells
SparklineCompact, suitable for inline
Image
ProgressBar
Rating
UrlAnchor + copy button; read-only
StatusBar----Top-level form element type: statusbar, not a field widget
Input Widgets
TextInput
TextArea
NumberInput
Monetary
Checkbox
Switch
DatePickerInput
DateTimePicker
DateRangePicker---Form + Display only
TimeInput
ColorInput
RangeSlider--Not suitable for inline editing
Slider--Not suitable for inline editing
RichTextEditor---Form + Display only
FileInput
DropzoneDrag-and-drop single file upload
PdfViewer---Inline PDF preview of a File field
RadioCards
PasswordInput----Form-only by design
JSONInput
FormulaInputSandboxed formula editor with ƒx palette
MultiFileUploadMultiple file attachments
CodeEditorSyntax-highlighted code (CodeMirror)
SignatureCanvas signature; stored as PNG data URL
Select Widgets
SelectCombo
Radio
DataCombo
AvatarComboDataCombo with avatar display
MultiCombo
GroupAccessManager--Complex UI, form-only editing
MultiOptionGroup--Complex UI, form-only editing
OptionGroups---Renders a OneToMany as one labelled option group per row instead of a grid — see below
Layout Widgets
List---Embedded editable list (FormList)
Tags-
TitleSection headers
Divider---Visual separator
Button---Action button
LinkButtonElement type: linkButton, not a field widget
Additional Input Widgets
PinInput----Form-only, numeric PIN entry
SegmentedControlButton group selection
MonthPickerInputMonth selector
YearPickerInputYear selector
ObjectFieldsInput---JSON object editor
FilterBuilder---Query filter builder
Additional Display Widgets
Icon--Lucide icon display
BoxedIcon--Icon with background
WebsitePreview--Website iframe preview
SummaryIndicatorView-icon indicator; click opens a detail popover

Legend:

  • ✓ = Supported
  • - = Not supported (falls back to display mode or shows “Unsupported widget”)

Single-line text input.

- type: field
name: name
properties:
widget: TextInput
placeholder: Enter name
size: lg
PropertyDescription
placeholderPlaceholder text
sizexs, sm, md, lg, xl
disabledDisable input

Multi-line text input.

- type: field
name: description
properties:
widget: TextArea
rows: 4
placeholder: Enter description

WYSIWYG HTML editor: headings, bold/italic/underline, links, bullet/numbered/check lists, inline images, and tables.

- type: field
name: content
properties:
widget: RichTextEditor

What survives a round-trip. The editor stores a document, not a string of markup: HTML you write into the field is parsed into that document and re-serialized when it is read back, and anything the document model has no place for is dropped — only its text is kept. The elements listed above round-trip, and so does a style attribute on a table, row or cell (which is how a table keeps its look once it is mailed, since mail clients drop <style> blocks). Markup outside that set — a <div> wrapper, a <section>, a styled <span> — is not preserved. If a field holds HTML you need back byte-for-byte, store and display it rather than putting it behind an editor.

Syntax-highlighted code editor (CodeMirror 6) for script/markup fields. Select the language with the language prop — not mode (mode is the view render mode).

- type: field
name: custom_js
properties:
widget: CodeEditor
language: javascript
PropertyDescription
languagejson, javascript, python, markdown, or html (defaults to plain)
minHeightEditor min height (e.g. "160px")

Canvas-based signature capture (no external dependency). The drawn signature is stored on the field as a PNG data URL string; read-only and display modes render it as an image. Back it with a Text/Char field.

- type: field
name: signature
properties:
widget: Signature
label: Customer Signature

Numeric input with optional formatting.

- type: field
name: price
properties:
widget: NumberInput
precision: 2
prefix: $
min: 0
PropertyDescription
precisionDecimal places
prefixStatic text before value (e.g., "$", "€")
suffixStatic text after value (e.g., "%", "kg")
suffix_fieldfalse only — drop the unit the model’s suffix_field prints, because this view shows that unit field itself
minMinimum value
maxMaximum value

:::tip Dynamic Prefix/Suffix For dynamic values from related fields (e.g., displaying UOM name), define prefix_field or suffix_field on the model field definition instead:

# Model definition
quantity = Float(precision=3, suffix_field="uom__name")

The model’s dynamic fields take priority over the view’s static values. See Prefix and Suffix Fields.

Where the view renders that unit itself — a UoM column next to the quantity column, a UoM picker next to the input — set suffix_field: false on the number so the unit isn’t printed twice (5.00 Units | Units). Only false is accepted; it turns the model’s unit off for that field in that view and nothing else. :::

The money widget — the default for Monetary fields and the right choice whenever a value is currency. It formats the amount with the record’s currency symbol, position and rounding, and is locale-aware. Bind the currency with currency_field (the model field holding the currency object); in dashboard/display widgets the alias currencyField is also accepted.

- type: field
name: amount
properties:
widget: Monetary
currency_field: currency
PropertyDescription
currency_fieldSibling field holding the currency (symbol/position/rounding). Prefer setting currency_field on the model field so it applies everywhere.
precisionOverride decimal places (otherwise from the currency’s rounding)

:::tip Model-side currency For form fields bound to a model column, set currency_field on the field definition itself rather than repeating it in every view. See Prefix and Suffix Fields. :::

Raw JSON editor — the default widget for JSON fields. It pretty-prints the value, validates on blur, and saves the parsed object (not the string). Display and kanban modes render the JSON read-only in a code block.

- type: field
name: procurement_trace
properties:
widget: JSONInput
readonly: true
visible: Q(procurement_trace__isnotnull=True)
PropertyDescription
minRowsMinimum textarea rows in form mode (default: 4)
maxRowsMaximum textarea rows before scrolling (default: 12)
placeholderPlaceholder text

For an object editor with typed key/value rows instead of raw text, see ObjectFieldsInput.

A single-line editor for safe-eval formula fields (e.g. salary components, financial report lines). It surfaces the expression vocabulary through a ƒx palette listing the available variables and functions, and validates client-side (no string literals, only known names) so mistakes surface before save.

The variable namespace comes from one of two sources:

# Server namespace: a no-arg classmethod returning {variables, functions}
- type: field
name: formula
properties:
widget: FormulaInput
label: Formula
namespaceModel: Payslip
namespaceMethod: formula_namespace
# Sibling-row namespace: variables are the live values of a peer grid column
- type: field
name: formula
properties:
widget: FormulaInput
label: Formula
placeholder: e.g. revenue - cogs
variablesFromField: code # peer column supplying variable names
variableLabelField: label # peer column used as each variable's description
PropertyDescription
namespaceModelModel whose formula_namespace() supplies variables/functions (default Payslip)
namespaceMethodClassmethod name returning {variables, functions} (default formula_namespace)
variablesFromFieldSource variables from a sibling grid column instead of the server
variableLabelFieldSibling column used as each variable’s description
placeholderPlaceholder formula

Upload and manage multiple file attachments on one field. Form mode shows the uploader; list/kanban show a count badge; display shows the file-name badges.

- type: field
name: attachments
properties:
widget: MultiFileUpload
label: Attachments
PropertyDescription
acceptAccepted MIME types / extensions (e.g. "image/*,.pdf")

For a single file, use FileInput/ImageInput.

Single-file upload with a drag-and-drop surface — the visual alternative to FileInput’s bordered “Select File” button. Drop a file onto the area, or click to browse. Same value contract as FileInput (back it with a File field), so the two are interchangeable; use Dropzone where the upload deserves a prominent target and FileInput for a compact control. Outside form mode it renders the same compact filename cell as FileInput.

- type: field
name: document
properties:
widget: Dropzone
label: PDF
placeholder: Drag a PDF here or click to upload
PropertyDescription
placeholderPrompt shown inside the drop area
acceptAccepted MIME types (e.g. "application/pdf")
maxSizeMax file size in bytes

Inline preview of a PDF held in a File field — renders the file’s served URL in an <iframe> (browsers render PDFs natively; no extra dependency). Read-only; pair it with Dropzone/FileInput for upload. By default in a form it sticks to the top of its column so the document stays in view while the surrounding content scrolls — a document-centric layout (preview beside the fields/tabs). When the viewer lives in its own scroll column — e.g. a right-docked propertiesPanel beside the canvas — set sticky: false, since the pane already scrolls independently.

- type: field
name: document
properties:
widget: PdfViewer
height: 75vh
nolabel: true
PropertyDescription
heightViewer height (e.g. "75vh", 640) — defaults to 75vh
stickyStick to the top while surrounding content scrolls — defaults to true in a form; set false when the viewer is in its own independently-scrolling pane

Date selector.

- type: field
name: due_date
properties:
widget: DatePickerInput

Date and time selector.

- type: field
name: scheduled_at
properties:
widget: DateTimePicker

Boolean toggle.

- type: field
name: active
properties:
widget: Switch
label: Active

Boolean checkbox.

- type: field
name: terms_accepted
properties:
widget: Checkbox
label: I accept the terms

Radio button group for Selection fields and ManyToOne fields.

Selection field example:

- type: field
name: priority
properties:
widget: Radio
orientation: horizontal

ManyToOne field example:

- type: field
name: category
properties:
widget: Radio
orientation: vertical
filter: Q(active__eq=true)
PropertyDescription
orientationhorizontal (default) or vertical
sizexs (default), sm, md, lg
filterQ-expression to filter M2O options
colorMantine color for radio buttons

For ManyToOne fields, all records from the related model are fetched and displayed as radio options.

Card-based radio selection with icon, label, and description. Supports both Selection fields and ManyToOne fields. Ideal for visually rich option selection.

Selection field with custom options:

- type: field
name: type
properties:
widget: RadioCards
columns: 3
choices:
- value: Consumable
label: Consumable
icon: Package
description: Used once, not tracked in inventory
- value: Stockable
label: Stockable
icon: Warehouse
description: Tracked in inventory with stock levels
- value: Service
label: Service
icon: Wrench
description: Non-physical service offering

ManyToOne field example:

- type: field
name: product_category
properties:
widget: RadioCards
icon: Folder
columns: 2
filter: Q(parent__isnull=true)
PropertyDescription
choicesArray of option objects with value, label, icon, description
columnsNumber of columns for grid layout
sizesm, md (default), lg
iconFallback icon for all cards (required for M2O, optional for Selection)
filterQ-expression to filter M2O options
colorMantine color for badges in display modes

Option object properties (for Selection fields):

PropertyRequiredDescription
valueYesThe value stored when selected
labelNoDisplay label (defaults to value)
iconNoLucide icon name (overrides fallback icon prop)
descriptionNoDescription text below label

Notes:

  • For Selection fields: options can be provided explicitly or derived from the field’s choices
  • For ManyToOne fields: all records are fetched from the related model. Use icon prop to set a fallback icon (defaults to IconCircle if not provided)
  • Both widgets support groups, visible, and readonly properties (handled at the form level)

Selection dropdown for Selection fields.

- type: field
name: status
properties:
widget: SelectCombo

Dropdown for ManyToOne fields.

- type: field
name: customer
properties:
widget: DataCombo
filter: Q(is_customer=True)
PropertyDescription
filterDomain filter for options
createAllow inline “Create” / “Create & Edit” of new records from the dropdown (default true). Set create: false to force selection from existing records only. Also gated by permission — the options never appear unless the user has create access on the related model.
editShow the open/edit icon that opens the selected record’s form (default true). Set edit: false to hide it (select-only). When shown, the form opens in edit mode only if the user has update access on that record — otherwise it opens read-only.
contextDefault values for “Create & Edit” (see Field-Level Context)
description_fieldField on the related model shown as a muted second line under the selected value (see below)

description_field — a second line under the selected value. Point it at a field on the related model (e.g. a Contact’s address) and the picker shows that value as a muted line beneath the chosen record — useful to disambiguate look-alike names. It renders for the selected value only (not in the search dropdown). The referenced field must be a Selection, Char, Text, or ManyToOne (a relation shows its display name) on the related model, and is fetched automatically — you don’t list it elsewhere in the view.

- type: field
name: contact
properties:
widget: DataCombo
description_field: formatted_address

Permission note: create and edit are view-design toggles, layered on top of the user’s access rights. create: true still hides the create options for a user without create permission on the related model, and the open icon opens read-only for a user without update permission. These checks reuse the same per-model access the rest of the UI uses; the backend independently enforces them on save. The same create/edit behavior applies to MultiCombo (no open icon) and AvatarCombo.

A relation picker’s scope has two possible homes, and most belong in the first:

  • The field, via filter= on the ManyToOne/OneToOne/ManyToMany — see Relationships → Scoping the picker. View composition stamps it onto every picker bound to that field, so each view inherits it and none has to remember. This is right whenever the constraint is a property of the field: a payroll journal is a Miscellaneous journal on every screen that shows one.
  • The view node, via the filter: property below — for the scope that genuinely differs per screen. One contact field offers customers on an invoice and vendors on a bill; only the view knows which.

When both are present, the view’s filter: replaces the field’s — it is not intersected with it, because the two-scopes case above would AND to an empty dropdown. To narrow further, write the whole condition. filter: false opts a screen out of the field’s scope entirely.

Filters can reference current form/row values using three patterns:

PatternDescriptionExample
[field]Bracketed field referenceQ(company__in=[company])
[field__nested]Bracketed nested traversalQ(currency__eq=[contact__company])
_parent.fieldParent form field (in line items)Q(company__in=[_parent.company])
_parent.field__nestedParent’s nested fieldQ(currency__eq=[_parent.contact__company])
field__nestedUnbracketed (after =)Q(currency__eq=contact__company)

:::note A reference with no value drops its condition Every reference form is substituted with the live value. When that value is absent, the condition has no subject — “accounts of this invoice’s company” before a company is picked — so that condition is dropped and the rest of the filter still applies. If it was the only condition, no filter is sent and the picker offers everything.

An undefined condition stops constraining rather than excluding everything, because the alternative renders as a dropdown with no options: indistinguishable, to the user, from “there is no data”, and explained by nothing. A picker narrowing options is an affordance, never a guarantee — where a pairing must actually hold, enforce it with a Model.constraint at the write, which can say why.

“Absent” means null or missing, not falsy. An unticked Boolean is a real answer, so Q(is_kit=kit_only) with kit_only false narrows to non-kits; only a field the record does not carry at all is treated as blank.

Only the condition holding the reference goes, so the rest survives:

filter: Q(active=True) & Q(company=_parent.company)
Parent companyRows offered
setactive, in that company
blankactive

The same holds under OR — a branch whose reference is empty drops out and the others still apply:

filter: Q(warehouse=_parent.warehouse) | Q(transfer_type=_parent.transfer_type)
WarehouseTransfer TypeRows offered
setsetin that warehouse or of that type
blanksetof that type
blankblankall (no filter)

A negated condition is dropped rather than inverted: ~Q(x=y) with y empty would otherwise return as the strictest condition in the filter. :::

:::warning Depth Limitation Right-side field references are limited to 2 levels of relation traversal due to backend serialization depth. The install/update process validates this and raises an error if exceeded.

DepthExampleValid
1contact__id
2contact__company__id
3contact__company__currency__id✗ Error

Workarounds for deep nesting:

  1. Add a related_field on the model to surface the value at a shallower level
  2. Let the backend handle the filter (don’t use field references) :::

Example - Filter by parent form’s company (in invoice line items):

- type: field
name: account
properties:
widget: DataCombo
filter: Q(type__nin=['Receivable','Payable']) & (Q(companies__in=[_parent.company]) | Q(companies__isnull=True))

Example - Unbracketed field reference:

- type: field
name: currency
properties:
widget: DataCombo
filter: Q(id__eq=contact__currency)

A filter can only reference form values, not the current user or anything the server must compute. When a picker’s options depend on who is logged in (e.g. “only employees I manage”), give the field a static ctx object and act on it in the related model’s name_search:

- type: field
name: employee
properties:
widget: DataCombo
ctx:
scope: org_reports # a signal you define

The field’s ctx is sent with the options request and exposed server-side via name_search_ctx. Override the related model’s name_search to read it and narrow the query — the scope is computed from env.user, so it can’t be tampered with client-side:

from fullfinity.engine.context import name_search_ctx
class Employee(Model):
async def name_search(cls, term=None, limit=None, filters=None, operator="icontains"):
if name_search_ctx.get().get("scope") == "org_reports":
domain = await cls.org_reports_domain() # Q built from env.user, or None
if domain is not None:
filters = (filters & domain) if filters is not None else domain
return await super().name_search(term=term, limit=limit, filters=filters, operator=operator)

Only fields that declare ctx send one, so other pickers of the same model are unaffected. Pair this with a matching check on save (a create/write guard using the same domain) so the dropdown and the enforcement agree.

Dropdown for ManyToOne fields with avatar display. Similar to DataCombo but shows avatars alongside names in the dropdown options and selected value. Ideal for user/contact selection fields.

- type: field
name: assigned_to
properties:
widget: AvatarCombo
avatarField: avatar
filter: Q(active=True)
PropertyTypeDefaultDescription
avatarFieldString"avatar"Field name on related model containing avatar attachment ID
avatarSizeString"sm"Avatar size (xs, sm, md, lg, xl)
avatarRadiusString"xl"Avatar border radius
filterString-Domain filter for options (same as DataCombo)
createBooleanfalseAllow creating new records
contextObject-Default values for “Create & Edit”

Mode Behavior:

ModeBehavior
FormFull AvatarCombo input with edit/readonly support
ListAvatar combo in edit mode, avatar + name display in view mode
FormListSame as List
KanbanAvatar + name display only (no input)
DisplayAvatar + name with tooltip

Example - User assignment with avatar:

- type: field
name: user_id
properties:
widget: AvatarCombo
avatarField: avatar
avatarSize: sm
filter: Q(active=True) & Q(groups__name__in=['Sales'])

Note: The avatarField should reference an attachment ID field (File type) on the related model. The widget fetches and caches avatar images automatically.

Multi-select for ManyToMany fields.

- type: field
name: tags
properties:
widget: MultiCombo
PropertyDescription
filterDomain filter for options
createAllow creating new records
contextDefault values for “Create & Edit” (see Field-Level Context)

Image upload field.

- type: field
name: photo
properties:
widget: ImageInput
PropertyDescription
width / heightFrame size in pixels
radiusCorner radius (xsxl or a number)
initials_fieldSibling field whose value seeds an auto-coloured initials avatar shown in place of the empty-image placeholder (see below)

initials_field — initials avatar for empty images. When set, an empty image renders an auto-coloured initials avatar derived from another field on the record instead of the generic placeholder icon. Point it at a Selection, Char, or Text field (the value is used verbatim) or a ManyToOne (the related record’s display name is used); any other field type is rejected at save time. The referenced field is fetched automatically — it doesn’t need to appear elsewhere in the view.

- type: field
name: image
properties:
widget: ImageInput
width: 140
height: 140
initials_field: name

Color picker.

- type: field
name: color
properties:
widget: ColorInput

Button group for mutually exclusive options. Best for a short Selection (2–4 choices) rendered as an inline toggle. Segments are derived from the field’s Selection choices automatically — no options needed — or supplied explicitly.

- type: field
name: view_mode
properties:
widget: SegmentedControl
fullWidth: true
PropertyDescription
choicesExplicit [{ "value": ..., "label": ... }]; omit to derive from the field’s choices
fullWidthExpand to fill container width
orientationhorizontal (default) or vertical
sizexs, sm, md, lg, xl
colorMantine color for active segment

Like Radio/RadioCards/SelectCombo, options are derived from the field’s choices unless provided explicitly. Displayed labels are localized; the stored value stays canonical.

Month and year selector.

- type: field
name: billing_month
properties:
widget: MonthPickerInput

Year selector.

- type: field
name: fiscal_year
properties:
widget: YearPickerInput

Numeric PIN entry with individual character boxes.

- type: field
name: verification_code
properties:
widget: PinInput
length: 6
PropertyDescription
lengthNumber of input boxes (default: 6)
typenumber (default) or alphanumeric
maskHide input with asterisks

Plain text display.

- type: field
name: name
properties:
widget: Text
fw: "600"
size: lg
c: dimmed
PropertyDescription
fwFont weight (400-900)
sizexs, sm, md, lg, xl
cColor (dimmed, or hex)
lineClampMax lines to show
htmlRender the value as raw HTML instead of plain text (e.g. a formatted address)

Rendering raw HTML: set html: true to inject the field value as HTML — useful for server-formatted blocks such as a multi-line address.

- type: field
name: contact_address
properties:
widget: Text
nolabel: true
html: true
c: dimmed
size: sm

App views have no separate Html widget — raw-HTML display is the html prop on Text (portal views do use widget: Html). The HTML must come from trusted server-side rendering.

Colored badge.

- type: field
name: status
properties:
widget: Badge
variant: dot
size: sm
colors:
draft: gray
active: green
archived: red
PropertyDescription
variantfilled, outline, dot
sizeBadge size
colorsColor mapping by value
labelsDisplay-text mapping by stored value — e.g. {"true": "Passed", "false": "Failed"} to relabel a boolean, or {"Draft": "Not sent"} to reword a choice. Unmapped values render as stored, except a Boolean, which renders Yes/No.

A Boolean never renders its stored true/false as text. With no labels it reads Yes/No — honest under a column header that already names the field, but say what the states mean whenever the field carries a verdict:

- type: field
name: is_passed
properties:
widget: Badge
label: Result
labels: {'true': Passed, 'false': Failed}
colors: {'true': green, 'false': red}

On a card with no column header, name the on state and hide the off one:

- type: field
name: is_billing
properties:
widget: Badge
labels: {'true': Billing}
visible: Q(is_billing=True)
colors: {'true': blue}

colors keys stay on the stored value ('true', 'false', "Draft"), so relabelling never breaks the colour mapping. A boolean status ribbon differs deliberately: there the dot colour carries on/off, so its label names the dimension and defaults to the humanized field name.

Renders a field’s value as a Mantine Alert banner. Because the body text comes from the field, a computed message field can drive case-specific wording, and the alert self-hides when the field is empty — no visible rule needed. Presentation (title, color, variant, icon) is fixed on the field’s properties.

- type: field
name: reservation_notice
properties:
widget: Alert
title: Insufficient stock
color: orange
variant: light
icon: AlertTriangle

Back it with a calculated field that returns the message (or "" to hide it):

reservation_notice = Text(calculate="compute_reservation_notice", store=False)
@Model.calculate("is_fully_reserved", "lines")
async def compute_reservation_notice(self):
for record in self:
record.reservation_notice = "" if record.is_fully_reserved else "No stock available to reserve…"
PropertyDescription
titleOptional static heading above the message
colorMantine color (e.g. orange, red, blue)
variantlight, filled, outline, transparent
iconLucide icon name (e.g. AlertTriangle)

For a fixed banner whose text never varies, use the static - type: alert element instead of a field-bound widget.

Display a many-to-many as coloured pills.

- type: field
name: tags
properties:
widget: Tags
size: sm
PropertyTypeDefaultDescription
sizeString"xs"Pill size
colorFieldString"color"Field on each row holding its colour

Colours come in two shapes, and both work. A palette index (an Integer field, as on CrmTag.color) picks from the built-in tag palette. A hex string ("#FA5252" — a Char edited with ColorInput) is used verbatim, with the text colour chosen by luminance so a label stays readable on it. Anything else falls back to the first palette colour.

The people on a record, as overlapping faces. For a relation to User — assignees, members, attendees — where a row of spelled-out names would compete with everything else on the card.

- type: field
name: assignees
properties:
widget: AvatarGroup
size: sm
max: 4
PropertyTypeDefaultDescription
sizeString"sm"Avatar size
maxNumber4Faces before the rest collapse into a +N circle

Each face resolves its own image and falls back to initials, exactly as Avatar does; the overflow circle names the remainder on hover, so the count can still be interrogated. Display-only — the record’s own form is where membership changes.

Modes: kanban, list, formlist, display.

Display-only avatar widget with automatic image fetching. Shows user initials when no image is available.

- type: field
name: user_id
properties:
widget: Avatar
avatarField: avatar
size: md
PropertyTypeDefaultDescription
avatarFieldString"avatar"Field name on value object containing attachment ID
sizeString"md"Avatar size (xs, sm, md, lg, xl)
radiusString"xl"Border radius
colorString"initials"Background color for initials

How it works:

The widget automatically fetches avatar images when:

  • Value is an object with an attachment ID field (specified by avatarField)
  • Value itself is an attachment ID (number)
  • Fallback fields: image, avatar on the value object

Example - Display user avatar in a list:

- type: field
name: assigned_to
properties:
widget: Avatar
avatarField: avatar
size: sm

Display image.

- type: field
name: image
properties:
widget: Image
h: 100
w: 100
fit: cover
PropertyDescription
hHeight in pixels
wWidth in pixels
fitcover, contain, fill
initials_fieldSibling field seeding an auto-coloured initials avatar when there’s no image (same rules as ImageInput) — applies to list, kanban, and display cells

Star rating display.

- type: field
name: rating
properties:
widget: Rating
count: 5

Display a Lucide icon.

- type: field
name: icon_name
properties:
widget: Icon
size: 24
color: blue
PropertyDescription
sizeIcon size in pixels
colorMantine color (e.g., blue, red.6)

Icon with colored background box. The field’s value is the icon name; the tile around it can be tinted per record by naming a sibling colour field with background_field.

- type: field
name: icon_name
properties:
widget: BoxedIcon
w: 80
size: 34
borderRadius: 12px
background_field: color # a sibling field holding this record's colour
PropertyDescription
w / hTile size (square; either one sets it)
sizeIcon size in pixels
colorIcon colour (default white)
backgroundStatic tile background
background_fieldField whose value tints the tile — beats background
borderRadiusTile corner radius

The colour field named by background_field must itself be on the view (add it with visible: false if it should not be displayed on its own).

Progress bar display.

- type: field
name: completion
properties:
widget: ProgressBar
color: blue
size: md
PropertyDescription
colorMantine color
sizexs, sm, md, lg, xl
stripedShow striped pattern
animatedAnimate the stripes

A link value rendered as what it is: a clickable anchor that opens in a new tab, with a one-click copy button beside it. Long URLs wrap rather than overflow, so it is safe in a narrow column or a settings row. Read-only — it displays a URL, it never edits one.

- type: field
name: payment_url
properties:
widget: Url
label: Payment Link
PropertyDescription
linkLabelFriendly text to show instead of the raw URL
copyfalse hides the copy button (default true)
justifycenter / flex-end — alignment of the link + copy within the field

Reach for this instead of a read-only TextInput plus a “Copy Link” button: copying is the widget’s own affordance, and a button that only copies has no way to do the copy from the backend anyway (an action result cannot reach the clipboard).

A compact, quiet detail indicator: a single icon (chosen in the view via icon) tinted by a status colour, that opens a popover with a labelled breakdown on click. Meant for a per-row signal that stays out of the way until you want the detail — stock availability on a quote line, fulfillment progress on a confirmed one — without spending two or three columns on the figures.

Bind it to a field that returns a summary object (or null to render nothing, so non-applicable rows stay blank). The popover has no fixed subject: it renders whatever rows the payload carries.

{
"status": "Partial",
"color": "orange",
"uom": "Units",
"rows": [
{ "label": "Available", "value": 3 },
{ "label": "On hand", "value": 3 },
{ "label": "Expected", "value": "2026-09-12", "type": "date" },
{ "divider": true, "label": "Not Invoiced" },
{ "label": "Warehouse", "value": "Main Warehouse" }
]
}
Payload keyDescription
statusHeadline text — also the icon’s tooltip, and the inline text next to the icon on a form
colorMantine colour name (or hex) for the icon tint and the headline
uomDefault unit appended to numeric row values; a row may override it with its own uom
rowsOrdered list of {label, value} rows. type: "date" renders an ISO date in the viewer’s locale; numbers get locale digit grouping. A {divider: true, label} entry draws a separator with an optional sub-heading, so one payload can carry two sections
- type: field
name: stock_summary
properties:
widget: SummaryIndicator
icon: PackageSearch
nolabel: true
PropertyDescription
iconLucide icon name for the indicator (defaults to Info)
nolabelOn a list/formlist column, blanks the header (see List Views) — keeps an icon-only column tight and unlabelled

Row labels and status are authored server-side as English text, which is what the translation layer keys on, so they translate like any other string (and fall back to the English they already are when uncatalogued). On a form the icon is paired with its label and status text inline; in list/formlist cells it renders as the bare icon.

----------|-------------| | icon | Lucide icon name for the indicator (defaults to PackageSearch) | | nolabel | On a list/formlist column, blanks the header (see List Views) — keeps an icon-only column tight and unlabelled |

The icon colour comes from the payload’s color; the popover lists Available / On hand / Reserved / Incoming / Expected / Warehouse (rows with no value are omitted). On a form the icon is paired with its label and status text inline; in list/formlist cells it renders as the bare icon.

Dashboard widgets display KPIs, metrics, and summary data in form views. They support locale-aware number formatting using the user’s language settings.

:::info Display Widget Properties Dashboard and display widgets (Card, CompactList, Charts) can specify currencyField directly in view properties because they display arbitrary data rather than mapping to model fields. For form fields bound to model definitions, use currency_field on the model instead. The prefix and suffix properties are always static strings. :::

Displays a KPI/metric card with value, label, icon, and optional click action. Perfect for dashboard overviews.

- type: field
name: total_revenue
properties:
widget: Card
label: Total Revenue
icon: DollarSign
color: green
format: currency
currencyField: currency
method: action_view_revenue_details
PropertyTypeDefaultDescription
labelString"Value"Display label above the value
iconString"ChartBar"Lucide icon name
colorString"blue"Mantine color for icon/hover
formatString-Format type: currency, number, integer, percent
currencyFieldString-Field path containing currency object (e.g., "currency", "company__currency")
prefixString-Static prefix text (e.g., "$", "€")
suffixString-Static suffix text (e.g., "%", "kg")
precisionNumber/String2Decimal places (static int) or field path (e.g., "uom__rounding")
methodString-Action method to call when clicked

A compact table/list for displaying summary data like top customers, recent items, etc.

Model field:

top_customers = JSON(description="Top Customers", default=[])

View usage:

- type: field
name: top_customers
properties:
widget: CompactList
currencyField: currency
columns:
- field: name
label: Customer
flex: 2
bold: true
- field: count
label: Orders
align: center
format: integer
- field: amount
label: Total
format: currency
color: blue
bold: true
method: action_view_customer

CompactList Properties:

PropertyTypeDefaultDescription
columnsArrayAuto-generatedColumn definitions (see below)
currencyFieldString-Field path containing currency object (e.g., "currency")
prefixString-Static prefix text (e.g., "$", "€")
suffixString-Static suffix text (e.g., "%", "kg")
showHeaderBooleantrueShow column headers
emptyTextString"No data available"Text when list is empty
methodString-Action method called on row click (receives item_id, item)

Column Properties:

PropertyTypeDefaultDescription
fieldStringRequiredKey in the data object
labelStringField nameColumn header label
formatString"text"text, currency, number, integer, percent, badge
precisionNumber/String2Decimal places (static int) or field path
alignStringAutoleft, center, right (numbers default to right)
flexNumber1Flex grow value
widthString-Fixed column width
colorString-Mantine color for text
boldBooleanfalseBold text
badgeColorString"gray"Badge color when format is badge

Displays related records from a JSON field as cards with customizable layout and row actions. Useful for showing credit notes, related documents, or any list of linked items with action buttons.

Model field:

outstanding_credits = JSON(description="Outstanding Credits", default=[])

View usage:

- type: field
name: outstanding_credits
properties:
widget: LinkedRecords
title: Available Credits
content:
- type: row
content:
- type: field
name: record_type
properties:
widget: Badge
color: blue
- type: field
name: number
properties:
fw: 600
- type: row
content:
- type: field
name: date
properties:
widget: Date
c: dimmed
- type: field
name: amount
properties:
widget: Float
currency: currency
rowActions:
- icon: ExternalLink
method: action_view
tooltip: View
- icon: Check
method: action_apply
tooltip: Apply
confirm: Apply this credit?

LinkedRecords Properties:

PropertyTypeDefaultDescription
titleString-Title displayed above the cards
contentArrayAuto-generatedLayout definition with rows and fields
rowActionsArray[]Action buttons for each card (see below)
emptyTextString"No records"Text when list is empty
hideWhenEmptyBooleantrueHide widget when no records
maxHeightNumber-Maximum height with scroll
visibleBoolean/String-Visibility (Q-expression supported)
readonlyBoolean/String-Hide actions when true
groupsArray-Group-based access control

Row Action Properties:

PropertyDescription
iconLucide icon name — required, a row action is icon-only
methodModel method to call with that record
tooltipHover label for the icon
visibleBoolean or Q-expression, evaluated per card
disabledBoolean or Q-expression — greyed instead of hidden (hiding vs disabling)
hintReason shown instead of tooltip while the action is disabled
confirmAsk before running the method
rowActions:
- icon: Check
method: action_apply
tooltip: Apply
disabled: Q(state='Applied')
hint: This credit has already been applied.

Field Widgets in Content:

WidgetDescription
TextDefault text display
BadgeColored badge (color property)
DateFormatted date
FloatFormatted number with currency/prefix/suffix support

Float Field Properties:

PropertyDescription
currencyField name containing currency object (symbol, position, rounding)
prefixStatic string or field path (e.g., "uom__name")
suffixStatic string or field path
precisionNumber or field path for decimal places

Row Action Properties:

PropertyTypeDescription
iconStringLucide icon name
methodStringAction method to call (receives item_id, item)
tooltipStringHover tooltip
colorStringIcon color
confirmString / ObjectAsk before the method runs — same grammar as a form button’s confirmation prompt
visibleStringSimple Q-expression for conditional visibility

Chart widgets display visual data representations. They work with JSON fields containing numeric arrays or structured data. All chart widgets support currency formatting and locale-aware number display.

A compact inline chart perfect for showing trends in list views, kanban cards, and forms. Expects a JSON field with an array of numbers.

Model field:

sales_trend = JSON(description="Sales Trend", default=[])

View usage:

- type: field
name: sales_trend
properties:
widget: Sparkline
w: 120
h: 24
color: blue
curveType: linear
PropertyTypeDefaultDescription
wNumber100Width in pixels
hNumber30Height in pixels
colorString"blue"Mantine color (e.g., "teal", "red.6")
curveTypeString"linear"Curve type: linear, bump, natural, monotone, step
strokeWidthNumber2Line stroke width
fillOpacityNumber0.2Fill opacity (0-1)
withGradientBooleantrueUse gradient fill
trendColorsObject-Dynamic colors based on trend (see below)

Trend Colors:

Use trendColors instead of color to change the chart color based on trend direction:

- type: field
name: performance_data
properties:
widget: Sparkline
trendColors:
positive: teal
negative: red
neutral: gray
  • positive: Color when first value < last value (upward trend)
  • negative: Color when first value > last value (downward trend)
  • neutral: Color when first value = last value

Bar chart for visualizing data with multiple series.

Model field:

monthly_data = JSON(description="Monthly Data", default=[])

Data format:

[
{"month": "Jan", "sales": 100, "costs": 60},
{"month": "Feb", "sales": 120, "costs": 70},
{"month": "Mar", "sales": 90, "costs": 55}
]

View usage:

- type: field
name: monthly_data
properties:
widget: BarChart
dataKey: month
h: 150
currencyField: currency
PropertyTypeDefaultDescription
dataKeyStringRequiredField name for X-axis labels
hNumber150Chart height in pixels
currencyFieldString-Field path containing currency object (e.g., "currency", "company__currency")
prefixString-Static prefix text (e.g., "$", "€")
suffixString-Static suffix text (e.g., "%", "kg")
precisionNumber/String2Decimal places (static int) or field path (e.g., "uom__rounding")

The chart automatically generates series from all numeric fields in the data (excluding the dataKey field).

Area chart with the same properties as BarChart.

- type: field
name: trend_data
properties:
widget: AreaChart
dataKey: date
h: 200
currencyField: currency
precision: 0

Values in chart tooltips are formatted using the user’s locale settings (decimal and thousands separators from Language profile).

Embedded list view for OneToMany fields.

- type: field
name: order_lines
properties:
widget: List
view: order_line_list_view
create: true
delete: true
ctx:
form_identifier: order_line_form_view
default_qty: 1
PropertyDescription
viewList view identifier to use
createAllow creating new records
deleteAllow deleting records
editabletrue for inline editing, "modal" for modal editing
limitMaximum rows to display initially (default: 100). Shows “Load more” button when exceeded
contextDefault values and view references (see Field-Level Context)

For large OneToMany lists, use the limit property to enable client-side pagination:

- type: field
name: order_lines
properties:
widget: List
editable: true
limit: 50

When rows exceed the limit:

  • Only the first limit rows are displayed initially
  • A “Load X more (Y remaining)” button appears on the right
  • Clicking the button loads the next batch of rows
  • Pagination resets when navigating to a different record

List columns (fields in the List view) support visible, readonly, required, and filter properties with Q-expression evaluation. These are evaluated per-row against the row data.

- type: field
name: discount_amount
properties:
widget: NumberInput
visible: Q(has_discount__eq=true)
readonly: Q(is_locked__eq=true)
required: Q(amount__gt=0)

Embedded lists also receive the child model’s model metadata. When the parent record is locked by controlled edits, the list structure remains locked, but child fields listed in the child model’s controlled-edit exclusions can still be edited inline.

Parent Data Access:

Use the _parent. prefix to access fields from the parent form (the form containing the List widget):

- type: field
name: account
properties:
widget: DataCombo
filter: Q(companies__in=[_parent.company]) | Q(companies__isnull=True)

Nested Field Access:

Use __ for traversing relationships (Django ORM style):

- type: field
name: price
properties:
readonly: Q(_parent.contact__company__locked__eq=true)
PropertyTypeDescription
visibleBoolean/StringPer-row visibility. false hides cell, Q-expression evaluated per-row
readonlyBoolean/StringPer-row readonly. true or Q-expression
requiredBoolean/StringPer-row required. true or Q-expression
filterStringQ-expression filter for DataCombo/MultiCombo. Supports _parent. prefix

Embedded kanban view.

- type: field
name: tasks
properties:
widget: Kanban
view: task_kanban_view
PropertyTypeDescription
labelstringField label
nolabelbooleanHide label
placeholderstringPlaceholder text
sizestringSize variant
visibleBoolean/StringShow/hide field. Can be false or Q-expression
readonlyBoolean/StringMake field read-only. Can be true or Q-expression
requiredBoolean/StringMake field required. Can be true or Q-expression
groupsArrayGroup identifiers for access control

Example with visibility/required:

- type: field
name: price
properties:
widget: NumberInput
readonly: true
required: Q(type__eq='product')

See Element Properties for details.

PropertyTypeDescription
fwstring/numberFont weight
cstringText color
mtstringMargin top
mbstringMargin bottom
styleobjectCustom CSS

Field-level context allows you to pass default values and configuration to related record creation. This is useful for:

  • Pre-populating fields when creating records from embedded lists
  • Setting defaults when using “Create & Edit” in DataCombo/MultiCombo
  • Specifying which form view to use for modal editing

Use the context property with default_<fieldname> keys:

- type: field
name: order_lines
properties:
widget: List
ctx:
default_qty: 1
default_uom: 5
form_identifier: order_line_form_view
Key PatternDescription
default_<field>Pre-populate field with value when creating new records
form_identifierForm view identifier for modal editing
Custom keysAvailable via self._ctx in backend methods

The full context object is passed to the backend and is accessible via self._ctx on model instances.

default_<field> values: literals and field references

Section titled “default_<field> values: literals and field references”

A default_<field> value is either a literal (default_qty: 1, default_type: Opportunity, default_is_customer: true) or a reference to a field on the record the widget is rendered on, which is resolved to that field’s value before the new record is created. References use the same grammar as filter: — a bare field name, __ to traverse a relation, _parent. to reach the parent form from a line:

- type: field
name: lot
properties:
widget: DataCombo
create: true
filter: Q(product=product)
ctx:
default_product: product # → the id of this record's `product`
default_category: product__category # → traverses the relation
default_company: _parent.company # → the parent form's company

A string that doesn’t name a field on the record stays a literal, so default_type: Opportunity is unambiguous. A reference to a field that is empty is dropped rather than sent as null — the created record keeps its own default instead of being blanked.

Every field type can be referenced, and each is carried in the shape a write expects:

Referenced fieldWhat the prefill carries
Char, Text, Selection, Integer, Float, Monetary, Boolean, Date, Datetimethe value itself
ManyToOne, OneToOnethe record’s id — resolved back to the record, so the field shows its name
ManyToMany, OneToManycommand notation, [["link", [ids]]] (link these records)

A literal follows the same rules: default_qty: 1, default_is_customer: true, and a relational literal is written in command notation (default_lines: [["create", {"qty": 1}]]) — a bare list of ids is silently discarded on save.

This is what makes inline creation valid: a combo filtered to Q(product=product) offers only that product’s records, so a record created from it must be born with the same product. Without the prefill, the create fails on a required field (or silently lands in the wrong scope).

List widget with defaults:

- type: field
name: invoice_lines
properties:
widget: List
editable: true
ctx:
default_quantity: 1
default_tax_included: true

DataCombo with Create & Edit defaults:

When users click “Create & Edit” in a DataCombo, the context values are passed to the new record form:

- type: field
name: contact
properties:
widget: DataCombo
create: true
ctx:
default_is_customer: true
default_company: 5

Context is passed to the backend when creating new records. The backend processes default_* in _default_get and makes the full context available via self._ctx:

class OrderLine(Model):
async def _default_get(cls, context=None):
"""Called during record creation with field-level context."""
defaults = await super()._default_get(context)
# default_* keys are automatically applied by the base method
# Access custom context values for additional logic
if context and context.get('custom_flag'):
defaults['some_field'] = compute_default()
return defaults
async def compute_some_field(self):
"""Access context in calculated fields or any method."""
# self._ctx contains the full context passed from frontend
if self._ctx.get('default_currency'):
# Use context value
pass