Skip to content

Concepts — Field Metadata Across Four Vocabularies

The same field is described in up to four places, each with its own vocabulary:

Layer What it describes Who reads it
niceview.Field() (FieldInfo) how the field looks and behaves in the UI ModelForm, ModelGrid, render_field()
NiceGUI widget options the concrete Quasar element NiceGUI
pydantic.Field() + annotated-types constraints the data: type, defaults, validity Pydantic
JSON Schema the same data contract, serialized anything but niceview

The tables below map them onto each other. - means: no equivalent, or not supported.

Three things to keep in mind while reading:

  • niceview never reads JSON Schema. The JSON Schema column is a translation guide, not a feature: it is what you need when your code interprets a schema and builds FieldInfos from it — see render_field(). There is no from_json_schema(), deliberately (a schema must never become a class).
  • The pydantic column is the automatic direction. Fields resolves it when a ModelForm / ModelGrid is built; everything marked - there has to be set explicitly on niceview.Field(). Explicit values always win over resolved ones.
  • niceview.Field() is NiceGUI's vocabulary plus a named set of extensions. Every constructor argument of every supported widget is either a FieldInfo attribute of the same name, set by niceview itself, or deliberately left to props= — declared in WIDGET_OPTIONS and checked by tests/test_widget_option_coverage.py, so a NiceGUI upgrade cannot widen the gap unnoticed.

← Back to the overview · See also Field Types & Customization

Identity and text

niceview.Field() NiceGUI widget option pydantic / annotated-types JSON Schema Notes
label label= (text= on ui.checkbox / ui.switch) title title Falls back to the capitalized field name, plus the required marker. Widgets without a label parameter get a caption above them → D9
hint Quasar hint prop description description Help text below the widget. Widgets without a hint slot ignore it → D3
placeholder placeholder= examples[0] examples An example of the expected input, not a description → D2
tooltip .tooltip() - - Opt-in: it would only repeat the hint on hover → D2
- value= default / default_factory default The value comes from the item (ModelForm) or the value argument (render_field()), never from the metadata
- - examples examples
- - alias, validation_alias, serialization_alias property name Naming/serialization only

Requiredness, visibility, state

niceview.Field() NiceGUI widget option pydantic / annotated-types JSON Schema Notes
required validation= (layer 1a) is_required() (no default) required: [...] Appends ' *' to the label and rejects an empty value, with or without a model → D4
editable=False .disable(), AgGrid editable frozen=True readOnly Pydantic emits no readOnly for frozen → D5
hidden - - - niceview-only: skipped by render() and by the grid
validation validation= field/model validators - Layer 1b, runs before the model → D14
- - deprecated, exclude deprecated, writeOnly

Numbers and ranges

niceview.Field() NiceGUI widget option pydantic / annotated-types JSON Schema Notes
min ui.number(min=), ui.slider(min=) ge=, gt= minimum, exclusiveMinimum Exclusive bounds are treated as inclusive → D1. Always stored as floatD8
max ui.number(max=), ui.slider(max=), ui.rating(max=) le=, lt= maximum, exclusiveMaximum Same as min. For ui.rating it is the number of stars (default 5)
step ui.number(step=), ui.slider(step=) multiple_of= multipleOf Different meanings → D6
precision ui.number(precision=) - - Decimal places; JSON Schema has no equivalent
prefix / suffix ui.number(...), ui.input(...) - -
number_format ui.number(format=) - - A printf format like '%.2f'. Named apart from JSON Schema's format, which maps to widget_typeD7

Choices

niceview.Field() NiceGUI widget option pydantic / annotated-types JSON Schema Notes
options ui.select/radio/toggle(options) Literal[...] args, Enum members enum List, dict (value -> label), or a sync/async callable. Enum keys are the members → D13
literal_options as above Literal[...] args enum Resolved automatically, not user-settable; fallback when options is unset
multiple ui.select(multiple=) list[Literal[...]] type: array + items.enum None and [] are interchangeable
with_input ui.select(with_input=) - - Searchable select
clearable ui.select, ui.toggle, ui.input_chips (argument); every text input incl. ui.color_input (prop) - - Not derived from OptionalD11
new_value_mode ui.input_chips, ui.select - uniqueItems (loosely) 'add-unique' by default
key_generator ui.select(key_generator=) - -
item_type - list[T] argument, relationship target items, $ref Drives editgrid / modelselect and comma-separated ui.input lists

Type → widget

widget_type is what render_field() needs explicitly; ModelForm infers it from the annotation. In JSON Schema the same decision is a type + format pair.

widget_type Python type JSON Schema Notes
'ui.input' str, unknown types type: string
'ui.textarea' - (explicit) - No JSON Schema keyword — a UI decision
'ui.number' int, float type: integer / number Set field_type=int in the model-free path
'ui.switch' bool type: boolean 'ui.checkbox' is the explicit alternative
'ui.select' Literal[...], Enum, list[Literal[...]] enum (+ type: array) 'ui.radio' / 'ui.toggle' / 'checkbox_group' are explicit alternatives
'date' datetime.date type: string, format: date Native HTML input
'time' datetime.time type: string, format: time Native HTML input
'datetime' datetime.datetime type: string, format: date-time Native HTML input; local_tz controls the displayed zone
'timedelta' datetime.timedelta type: string, format: duration ISO 8601 duration in a ui.input
'ui.input_chips' list[str] type: array, items: {type: string}
'ui.input' (comma-separated) list[int], list[float], list[bool] type: array Needs item_type
'ui.color_input' - (explicit) format: color (non-standard)
'ui.slider', 'ui.rating' - (explicit) - Bounds from min/max
'checkbox_group' - (explicit) type: array + items.enum Composite of ui.checkbox, see Field Types
'editgrid' list[BaseModel] type: array, items: {$ref} ModelForm only — needs a model type
'modelselect' SQLModel relationship $ref ModelForm only — needs a repository
'ui.input' (password) pydantic.SecretStr format: password, writeOnly Set automatically → D10

Widget extras and presentation

Pure UI concerns: nothing in pydantic or JSON Schema corresponds to them.

niceview.Field() NiceGUI widget option pydantic / annotated-types JSON Schema
password, password_toggle_button ui.input(password=, password_toggle_button=) - -
autocomplete ui.input(autocomplete=) - -
color_preview ui.color_input(preview=) - -
props .props() - -
classes .classes() - -
style .style() - -
- on_change= - -
- ui.rating(icon=, color=, size=) — via props=D12 - -

on_change and the remaining NiceGUI constructor arguments are not exposed as field metadata: ModelForm owns the change handling (form.on_change(...)), and everything else is reachable on the rendered widget — form.w('name') or the return value of render_field().

Table / grid columns

Only relevant for ModelGrid / ModelGridInlineEdit; these map to AgGrid column definitions rather than to a widget. (ModelList / DrillDownWrapper read only hidden and their own title_field / subtitle_fields options.)

niceview.Field() AgGrid column property pydantic / annotated-types JSON Schema
table_label headerName title (via label) title
table_hidden column omitted - -
table_align, table_cell_style cellStyle - -
table_sortable sortable - -
table_sort sort - -
table_filterable filter (type inferred) - -
table_floating_filter floatingFilter - -
aggrid_type type - -
aggrid any column property (verbatim) - -

Constraints niceview does not render

These are real constraints — Pydantic enforces them and the error message shows up in the form as a normal field error — but no widget option is derived from them.

Constraint niceview.Field() NiceGUI widget option pydantic / annotated-types JSON Schema
String length - - min_length=, max_length= minLength, maxLength
String pattern - - pattern= pattern
List length - - min_length=, max_length= on a list minItems, maxItems
Unique items - - - uniqueItems
Alternatives - - Union[...] with >1 non-None type oneOf, anyOf
Nested object - - BaseModel field (not in a list) type: object, $ref
Free-form mapping - - dict[...] additionalProperties

The last three fall back to a plain ui.input showing the value's str() — a Union with more than one non-None member additionally logs a warning. A single-member Optional[T] is unwrapped and treated as T, and a single-value Literal['x'] (JSON Schema const) renders as a ui.select with exactly one option.

Deviations

Where the four vocabularies do not line up, this is what niceview actually does.

D1 — Exclusive bounds become inclusive. gt/lt (JSON Schema exclusiveMinimum / exclusiveMaximum) are copied into min/max unchanged, so gt=0 renders a widget that allows 0. Pydantic still rejects it — the value is refused on validation, not in the widget.

D2 — One text, one destination. title becomes the label, description becomes the description, examples[0] becomes the placeholder (an example of the expected input). Set any of them explicitly to override the inference — an empty string counts as explicit, so niceview.Field(placeholder='') renders no placeholder at all.

hint and tooltip are deliberately not in that list — nothing is inferred into them. Where description ends up instead (description_as, 'tooltip'/'hint'/None) is covered in Field Types → Texts.

D3 — Widgets without a hint slot ignore hint. Checkbox, switch, radio, toggle, slider, rating and checkbox_group have nowhere to put it. Use the label or a tooltip there — which is also why description_as defaults to the tooltip, the one slot every widget has.

D4 — required is display and a rule, but not enforcement. It appends ' *' to the label (required_marker=None switches that off) and rejects an empty value — None, '' or an empty collection, never False and never 0. That works without a model, so a JSON Schema required means the same thing as a Pydantic field without a default. What it is not is enforcement of the model's own constraints: those stay with Pydantic, and a disabled field is skipped entirely so it cannot block a form forever.

D5 — frozen disables the widget, readOnly is not what Pydantic emits. niceview.Field(editable=False) disables the widget and switches off inline editing in the grid; pydantic.Field(frozen=True) and model_config = ConfigDict(frozen=True) resolve to it, because Pydantic raises on every assignment to a frozen field, including on the working copy a form edits. An explicit editable=True still wins and logs a warning. Note that Pydantic does not emit readOnly in the generated schema for frozen fields (checked on 2.13), so the JSON Schema column is a translation, not a round trip.

D6 — step and multipleOf mean different things. multiple_of is a divisibility constraint; niceview reuses it as the widget's stepper increment. They agree for the common case (multiple_of=0.5), but a step set by hand constrains nothing, and Pydantic enforces multiple_of regardless of what the stepper offers.

D7 — format is a name collision, so ours is called number_format. FieldInfo.number_format is NiceGUI's ui.number display format ('%.2f'). JSON Schema's format (date, date-time, duration, password, ...) corresponds to widget_type instead — see the type table above.

D8 — min/max are floats. ge=0 on an int field yields min=0.0. Harmless for the widget; notable when comparing values. Use field_type=int / precision=0 to keep integers integral on the way back.

D9 — Widgets without a label parameter get a caption. ui.radio, ui.toggle, checkbox_group, ui.slider and ui.rating have no NiceGUI label argument, so niceview renders a text-caption label above them — a form in which some fields have no label is not a form. label='' opts out.

D10 — SecretStr is inferred. A pydantic.SecretStr field renders as a password input with a reveal button, and field_value() wraps the text back into a SecretStr.

D11 — clearable is not derived from Optional. Whether a field may be cleared in the UI is a UI decision; Optional[T] only says the model accepts None. Set clearable=True explicitly — and make the field accept None, because that is what clearing writes.

D12 — Not every NiceGUI option is a FieldInfo attribute. ui.rating's icon, color and size are plain Quasar props and stay reachable through props=. Which option lives where is declared in WIDGET_OPTIONS and enforced by tests/test_widget_option_coverage.py.

D13 — Enum options are keyed by the member. For an Enum field the options dict is {member: member.name}, so the widget's value is the enum member itself, not its .value and not its name — which is what a JSON Schema enum would list.

D14 — Three validation layers, in this order. Layer 1 is the widget's own (required, field_info.validation), layer 2 the type conversion — neither pydantic nor JSON Schema corresponds to either. Layer 3 is ModelForm's addition: the whole item against the model. Full detail, including form.item/form.draft and model-level errors, is in Components → Validation.