Changelog¶
All notable changes to this project are documented here. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.27.0] - 2026-08-28¶
Added¶
DrillDownWrapperacceptslist_actionsfor the application's own buttons in the list view's title row, right-aligned just left of Add — the list-view counterpart of the existing detail-view actions. Theiron_clickreceives a newDrillDownListActionEventArguments(e.wrapper,e.name,e.action— nokey/item, since the list view is about no single item);requires_validis rejected there, since there is no form to ask.DrillDownWrapperacceptsdetail_actionsas the new name for the existing detail-view actions, exposed viawrapper.list_action_buttons/wrapper.action_buttons.chrome_actionsis still accepted as an alias (detail_actionswins if both are given), so existing callers are unaffected.DrillDownWrapperacceptssearch=Truefor a free-text search box in the list view's title row, left oflist_actions/Add — filters rows across all visible fields, client-side, as the user types (EditGridWrapper'ssearch=counterpart for the drill-down's list view). Exposed aswrapper.search_input; hidden in the detail view.ModelGrid/ModelListhonourMeta.field_infos, likeModelFormalready did.Meta.default_profilepicks a profile when noprofile=/layout=kwarg is given, forModelForm/ModelGrid/ModelListalike (and the wrappers around them). Unlike an explicitprofile=, a name no longer inMeta.profilesdegrades to no profile rather than raising.ModelGrid/ModelListacceptlocal_tz, likeModelForm's:datetime/date/timevalues are converted the same way (Meta-sourceable too).
Fixed¶
ModelGridno longer crashes on atimedeltafield — a rawtimedeltahas no JSON representation (orjson, NiceGUI's encoder, does not support it); it now renders as the same canonical ISO-8601 duration stringModelFormalready shows.ModelListshows a choice field's label, not its raw stored value, and joins a list-valued field's items instead of showing a Pythonrepr()— both already worked correctly inModelGrid.- A
boolfield renders as a real checkbox inModelGrid/ModelGridInlineEdit(AG Grid's owncellDataType: 'boolean'— a native, clickable checkbox in the inline-editable grid, non-interactive in the plain one) instead of the texttrue/false, and as'✓'/'✗'inModelListinstead of Python'sTrue/False. - A
ui.numberfield withprecision/number_format/prefix/suffixset is now formatted the same way the widget itself would, inModelGrid(via avalueFormatter, so the column's own sort andagNumberColumnFilterstill compare the real number, not the formatted text) and inModelList. Previously both showed the plain unformatted number.
Docs¶
- Every kwargs
TypedDict(_DrillDownWrapperOptionInputsand friends),FieldInfo,ChromeStyle/FieldStyle/ChromeText, and every*EventArgumentsa handler receives now documents every field: mkdocstrings only shows a description for a field that carries an attribute docstring, and many —item_title_fieldamong them — had none, so they rendered blank in the generated docs.tests/test_option_docstring_coverage.pynow fails the build if a field loses its docstring again. Existing descriptions were also shortened throughout. - A concept explained in more than one doc page (the chrome place/role/shape cascade, its merge
semantics, the field-styling categories, the three validation layers,
description_as) now has exactly one canonical explanation, with the other pages linking to it instead of restating it.
[0.26.5] - 2026-08-27¶
Added¶
ModelGrid/EditGridWrapperaccepthtml_fields: a list of field names whosecell_renderersoutput is raw HTML (e.g. an icon<span>) instead of plain text — translated to ag-grid's index-basedhtml_columnsatrender()time, so it stays correct acrossinclude/excludefield ordering.
[0.26.4] - 2026-08-27¶
Added¶
EditGridWrapperacceptssearch=Truefor a free-text search box in the title row, filtering grid rows across all columns as the user types (ag-grid's client-side quick filter, no adapter reload). Off by default.
[0.26.3] - 2026-08-22¶
Fixed¶
DrillDownWrapperno longer replays its slide-in animation on every data change — a reactive adapter'son_change(e.g. an autosaving detail form's own field edit) refreshes the body to reflect the new data, but was unconditionally re-applying theniceview-slide-in-*CSS class every time, making unrelated field edits visually slide the whole detail view in from the side. The animation now only plays on actual navigation (open()/ the Back button).
[0.26.2] - 2026-08-22¶
Fixed¶
- CI mypy failures from the 0.26.0/0.26.1 releases —
meta_option()now types itskwargsparameter asAnyinstead ofdict, since a**kwargs: Unpack[SomeTypedDict]value is not assignable todict[Any, Any]under mypy;ModelGrid.with_repositories()'s type hint now matches itsdict[type[BaseModel] | str, CollectionAdapter]field-or-type key union like every other component; and a modelselect validation lambda no longer uses a default-argument capture that mypy could not infer the type of. No behavior change.
[0.26.1] - 2026-08-21¶
Added¶
-
Meta.include/Meta.excludenow apply toModelGridandModelList(as they already did toModelForm), so a model can declare its default field set once for every view; theinclude=/exclude=kwarg still overrides per call. -
DrillDownWrapperacceptstitle_field/subtitle_fieldsas aliases ofitem_title_field/item_subtitle_fields(the namesModelListuses), so switching between aModelListand aDrillDownWrapperneeds no rename. Theitem_*form wins if both are given.
[0.26.0] - 2026-08-21¶
Added¶
with_repositories()is uniform across every component —ModelForm,ModelGrid,ModelListand all three wrappers (EditFormWrapper,EditGridWrapper,DrillDownWrapper).ModelList(and aDrillDownWrapperlist) now resolves a modelselect key field shown as title/subtitle to the referenced item's label instead of its raw key; a stale key falls back to the key.DrillDownWrapper.with_repositories()forwards to both views (the list rows and the default detail form).
Changed¶
with_repositories()is now additive (merge, not replace). Repeated calls combine, a later entry overriding an earlier one for the same key; a wrapper merges its registrations into the component it embeds instead of clobbering that component's own. First-call behaviour is unchanged.
Fixed¶
- Embedded
editgridchild persistence now honours field-name-keyed repositories. It resolved the repository by model type only, missing a repository registered under the field name.
Added¶
-
Key-select
modelselectover aCollectionAdapter. A scalar field can now reference another collection by storing the key itself (building: str | Nonewithniceview.Field(widget_type='modelselect', item_type=Building)): the form and grid show the collection's label (str(item)), edit through a searchable select, and write the key back — no SQLModel or{name}_idcompanion required. The mode is chosen by the field's type (a model type → object-select as before, a scalar → key-select). A validator flags a stored key that no longer exists in the collection (new textChromeText.unknown_selection). -
with_repositoriesaccepts field-name keys. Repositories may be keyed by field name (preferred — two fields can reference the same model through different collections) in addition to the related model type (unchanged, backward compatible). A field-name-keyed adapter also letsitem_typebe inferred from the adapter. Applies toModelForm,ModelGridandEditGridWrapper. -
JsonDirectoryAdapter(Type, dir_path, key_field=)— aCollectionAdapterover a directory of one-parsed-model-per-file JSON documents, each file named by a model-owned key (key_field, default'id'). The parsed sibling ofDirectoryAdapterand the per-directory sibling ofJsonListAdapter; a natural backing store for key-select foreign keys. Now exported from the top level, implementsReloadableAdapter, and takesstrict=(like the other JSON adapters). A file that cannot yield a keyed record (malformed JSON, non-object root, missing/invalid key) is skipped from the listing rather than surfacing as an empty ghost record.
[0.24.0] - 2026-08-21¶
Added¶
-
Choice columns render their label in the grid. A field with
options(or a bareLiteral) now shows its label instead of the stored id inModelGridandModelGridInlineEdit, and — when inline-editable — edits through a select of those labels (aggridrefData+agSelectCellEditor). No extra config; it mirrors the form's select. The stored value is unchanged. -
ModelGrid.with_repositories({Model: adapter})resolves modelselect columns against a related model, so they show its labels (and, for a scalar FK, offer the select). May be called afterrender()— columns and rows refresh in place.EditGridWrapper.with_repositories()now forwards to the grid as well as the dialogs. A modelselect bound to a relationship object is label-display only in the grid (its write-back needs the form's FK sync — edit it via the dialog).
[0.23.1] - 2026-08-21¶
Changed¶
ModelList/DrillDownWrapperlist subtitles show the field values only, no longer prefixed with the field label ('alice@example.com · 123'instead of'Email: … · Phone: …').
[0.23.0] - 2026-08-21¶
Changed¶
-
DrillDownWrapper'slist_title=is renamed totitle=(hard cut, no alias), so all three chrome wrappers name their title the same way. Rename the keyword at every call site. -
Unified title semantics across the wrappers. For the collection wrappers
DrillDownWrapperandEditGridWrapper: omitted orNone→ auto title'{Type} List',''→ no title, any other string verbatim. This matches the field-label rule (unset → auto,''→ off). Two behaviour changes:EditGridWrapper(title='')now shows no title (was the auto title), andDrillDownWrapper(title=None)now shows the auto title (was none).EditFormWrapperis unchanged — a single-item form has no auto title, so omitted,Noneand''all mean no title.
Added¶
-
EditGridWrapper(on_add=...)— replaces the default Add action (createitem_type()and open the create dialog) with a handler of your own, sync or async, mirroringDrillDownWrapper.on_add. The other CRUD buttons keep their built-in behaviour. -
titleanddescriptionnow default from the model'sMetaon all three chrome wrappers (DrillDownWrapper,EditGridWrapper,EditFormWrapper), overridden by the kwarg. The title is split by cardinality (like Django'sverbose_name/verbose_name_plural): the form wrapper reads the singularMeta.title, the collection wrappers (grid and drilldown list) read the pluralMeta.title_plural, andMeta.descriptionis shared. Resolution is kwargs → Meta → (auto'{Type} List'for the collection title, else None); a model with onlyMeta.titleset never bleeds that singular into a grid heading.EditFormWrapperthereby gains aMeta-sourced heading it previously had no layer for. TheDrillDownWrapperdetail title stays the per-item title (item_title_field); only itsdescriptionisMeta-sourced.
[0.22.1] - 2026-08-20¶
Fixed¶
DrillDownWrapperdetail view no longer emits a duplicate non-field error label. The default detail renderer calledrender_nonfield_errors()afterrender(), which already places that label — leaving a stray empty label behind. Internal only; no API change.
[0.22.0] - 2026-08-18¶
Added¶
BoundFieldAdapter(parent_adapter, field_name)— anItemAdapterthat focuses a parentItemAdapteronto one named sub-field. It fills the one gapModelFormleaves open: editing a single embedded model (address: Address) with its own form, by binding it toBoundFieldAdapter(parent, 'address').save()is read-modify-write (re-read the parent, set only that field, save the parent), so sibling fields are never touched and severalBoundFieldAdapters over one parent stay independent — but it does not take part in the parent adapter's optimistic locking. See Data Adapters.
Changed¶
-
ModelListnow renders itsui.listat full width (w-full), so a list in a full-width card or column fills it instead of shrinking to its content. AffectsModelListand every defaultDrillDownWrapperlist. -
Adapters now inherit their protocols explicitly for consistency:
BoundItemandJsonAdapterfromItemAdapter[T],FilteredAdapterfromCollectionAdapter[T], ReloadableAdapter(matchingListAdapter,DirectoryAdapter,JsonListAdapter,SqlModelAdapter). Internal only — the structural conformance, and thus every usage, is unchanged. The unused@runtime_checkableonItemAdapterwas removed (nothing everisinstance-checked it).
[0.21.3] - 2026-08-18¶
Changed¶
-
The application-wide
FieldStylenow reaches the model-freerender_field(), not only aModelForm.input_props/control_props(by widget category) and thedefault_classesfallback apply to a standalone field too; a field's own props/classes still win, and a ModelForm's per-form layers (base_props, its owndefault_classes, the layout) remain form-only. The sharedfield_style_props()helper keeps the two paths from drifting. -
timedeltafields accept tolerant input. Besides the canonical ISO 8601 duration they accept it case-insensitively (p7d), the fixed-length calendar units pydantic understands (P1Y= 365 d,P1M= 30 d,P1W= 7 d), and a human shorthand of<number><unit>parts with unitsy w d h m s(7d,2h30m,1.5h,-2h); on blur the field rewrites the entry to its canonical form. A bare number is still rejected — pydantic would read7as 7 seconds. New helperniceview.widgets.parse_timedelta().
[0.21.2] - 2026-08-18¶
Fixed¶
niceview.Field(label='')in a model annotation now suppresses the label, overriding both the auto-generated name and apydantic.Field(title=…)— the same as passing it at the constructor. Previously an empty label in the annotation was indistinguishable from an unset one and fell back to the generated name.
[0.21.1] - 2026-08-18¶
Added¶
- Form layout containers and two field bits are chrome knobs now.
ChromeStylegainsform_row_classes,form_column_classes,form_card_classesandform_card_propsfor the containers a layout builds;FieldStylegainscaption_classes(the label above radio / toggle / checkbox_group / slider / rating) andcheckbox_group_classes(the checkbox container). Defaults are unchanged, so nothing renders differently — but a row's alignment no longer needs a':classes'first element on every layout. See Layout.
[0.21.0] - 2026-08-16¶
An application's own button reaches every title row, and the examples become documentation pages of their own.
Added¶
-
chrome_actions=on every wrapper.EditGridWrapperandDrillDownWrappernow take the sameFormActiontable in their title row thatEditFormWrapperalready did — rendered left of niceview's own buttons, exposed aswrapper.action_buttons. A title row without a form has no item to hand over, so each place sends the event arguments of what it is about:GridActionEventArguments(e.row_key,e.item— the selected row, bothNonewhen nothing is selected) andDrillDownActionEventArguments(e.key,e.item— the item on screen), beside the existingFormActionEventArguments(e.form). All three are exported fromniceview, so a handler can spell out what it takes. A drill-down's actions sit left of Delete and are hidden in the list view, where there is no single item to act on.requires_validfollows the detail form such a wrapper builds itself; where there is no form to ask — anEditGridWrapper, aDrillDownWrapperwith arender_detailof its own — it raises instead of leaving the button enabled without a word. -
The examples are part of the documentation site. One page per example, generated at build time from the example's own docstring — the text the app already prints on its first page — with a screenshot of it running, a link to the source and the source itself. Nothing to keep in sync by hand: a new example appears in the navigation on its own. The screenshots are committed and refreshed with
docs/screenshots/capture_examples.py, so building the docs needs no browser.
Changed¶
modelform.render_action_button()builds every action button now — a form's'@name'and every wrapper'schrome_actions— so the two cannot drift apart, andFormAction.on_clickis typed as the union of the three handlers.
[0.20.0] - 2026-08-16¶
A form gets buttons that are not fields, and the documentation gets a home of its own at clausgf.github.io/niceview.
Added¶
- Actions in a form. A button that is not a field — "Test connection" next to the host,
"Generate" next to the password. It has no value, no validation and no place in the model, so
it is a layout element rather than a pseudo-field:
Fieldsstays a mapping of model fields and every path that walks it needs no exception. Two parts, because a callback cannot live in a layout string:
ModelForm.from_item(cfg,
layout=[['# Server', ['host', 'port', '@test']]],
actions={'test': FormAction('Test', icon='bolt', on_click=test_connection)},
).render()
'@name' places the button, actions says what it does. FormAction carries label,
on_click, icon, tooltip, props, classes and requires_valid; the handler receives a
FormActionEventArguments with the form, whose item and draft are what an action almost
always needs. label and tooltip also take a callable, like every ChromeText slot.
-
requires_validdisables an action while the form has validation errors — the one bit of state worth taking over, since niceview knowshas_validation_errorsand the application would have to wire it up by hand. -
EditFormWrapper(chrome_actions=…)puts the sameFormActionin the title row, left of Refresh and Save, exposed aswrapper.action_buttons. Otherwise the half between the fields would exist and the more obvious one — an own button next to Save — would still have no way in. -
widgets.reserves_bottom_space()answers whether a field is taller than its box — Quasar keeps 20px free below one that can show a message (a validation, a hint), so that the layout does not jump when one appears. An action in a row uses it to align itself with the box of its neighbours (self-center mb-5) rather than with the middle of their total height; next to a switch or a slider, which reserve nothing, the plainself-centeris already right. -
ModelForm.render_action('test')places one action in a layout built by hand, asrender_field()does for a field. Rendered actions are reachable asform.w('@test')(the layout's spelling) and inform.action_buttons—widgetsstays keyed by field name. -
A documentation site at clausgf.github.io/niceview: the pages under
docs/as they are, plus an API reference generated from the docstrings (MkDocs Material + mkdocstrings, deployed by.github/workflows/docs.yml). The keyword options of every component are documented there too — they live in theTypedDicts the factories unpack, which is where their descriptions have always been.
Changed¶
style.chrome_button()acceptskind=Nonefor a button without one of niceview's roles. The roles are a closed vocabulary of what niceview itself means by a button; an application's action skips that layer and styles itself, while place and shape still apply so it fits its neighbours.
[0.19.0] - 2026-08-16¶
Chrome styling gets a second axis, field styling and texts get an application-wide default, and everything niceview says out loud becomes replaceable. See CONCEPT.md for how the three cascades fit together.
Added¶
- The
placeaxis. Every chrome button now sits in one of three places —'toolbar'(a wrapper's own action row),'form'(the same row for a wrapper embedded in a form),'dialog'(a dialog footer) — styled withtoolbar_button_props,form_button_props,dialog_button_props. Place and role are orthogonal (a Delete exists in both a toolbar and a dialog), so they are two layers rather than combined keys:
Wrappers take place= and pass it on; ModelForm renders an embedded editgrid with
place='form'.
- Per-place icon shape: toolbar_icon_button_props, form_icon_button_props,
dialog_icon_button_props. None inherits icon_button_props, '' suppresses the shape,
a value replaces it — replacing rather than adding, because Quasar's shapes are separate
boolean props and round rounded cancels nothing. "Round in a toolbar, square in a dialog"
is now one line.
- ok and cancel roles, both without a default.
- Dialog chrome: dialog_props, dialog_style, dialog_card_classes,
dialog_title_classes, dialog_button_row_classes, plus chrome_style= / chrome_text= on
confirm_dialog, input_dialog and submit_dialog. The four hard-coded copies of
':maximized=… width: 400px' are gone.
- Notification chrome: notify_position, notify_timeout, notify_close_button, and a
notify hook (message, kind) -> None for an application with its own messaging.
- ChromeText (niceview.text) with get_chrome_text() / set_chrome_text() and a
chrome_text= option on the widgets — every tooltip, dialog label, notification and field
marker niceview shows, in one replaceable table. Placeholders are named ({key}, {error})
and every slot also accepts a callable, resolved at render time, so a multilingual application
can resolve per client (a NiceGUI locale is per client, gettext's is per process).
FieldStyle with get_field_style() / set_field_style(): an application-wide default
for form fields, by widget category — input_props for the QInput/QSelect based widgets,
control_props for checkbox, switch, radio, toggle, checkbox_group, slider, rating, plus
default_classes. The cascade below it is unchanged (ModelForm(base_props=…), then the
field's own props).
- widgets.INPUT_BASED_WIDGETS / widgets.CONTROL_WIDGETS name those two categories.
- ChromeStyle.derived() / FieldStyle.derived() / ChromeText.derived() — the
application-wide value with single attributes changed, which is what a per-widget override
almost always wants.
- card_title_classes: '# Title' (with card) and '## Title' (without) can now be styled
apart. Both default to 'text-subtitle2', so nothing changes until it is set.
- chrome_style= on ModelForm, which styles its section titles and reaches the editgrid
wrappers it embeds. EditFormWrapper passes its own style and texts down to its form.
- examples/17_styling.py — styling presets (Quasar / compact / touch) and a German text set,
switchable at runtime. Presets are example code on purpose: the defaults stay empty.
Changed¶
- BREAKING:
ChromeStyle.button_propsis gone. "Every button of this application is dense" is a statement about a type, and NiceGUI owns it. Useui.button.default_props('dense flat')for all buttons, ortoolbar_button_props(and its siblings) for niceview's chrome. - BREAKING:
confirm_dialog(ok_color=…)→ok_role=…. The confirm button is picked from the role layer instead of being handed a color, sook_role='delete'follows whatever the application's delete buttons look like.ok_label/cancel_labelnow default toNone, meaning "take it fromChromeText". The explicitcolor=primaryon the confirm button is gone — aui.buttonis primary anyway. - BREAKING:
widgets.REQUIRED_MARKER/widgets.REQUIRED_MESSAGEare gone, replaced byChromeText.required_marker/.required_message.render_field()andcreate_widget()default toFROM_CHROME_TEXT, resolved at render time; passing an explicit string orNoneworks as before. - Notifications use Quasar's
type=instead ofcolor=, which brings the matching icon along. Anotifyhook ornotify_*options change how they are delivered. - Two texts that were duplicated in the code — the optimistic-lock message in
ModelGridandEditGridWrapper,'Required'inwidgetsandModelForm— are now one slot each. - The dialog confirm button reads
'OK'where it used to read'Ok'for an edit.
Notes¶
ModelGridtakes nochrome_style=: it renders no chrome of its own, so its messages follow the application-wide style.- A
listplace for buttons insideModelListrows was considered and dropped — the row's job is to navigate, and the detail view already carries the item's actions. Agridplace is not possible the same way at all: AG Grid cells live client-side, where Quasar props do not reach. Both are written up under "Possible extensions" in CONCEPT.md.
[0.18.1] - 2026-08-14¶
Fixed¶
clearable=Truehad no effect onui.color_input,ui.input,ui.number,ui.textareaand thedate/time/datetime/timedeltawidgets: NiceGUI has aclearableargument only onui.select,ui.toggleandui.input_chips, and the flag was silently dropped everywhere else. It is now set as a Quasar prop on theq-inputthose widgets are built from, so the clear button appears wherever a widget has somewhere to put it. Clearing writesNone, so the field has to accept it (str | None) — as before,clearableis never inferred fromOptional. Widgets without a clear affordance (checkbox, switch, radio, slider, rating, checkbox_group) still ignore the flag.
[0.18.0] - 2026-08-14¶
Added¶
- A form layout section without a card:
'## Title'as the first element of a group renders the same heading as'# Title', but stacks the fields in a plainui.columninstead of framing them in aui.card. The number of#picks the shape, everything else is unchanged — the heading usesChromeStyle.section_title_classeseither way, the group still always stacks, and':classes'still replaces the container's defaults.
profiles = {'detail': [
['# Name', ['first_name', 'last_name']], # card with a heading
['## Address', 'street', ['zip_code', 'city']], # heading only
]}
Changed¶
- Three or more
#in a layout title now raiseValueError('###' is not a heading level). They used to be stripped, so'## Address'and'### Address'were titled cards;'##'now means the section without the card, and there is no third shape to spell.
[0.17.0] - 2026-08-14¶
Changed¶
ChromeStyle.button_propsandicon_button_propsship empty. The chrome decides where a button goes and what it means; what it looks like is the application's call. Only the role layer keeps a default, and only where it carries meaning rather than taste (delete_button_props='color=negative').
This changes how every wrapper's buttons look: they are plain Quasar buttons now, where they
used to be dense flat — a look that predates ChromeStyle and was never a decision anyone
made. One line at startup brings it back, round included:
[0.16.2] - 2026-08-14¶
Added¶
- A chrome button's shape now follows the button itself: without a label it is round, with one
it stays square. The props of a chrome button are layered base → shape → role, with the two
new
ChromeStyleattributesicon_button_props(default'round') andlabelled_button_props(default'') in the middle; a role such asdelete_button_propsstill wins. Icon-only buttons outside a button group therefore look different than before —DrillDownWrapper's Back/Add/Delete, the lone Refresh of an autosavingEditFormWrapper, and any toolbar withbutton_group=False. Seticon_button_props=''for the old look. ChromeStyle.shape_in_group(defaultFalse): inside aui.button_groupthe shape layer is skipped, because a group joins straight edges and a circle has none — joined or round, not both.button_group=Falsegives round icon buttons everywhere;shape_in_group=Truelets a group-compatible shape through (Quasar'sroundedsurvives being joined,rounddoes not).
[0.16.1] - 2026-08-14¶
Changed¶
- Chrome buttons are only joined in a
ui.button_groupwhen more than one of them is on screen at the same time. Quasar styles a group as one joined control — squared-off inner edges, a shared border — so a group of one made a lone button look like part of something that was not there. Affected, all with a single visible button:EditFormWrapperwithautosave=True(Save is suppressed, Refresh remains), anEditGridWrapperwith all but one button hidden, andDrillDownWrapper, whose Add belongs to the list view and Delete to the detail view — configured together, never shown together. The newChromeStyleattributebutton_row_classes(default'flex items-center gap-1 w-fit flex-none') styles the container used instead of the group;button_group=Falsenow goes through it as well, rather than dropping the buttons loose into the title row without a gap.
[0.16.0] - 2026-08-13¶
Added¶
niceview.ChromeStyleplusset_chrome_style()/get_chrome_style(): the shared look of everything the wrappers draw around a form, grid or list — title row classes, button props per button kind, the button group, tooltips, the size of a section title inside a form, and the props and classes of aModelList's list, rows, labels and chevron. Set once for the application, or per widget with the newchrome_style=option onEditGridWrapper,EditFormWrapper,DrillDownWrapperandModelList:
set_chrome_style(button_props='dense outline', tooltips=False)
EditGridWrapper.from_list(User, users, chrome_style=get_chrome_style().replace(button_group=False))
Props are additive and classes replace, the same rule as the field cascade. Until now the
three title rows were built from literals copied between the wrappers, and the only way to
change their look was styling each exposed element in every application. A ModelList had
no such way at all: its rows are rebuilt by update_rows(), so styling .widget after
render() never reached them. list_chevron_icon=None renders rows without the drill-down
chevron, for a list that is not one. A style set on a DrillDownWrapper also styles the
ModelList it renders.
- DrillDownWrapper(description=...), exposed as wrapper.description after render() —
markdown below the title row, as EditGridWrapper/EditFormWrapper already had.
- DrillDownWrapper(back_button=...): label the Back button, '' for icon-only (the default),
None to hide it — the same ''/label/None semantics as the other buttons.
Changed¶
DrillDownWrapper's title row now looks like the other two wrappers: its buttons are joined in aui.button_groupat the right edge, the row no longer wraps (flex-nowrapinstead ofgap-2), and the buttons carry tooltips. The redundantroundandcolor=primaryprops are gone —color=primarywas NiceGUI's own default forui.button, and neither is set on the equivalent buttons ofEditGridWrapper.DrillDownWrapper(list_title=None)shows no title in the list view (the detail view keeps showing the current item's title). It previously rendered the string'None'.- Deleting a row in
EditGridWrappernow asks throughutil.confirm_dialogwith a red Delete button, the same confirmationDrillDownWrapperalready used, instead of a neutral Cancel/OKsubmit_dialog. - The create/edit dialog of
EditGridWrapperuses the same button row asniceview.util's dialogs: right-aligned, with the confirming button in the primary color. - The label of a read-only embedded grid (
editgridfield) renders astext-subtitle2, like every other section title inside a form — it wastext-h6, the size of the page title. util.submit_dialog's title no longer carries the classcenter, which is neither a Quasar nor a Tailwind class and never had an effect.