ModelForm — Field Types¶
examples/02_field_types.py · run it with uv run python examples/02_field_types.py

One form showing all field types supported by NiceView:
| Python type | Widget |
|---|---|
str |
ui.input or ui.textarea (via widget_type override) |
str with password=True |
ui.input (password mode with toggle) |
str with autocomplete=[...] |
ui.input (with autocomplete dropdown) |
int, float |
ui.number |
int/float with prefix/suffix |
ui.number (with unit decoration) |
bool |
ui.switch or ui.checkbox |
datetime.date |
HTML date input |
datetime.time |
HTML time input |
datetime.datetime |
HTML datetime-local input |
datetime.timedelta |
ui.input (ISO 8601 duration; tolerant input: 7d, 2h30m, p1w) |
Literal[...] |
ui.select, ui.radio, or ui.toggle (via widget_type override) |
Literal[...] with with_input=True |
ui.select (searchable) |
list[str] with options + multiple=True |
ui.select (multi-select) |
list[Literal[...]] |
ui.select (multi-select; options from the Literal, no options needed) |
list[Literal[...]] with props='use-chips' |
ui.select (multi-select, selections shown as removable chips) |
list[Literal[...]] with widget_type='checkbox_group' |
Row/column of ui.checkbox (alternative to the multi-select) |
str (color) |
ui.color_input (via widget_type override) |
list[str] |
ui.input_chips |
list[Annotated[str, Field(pattern=...)]] |
ui.input_chips (item constraints enforced by validation, not the widget) |
list[int], list[float], list[bool] |
ui.input (comma-separated) |
int with ge/le + widget_type='ui.slider' |
ui.slider |
int with le + widget_type='ui.rating' |
ui.rating |
list[BaseModel] |
Inline EditGridWrapper |
pydantic.SecretStr |
ui.input (password with reveal button) |
pydantic.Field(frozen=True) |
any widget, disabled |
| field without a default | any widget, label marked *, empty value rejected |
Texts come from the model, one source per destination: title becomes the label,
examples[0] the placeholder, description the field's description. Where that one is shown
is a rendering choice: description_as='tooltip' (the default) puts it on hover, 'hint' below
the widget, None nowhere. hint and tooltip set on a niceview.Field always win over it.
This example also demonstrates how to customize the widgets, layout and style via niceview.Field metadata, ui.grid() and ElementFilter.
Source
"""
# ModelForm — Field Types
One form showing all field types supported by NiceView:
| Python type | Widget |
|---|---|
| `str` | `ui.input` or `ui.textarea` (via `widget_type` override) |
| `str` with `password=True` | `ui.input` (password mode with toggle) |
| `str` with `autocomplete=[...]` | `ui.input` (with autocomplete dropdown) |
| `int`, `float` | `ui.number` |
| `int`/`float` with `prefix`/`suffix` | `ui.number` (with unit decoration) |
| `bool` | `ui.switch` or `ui.checkbox` |
| `datetime.date` | HTML date input |
| `datetime.time` | HTML time input |
| `datetime.datetime` | HTML datetime-local input |
| `datetime.timedelta` | `ui.input` (ISO 8601 duration; tolerant input: `7d`, `2h30m`, `p1w`) |
| `Literal[...]` | `ui.select`, `ui.radio`, or `ui.toggle` (via `widget_type` override) |
| `Literal[...]` with `with_input=True` | `ui.select` (searchable) |
| `list[str]` with `options` + `multiple=True` | `ui.select` (multi-select) |
| `list[Literal[...]]` | `ui.select` (multi-select; options from the `Literal`, no `options` needed) |
| `list[Literal[...]]` with `props='use-chips'` | `ui.select` (multi-select, selections shown as removable chips) |
| `list[Literal[...]]` with `widget_type='checkbox_group'` | Row/column of `ui.checkbox` (alternative to the multi-select) |
| `str` (color) | `ui.color_input` (via `widget_type` override) |
| `list[str]` | `ui.input_chips` |
| `list[Annotated[str, Field(pattern=...)]]` | `ui.input_chips` (item constraints enforced by validation, not the widget) |
| `list[int]`, `list[float]`, `list[bool]` | `ui.input` (comma-separated) |
| `int` with `ge`/`le` + `widget_type='ui.slider'` | `ui.slider` |
| `int` with `le` + `widget_type='ui.rating'` | `ui.rating` |
| `list[BaseModel]` | Inline `EditGridWrapper` |
| `pydantic.SecretStr` | `ui.input` (password with reveal button) |
| `pydantic.Field(frozen=True)` | any widget, disabled |
| field without a default | any widget, label marked `*`, empty value rejected |
Texts come from the model, one source per destination: `title` becomes the label,
`examples[0]` the placeholder, `description` the field's `description`. Where that one is shown
is a rendering choice: `description_as='tooltip'` (the default) puts it on hover, `'hint'` below
the widget, `None` nowhere. `hint` and `tooltip` set on a `niceview.Field` always win over it.
This example also demonstrates how to customize the widgets, layout and style via `niceview.Field` metadata, ui.grid() and `ElementFilter`.
"""
import datetime
from typing import Annotated, Literal
import pydantic
from nicegui import ElementFilter, ui
import niceview
from niceview import ModelForm
class Tag(pydantic.BaseModel):
label: str = pydantic.Field(default='', title='Label')
def __str__(self):
return self.label
class AllTypes(pydantic.BaseModel):
# required (no default): the label gets a ' *' marker and an empty value is rejected,
# with or without pydantic — see docs/components.md#validation
handle: Annotated[str, pydantic.Field(title='Handle (required)', description='Unique, lowercase', examples=['a-handle'])]
text: str = pydantic.Field(default='hello', title='String')
text_area: Annotated[str, niceview.Field(widget_type='ui.textarea', label='String in Textarea')] = 'hello\nworld'
password: Annotated[str, pydantic.Field(title='Password'), niceview.Field(password=True, password_toggle_button=True)] = 'hunter2'
city: Annotated[str, pydantic.Field(title='City (autocomplete)'), niceview.Field(autocomplete=['Berlin', 'Munich', 'Hamburg', 'Cologne', 'Frankfurt', 'Stuttgart'])] = 'Berlin'
number_int: int = pydantic.Field(default=42, ge=0, le=1000, title='Integer (0-1000)')
number_float: float = pydantic.Field(default=3.14, title='Float')
speed: Annotated[float, pydantic.Field(title='Speed'), niceview.Field(suffix=' km/h', precision=1)] = 120.0
flag_switch: bool = pydantic.Field(default=True, title='Bool')
flag_checkbox: Annotated[bool, pydantic.Field(title='Bool in Checkbox'), niceview.Field(widget_type='ui.checkbox')] = False
date: datetime.date = pydantic.Field(default_factory=datetime.date.today, title='Date')
time: datetime.time = pydantic.Field(default_factory=lambda: datetime.time(9, 0), title='Time')
dt: datetime.datetime = pydantic.Field(
default_factory=lambda: datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0),
title='Datetime',
)
duration: Annotated[datetime.timedelta, niceview.Field(
hint='ISO 8601 (P7D, PT1H30M) or shorthand (7d, 2h30m) — normalised on blur',
)] = pydantic.Field(
default_factory=lambda: datetime.timedelta(hours=1, minutes=30),
title='Timedelta',
)
choice: Literal['red', 'green', 'blue'] = 'green' # label and widget are auto-detected from the Literal type
choice_search: Annotated[Literal['apple', 'banana', 'cherry', 'date', 'elderberry'], niceview.Field(widget_type='ui.select', with_input=True, label='Fruit (searchable select)')] = 'apple'
choice_multi: Annotated[list[str], niceview.Field(widget_type='ui.select', options=['red', 'green', 'blue'], multiple=True, clearable=True)] = pydantic.Field(default_factory=lambda: ['red', 'blue'], title='Colors (multi-select)')
perms_multiselect: list[Literal['read', 'write', 'admin']] = pydantic.Field(default_factory=lambda: ['read'], title='Permissions (list[Literal], auto multi-select)') # type: ignore[arg-type]
perms_chips: Annotated[list[Literal['read', 'write', 'admin']], niceview.Field(props='use-chips', label='Permissions (multi-select with use-chips)')] = pydantic.Field(default_factory=lambda: ['read']) # type: ignore[arg-type]
perms_checkboxes: Annotated[list[Literal['read', 'write', 'admin']], niceview.Field(widget_type='checkbox_group', props='inline', label='Permissions (checkbox_group)')] = pydantic.Field(default_factory=lambda: ['read']) # type: ignore[arg-type]
choice_radio: Annotated[Literal['red', 'green', 'blue'], niceview.Field(widget_type='ui.radio', props='inline')] = 'green'
choice_toggle: Annotated[Literal['red', 'green', 'blue'], niceview.Field(widget_type='ui.toggle')] = 'green'
color: Annotated[str, niceview.Field(widget_type='ui.color_input', label='Color', color_preview=True)] = '#4a90e2'
volume: Annotated[int, pydantic.Field(ge=0, le=100, title='Volume'), niceview.Field(widget_type='ui.slider', step=1)] = 50
priority: Annotated[int, pydantic.Field(ge=1, le=5, title='Priority'), niceview.Field(widget_type='ui.rating')] = 3
chips: list[str] = pydantic.Field(default_factory=lambda: ['foo', 'bar'], title='Chips (list[str])')
chips_constrained: list[Annotated[str, pydantic.Field(pattern=r'^[a-z]+$', min_length=2, max_length=10)]] = pydantic.Field(
default_factory=lambda: ['ok', 'go'],
title='Chips (constrained items: lowercase, 2-10 chars)',
)
nums: list[int] = pydantic.Field(default_factory=lambda: [1, 2, 3], title='Numbers (list[int], comma-separated)')
secret: pydantic.SecretStr = pydantic.Field(default=pydantic.SecretStr('s3cret'), title='SecretStr (password input)')
created: str = pydantic.Field(default='2026-08-06', title='Frozen (disabled)', frozen=True)
tags: list[Tag] = pydantic.Field(
default_factory=lambda: [Tag(label='important')],
title='Tags (list of BaseModel with __str__ method)',
)
@ui.page('/')
def page():
# Global styling alternative (applies to every ui.input in the app):
# ui.input.default_props('outlined dense')
# The scoped alternative via ElementFilter is shown at the end of this function.
with ui.tabs().classes('w-full') as tabs:
tab_home = ui.tab('Documentation')
tab_all_types = ui.tab('All Types')
with ui.tab_panels(tabs, value=tab_home).classes('w-full'):
with ui.tab_panel(tab_home):
ui.markdown(__doc__ or '')
with ui.tab_panel(tab_all_types):
with ui.grid().classes('w-full gap-4 grid-cols-1 lg:grid-cols-2').mark('my-form'):
ModelForm.from_item(AllTypes(handle='alice')).render()
# Styling example: make all my-form elements outlined and dense
ElementFilter().within(marker='my-form').props('outlined dense')
ui.run(title='02 — Field Types')