Skip to content

Styling and texts

Three cascades: the chrome around a component, the fields inside it, and every string niceview says out loud. Concepts explains how they fit together, Components how to use them.

Chrome

style

Chrome styling: the shared look of everything the wrappers draw around a form, grid or list — the title row, its buttons, the dialogs, the title of an embedded section, and the rows of a ModelList (which is chrome all the way down: the item's own fields are just text in it).

Two axes, because they are orthogonal — every chrome button sits in exactly one place and carries exactly one role:

{place}_button_props  →  shape  →  {role}_button_props

The place is where the button sits ('toolbar' at the top level, 'form' for a widget embedded in a form, 'dialog' in a dialog footer). The shape follows the button itself — icon-only or labelled — and a place may override it. The role is what the button means (add, delete, ok, …) and has the last word.

There is deliberately no base layer below the places: "every button of this application looks like that" is a type statement, and NiceGUI already owns it — ui.button.default_props('dense flat'). niceview only styles what NiceGUI cannot see.

Field styling is the second cascade (FieldStyle below). It is separate because it is keyed by widget category, not by place or role — but it follows the same idea: an application-wide default, a per-form layer, and the field itself.

Merge semantics, readable off the type:

str          additive layer — props merge per key, the later layer wins
str | None   replacing layer — None inherits, '' suppresses, a value replaces
*_classes    replaces wholesale (a CSS class has no key to merge on)

set_chrome_style(toolbar_button_props='dense flat')       # application-wide default
EditGridWrapper.from_list(..., chrome_style=ChromeStyle.derived(tooltips=False))

Place module-attribute

Place = Literal['toolbar', 'form', 'dialog']

Where a chrome button sits. 'toolbar' is a wrapper's own action row, 'form' the same row for a wrapper embedded in a form, 'dialog' a dialog footer. A ModelList row and a ModelGrid row are not places: the list row navigates (that is its whole job) and a grid cell lives client-side in AG Grid, where Quasar props do not reach.

NotifyKind module-attribute

NotifyKind = Literal[
    "positive", "negative", "warning", "info"
]

Quasar's notification types. type= rather than color=: it brings the matching icon.

NotifyPosition module-attribute

NotifyPosition = Literal[
    "top-left",
    "top-right",
    "bottom-left",
    "bottom-right",
    "top",
    "bottom",
    "left",
    "right",
    "center",
]

Where a notification appears — NiceGUI's own set of positions.

ChromeStyle dataclass

The look of the wrapper chrome. Instances are immutable — derive one with replace(), or from the application-wide default with ChromeStyle.derived().

Source code in niceview/style.py
@dataclass(frozen=True)
class ChromeStyle:
    """
    The look of the wrapper chrome. Instances are immutable — derive one with replace(), or
    from the application-wide default with ChromeStyle.derived().
    """
    title_row_classes: str = 'w-full items-center flex-nowrap'
    """Classes of the title row (ui.row) of every wrapper."""
    title_classes: str = 'text-h6 grow'
    """Classes of the title label. 'grow' pushes the buttons to the right edge."""
    card_title_classes: str = 'text-subtitle2'
    """Classes of a layout section's title that draws a card ('# …')."""
    section_title_classes: str = 'text-subtitle2'
    """Classes of a title *inside* a form without a card ('## …'), and of the label of an
    embedded grid. One step below title_classes — it is a section, not the page heading."""

    # --- form layout containers --------------------------------------------
    form_row_classes: str = 'w-full items-start gap-4'
    """Default classes of a layout row (a nested list). A ':classes' first element of the row
    replaces this — a class list has no key to merge on, so it is all-or-nothing."""
    form_column_classes: str = 'w-full gap-4'
    """Default classes of a layout column — the plain default container and the body of a '## …'
    section. Replaced by a ':classes' first element, like form_row_classes."""
    form_card_classes: str = 'w-full'
    """Default classes of a '# …' section card. Replaced by a ':classes' first element."""
    form_card_props: str = 'flat bordered'
    """Props of a '# …' section card."""

    # --- place layer -------------------------------------------------------
    toolbar_button_props: str = ''
    """Props of the buttons in a wrapper's own action row. Empty: the chrome brings no look of
    its own, the buttons are Quasar's. Set it once for the application, e.g. 'dense flat'."""
    form_button_props: str = ''
    """Props of the buttons of a wrapper embedded in a form — chrome of a section, not of a
    page. Falls back to nothing, not to toolbar_button_props: the places are separate."""
    dialog_button_props: str = ''
    """Props of the buttons in a dialog footer."""

    # --- shape layer -------------------------------------------------------
    icon_button_props: str = ''
    """Props of a button that shows only its icon (label ''). Its shape follows the button
    itself, not where it sits — except inside a button group, see shape_in_group, and where a
    place overrides it below. Empty; 'round' is the usual choice."""
    labelled_button_props: str = ''
    """Props of a button that carries a label."""
    toolbar_icon_button_props: str | None = None
    """Icon shape in the toolbar. None inherits icon_button_props, '' suppresses the shape
    entirely, a value replaces it. Replacing rather than adding, because Quasar's shapes are
    separate boolean props: 'round' and 'rounded' are two keys, so a later layer cannot cancel
    an earlier one by setting a different one."""
    form_icon_button_props: str | None = None
    """Icon shape for a wrapper embedded in a form. See toolbar_icon_button_props."""
    dialog_icon_button_props: str | None = None
    """Icon shape in a dialog footer. See toolbar_icon_button_props."""
    shape_in_group: bool = False
    """Whether the shape layer applies inside a ui.button_group as well. Off, because a group
    joins straight edges and a circle has none: 'round' children turn a group into circles with
    a border segment glued on. Turn it on if your shape props survive being joined (Quasar's
    'rounded' does, 'round' does not) — or set button_group=False to get round icon buttons
    everywhere. Joined or round, not both."""

    # --- role layer --------------------------------------------------------
    add_button_props: str = ''
    """Props of the Add button."""
    edit_button_props: str = ''
    """Props of the Edit button."""
    delete_button_props: str = 'color=negative'
    """The only default in the whole style — and only because it is meaning, not taste."""
    save_button_props: str = ''
    """Props of the Save button."""
    refresh_button_props: str = ''
    """Props of the Refresh button."""
    back_button_props: str = ''
    """Props of the Back button."""
    ok_button_props: str = ''
    """Props of a dialog's confirm button."""
    cancel_button_props: str = ''
    """Props of a dialog's cancel button."""

    button_group: bool = True
    """Whether chrome buttons that show at the same time are joined in a ui.button_group."""
    button_group_style: str = 'width: fit-content; flex: none'
    """Inline style of that group — it must not stretch or shrink with the title."""
    button_row_classes: str = 'flex items-center gap-1 w-fit flex-none'
    """Classes of the container used instead of the group: for a single button, or when
    button_group is off. Same job as button_group_style — hold the buttons at their own width
    at the right edge — plus the gap the group does not need."""

    tooltips: bool = True
    """Whether the chrome buttons carry their default tooltips. What those tooltips *say* is
    niceview.text.ChromeText."""

    # --- dialogs -----------------------------------------------------------
    dialog_props: str = ':maximized="$q.screen.lt.md" transition-show="slide-up" transition-hide="slide-down"'
    """Props of every dialog niceview opens. Maximized on a small screen, sliding in from the
    bottom — the phone behaviour; a wide screen gets a plain centered card."""
    dialog_style: str = 'width: 400px'
    """Inline style of the dialog."""
    dialog_card_classes: str = 'w-full'
    """Classes of the ui.card inside the dialog."""
    dialog_title_classes: str = 'text-h6'
    """Classes of the dialog's title label."""
    dialog_button_row_classes: str = 'w-full place-content-end'
    """Classes of the dialog's button row: cancel first, confirm last, right-aligned."""

    # --- notifications -----------------------------------------------------
    notify_position: NotifyPosition = 'bottom'
    """Where niceview's notifications appear. Quasar's positions ('top', 'bottom-right', …)."""
    notify_timeout: float = 5.0
    """How long they stay, in seconds. 0 keeps them until dismissed."""
    notify_close_button: bool = False
    """Whether they carry a close button."""
    notify: 'Callable[[str, NotifyKind], None] | None' = None
    """Hook for an application with a notification system of its own: called with the message
    and its kind instead of ui.notify. None uses ui.notify with the three options above."""

    # --- ModelList rows ----------------------------------------------------
    list_props: str = 'dense separator'
    """Props of a ModelList's ui.list."""
    list_item_classes: str = 'cursor-pointer'
    """Classes of one ui.item. The list rows are clickable, hence the pointer."""
    list_title_props: str = ''
    """Props of an item's title ui.item_label."""
    list_subtitle_props: str = 'caption'
    """Props of an item's subtitle ui.item_label."""
    list_chevron_icon: str | None = 'chevron_right'
    """Icon at the right edge of a row, hinting at the detail view behind it. None renders no
    icon and no section for it — for a list that is not a drill-down."""
    list_chevron_classes: str = 'text-grey'
    """Classes of that icon."""

    def replace(self, **overrides: Any) -> Self:
        """Return a copy with the given attributes changed."""
        return dataclasses.replace(self, **overrides)

    @classmethod
    def derived(cls, **overrides: Any) -> 'ChromeStyle':
        """
        The application-wide style with the given attributes changed — the usual way to build
        the `chrome_style=` of a single widget, which *replaces* the default rather than adding
        to it:

            EditGridWrapper.from_list(..., chrome_style=ChromeStyle.derived(button_group=False))

        Deriving is not avoidable by merging a partial style: a fresh dataclass carries
        defaults, not "unset", so ChromeStyle(button_group=False) could not be told apart from
        "every other value deliberately at its default".
        """
        return get_chrome_style().replace(**overrides)

title_row_classes class-attribute instance-attribute

title_row_classes: str = 'w-full items-center flex-nowrap'

Classes of the title row (ui.row) of every wrapper.

title_classes class-attribute instance-attribute

title_classes: str = 'text-h6 grow'

Classes of the title label. 'grow' pushes the buttons to the right edge.

card_title_classes class-attribute instance-attribute

card_title_classes: str = 'text-subtitle2'

Classes of a layout section's title that draws a card ('# …').

section_title_classes class-attribute instance-attribute

section_title_classes: str = 'text-subtitle2'

Classes of a title inside a form without a card ('## …'), and of the label of an embedded grid. One step below title_classes — it is a section, not the page heading.

form_row_classes class-attribute instance-attribute

form_row_classes: str = 'w-full items-start gap-4'

Default classes of a layout row (a nested list). A ':classes' first element of the row replaces this — a class list has no key to merge on, so it is all-or-nothing.

form_column_classes class-attribute instance-attribute

form_column_classes: str = 'w-full gap-4'

Default classes of a layout column — the plain default container and the body of a '## …' section. Replaced by a ':classes' first element, like form_row_classes.

form_card_classes class-attribute instance-attribute

form_card_classes: str = 'w-full'

Default classes of a '# …' section card. Replaced by a ':classes' first element.

form_card_props class-attribute instance-attribute

form_card_props: str = 'flat bordered'

Props of a '# …' section card.

toolbar_button_props class-attribute instance-attribute

toolbar_button_props: str = ''

Props of the buttons in a wrapper's own action row. Empty: the chrome brings no look of its own, the buttons are Quasar's. Set it once for the application, e.g. 'dense flat'.

form_button_props class-attribute instance-attribute

form_button_props: str = ''

Props of the buttons of a wrapper embedded in a form — chrome of a section, not of a page. Falls back to nothing, not to toolbar_button_props: the places are separate.

dialog_button_props class-attribute instance-attribute

dialog_button_props: str = ''

Props of the buttons in a dialog footer.

icon_button_props class-attribute instance-attribute

icon_button_props: str = ''

Props of a button that shows only its icon (label ''). Its shape follows the button itself, not where it sits — except inside a button group, see shape_in_group, and where a place overrides it below. Empty; 'round' is the usual choice.

labelled_button_props class-attribute instance-attribute

labelled_button_props: str = ''

Props of a button that carries a label.

toolbar_icon_button_props class-attribute instance-attribute

toolbar_icon_button_props: str | None = None

Icon shape in the toolbar. None inherits icon_button_props, '' suppresses the shape entirely, a value replaces it. Replacing rather than adding, because Quasar's shapes are separate boolean props: 'round' and 'rounded' are two keys, so a later layer cannot cancel an earlier one by setting a different one.

form_icon_button_props class-attribute instance-attribute

form_icon_button_props: str | None = None

Icon shape for a wrapper embedded in a form. See toolbar_icon_button_props.

dialog_icon_button_props class-attribute instance-attribute

dialog_icon_button_props: str | None = None

Icon shape in a dialog footer. See toolbar_icon_button_props.

shape_in_group class-attribute instance-attribute

shape_in_group: bool = False

Whether the shape layer applies inside a ui.button_group as well. Off, because a group joins straight edges and a circle has none: 'round' children turn a group into circles with a border segment glued on. Turn it on if your shape props survive being joined (Quasar's 'rounded' does, 'round' does not) — or set button_group=False to get round icon buttons everywhere. Joined or round, not both.

add_button_props class-attribute instance-attribute

add_button_props: str = ''

Props of the Add button.

edit_button_props class-attribute instance-attribute

edit_button_props: str = ''

Props of the Edit button.

delete_button_props class-attribute instance-attribute

delete_button_props: str = 'color=negative'

The only default in the whole style — and only because it is meaning, not taste.

save_button_props class-attribute instance-attribute

save_button_props: str = ''

Props of the Save button.

refresh_button_props class-attribute instance-attribute

refresh_button_props: str = ''

Props of the Refresh button.

back_button_props class-attribute instance-attribute

back_button_props: str = ''

Props of the Back button.

ok_button_props class-attribute instance-attribute

ok_button_props: str = ''

Props of a dialog's confirm button.

cancel_button_props class-attribute instance-attribute

cancel_button_props: str = ''

Props of a dialog's cancel button.

button_group class-attribute instance-attribute

button_group: bool = True

Whether chrome buttons that show at the same time are joined in a ui.button_group.

button_group_style class-attribute instance-attribute

button_group_style: str = 'width: fit-content; flex: none'

Inline style of that group — it must not stretch or shrink with the title.

button_row_classes class-attribute instance-attribute

button_row_classes: str = (
    "flex items-center gap-1 w-fit flex-none"
)

Classes of the container used instead of the group: for a single button, or when button_group is off. Same job as button_group_style — hold the buttons at their own width at the right edge — plus the gap the group does not need.

tooltips class-attribute instance-attribute

tooltips: bool = True

Whether the chrome buttons carry their default tooltips. What those tooltips say is niceview.text.ChromeText.

dialog_props class-attribute instance-attribute

dialog_props: str = ':maximized="$q.screen.lt.md" transition-show="slide-up" transition-hide="slide-down"'

Props of every dialog niceview opens. Maximized on a small screen, sliding in from the bottom — the phone behaviour; a wide screen gets a plain centered card.

dialog_style class-attribute instance-attribute

dialog_style: str = 'width: 400px'

Inline style of the dialog.

dialog_card_classes class-attribute instance-attribute

dialog_card_classes: str = 'w-full'

Classes of the ui.card inside the dialog.

dialog_title_classes class-attribute instance-attribute

dialog_title_classes: str = 'text-h6'

Classes of the dialog's title label.

dialog_button_row_classes class-attribute instance-attribute

dialog_button_row_classes: str = 'w-full place-content-end'

Classes of the dialog's button row: cancel first, confirm last, right-aligned.

notify_position class-attribute instance-attribute

notify_position: NotifyPosition = 'bottom'

Where niceview's notifications appear. Quasar's positions ('top', 'bottom-right', …).

notify_timeout class-attribute instance-attribute

notify_timeout: float = 5.0

How long they stay, in seconds. 0 keeps them until dismissed.

notify_close_button class-attribute instance-attribute

notify_close_button: bool = False

Whether they carry a close button.

notify class-attribute instance-attribute

notify: Callable[[str, NotifyKind], None] | None = None

Hook for an application with a notification system of its own: called with the message and its kind instead of ui.notify. None uses ui.notify with the three options above.

list_props class-attribute instance-attribute

list_props: str = 'dense separator'

Props of a ModelList's ui.list.

list_item_classes class-attribute instance-attribute

list_item_classes: str = 'cursor-pointer'

Classes of one ui.item. The list rows are clickable, hence the pointer.

list_title_props class-attribute instance-attribute

list_title_props: str = ''

Props of an item's title ui.item_label.

list_subtitle_props class-attribute instance-attribute

list_subtitle_props: str = 'caption'

Props of an item's subtitle ui.item_label.

list_chevron_icon class-attribute instance-attribute

list_chevron_icon: str | None = 'chevron_right'

Icon at the right edge of a row, hinting at the detail view behind it. None renders no icon and no section for it — for a list that is not a drill-down.

list_chevron_classes class-attribute instance-attribute

list_chevron_classes: str = 'text-grey'

Classes of that icon.

replace

replace(**overrides: Any) -> Self

Return a copy with the given attributes changed.

Source code in niceview/style.py
def replace(self, **overrides: Any) -> Self:
    """Return a copy with the given attributes changed."""
    return dataclasses.replace(self, **overrides)

derived classmethod

derived(**overrides: Any) -> ChromeStyle

The application-wide style with the given attributes changed — the usual way to build the chrome_style= of a single widget, which replaces the default rather than adding to it:

EditGridWrapper.from_list(..., chrome_style=ChromeStyle.derived(button_group=False))

Deriving is not avoidable by merging a partial style: a fresh dataclass carries defaults, not "unset", so ChromeStyle(button_group=False) could not be told apart from "every other value deliberately at its default".

Source code in niceview/style.py
@classmethod
def derived(cls, **overrides: Any) -> 'ChromeStyle':
    """
    The application-wide style with the given attributes changed — the usual way to build
    the `chrome_style=` of a single widget, which *replaces* the default rather than adding
    to it:

        EditGridWrapper.from_list(..., chrome_style=ChromeStyle.derived(button_group=False))

    Deriving is not avoidable by merging a partial style: a fresh dataclass carries
    defaults, not "unset", so ChromeStyle(button_group=False) could not be told apart from
    "every other value deliberately at its default".
    """
    return get_chrome_style().replace(**overrides)

get_chrome_style

get_chrome_style() -> ChromeStyle

The current application-wide chrome style.

Source code in niceview/style.py
def get_chrome_style() -> ChromeStyle:
    """The current application-wide chrome style."""
    return _chrome_style

set_chrome_style

set_chrome_style(
    style: ChromeStyle | None = None, **overrides: Any
) -> ChromeStyle

Set the application-wide chrome style and return it. Call with a complete ChromeStyle, or with keyword arguments to change single attributes of the current one:

set_chrome_style(toolbar_button_props='dense', tooltips=False)

Wrappers read the style when they render, so this takes effect for everything rendered afterwards — call it once at startup, before the first page is built.

Source code in niceview/style.py
def set_chrome_style(style: ChromeStyle | None = None, **overrides: Any) -> ChromeStyle:
    """
    Set the application-wide chrome style and return it. Call with a complete ChromeStyle, or
    with keyword arguments to change single attributes of the current one:

        set_chrome_style(toolbar_button_props='dense', tooltips=False)

    Wrappers read the style when they render, so this takes effect for everything rendered
    afterwards — call it once at startup, before the first page is built.
    """
    global _chrome_style
    _chrome_style = (style or _chrome_style).replace(**overrides)
    return _chrome_style

Fields

FieldStyle dataclass

Application-wide defaults for the fields niceview renders — in a ModelForm and in the model-free render_field() alike — by widget category.

Two categories, because that is what the practice asks for: an input and a select take the same props, a switch does not ('outlined' says nothing to a checkbox). A category is niceview's vocabulary — NiceGUI can only say "every ui.input", which would make the application enumerate ten widget types.

The cascade below this: ModelForm(base_props=…) for one form, FieldInfo(props=…) for one field. Props are additive per key, the narrower layer wins; classes replace wholesale.

Source code in niceview/style.py
@dataclass(frozen=True)
class FieldStyle:
    """
    Application-wide defaults for the fields niceview renders — in a ModelForm and in the
    model-free render_field() alike — by widget *category*.

    Two categories, because that is what the practice asks for: an input and a select take the
    same props, a switch does not ('outlined' says nothing to a checkbox). A category is
    niceview's vocabulary — NiceGUI can only say "every ui.input", which would make the
    application enumerate ten widget types.

    The cascade below this: ModelForm(base_props=…) for one form, FieldInfo(props=…) for one
    field. Props are additive per key, the narrower layer wins; classes replace wholesale.
    """
    input_props: str = ''
    """Props for the QInput/QSelect based widgets — see widgets.INPUT_BASED_WIDGETS."""
    control_props: str = ''
    """Props for checkbox, switch, radio, toggle, checkbox_group, slider, rating —
    see widgets.CONTROL_WIDGETS."""
    default_classes: str = ''
    """Classes for every field that brings none of its own (in a ModelForm, only when the form
    sets no default_classes of its own either)."""
    caption_classes: str = 'text-caption'
    """Classes of the caption label placed above widgets that have no label slot of their own —
    radio, toggle, checkbox_group, slider, rating."""
    checkbox_group_classes: str = 'gap-x-4 gap-y-1'
    """Classes of the checkbox_group's inner container that holds the checkboxes."""

    def replace(self, **overrides: Any) -> Self:
        """Return a copy with the given attributes changed."""
        return dataclasses.replace(self, **overrides)

    @classmethod
    def derived(cls, **overrides: Any) -> 'FieldStyle':
        """The application-wide field style with the given attributes changed."""
        return get_field_style().replace(**overrides)

input_props class-attribute instance-attribute

input_props: str = ''

Props for the QInput/QSelect based widgets — see widgets.INPUT_BASED_WIDGETS.

control_props class-attribute instance-attribute

control_props: str = ''

Props for checkbox, switch, radio, toggle, checkbox_group, slider, rating — see widgets.CONTROL_WIDGETS.

default_classes class-attribute instance-attribute

default_classes: str = ''

Classes for every field that brings none of its own (in a ModelForm, only when the form sets no default_classes of its own either).

caption_classes class-attribute instance-attribute

caption_classes: str = 'text-caption'

Classes of the caption label placed above widgets that have no label slot of their own — radio, toggle, checkbox_group, slider, rating.

checkbox_group_classes class-attribute instance-attribute

checkbox_group_classes: str = 'gap-x-4 gap-y-1'

Classes of the checkbox_group's inner container that holds the checkboxes.

replace

replace(**overrides: Any) -> Self

Return a copy with the given attributes changed.

Source code in niceview/style.py
def replace(self, **overrides: Any) -> Self:
    """Return a copy with the given attributes changed."""
    return dataclasses.replace(self, **overrides)

derived classmethod

derived(**overrides: Any) -> FieldStyle

The application-wide field style with the given attributes changed.

Source code in niceview/style.py
@classmethod
def derived(cls, **overrides: Any) -> 'FieldStyle':
    """The application-wide field style with the given attributes changed."""
    return get_field_style().replace(**overrides)

get_field_style

get_field_style() -> FieldStyle

The current application-wide field style.

Source code in niceview/style.py
def get_field_style() -> FieldStyle:
    """The current application-wide field style."""
    return _field_style

set_field_style

set_field_style(
    style: FieldStyle | None = None, **overrides: Any
) -> FieldStyle

Set the application-wide field style and return it:

set_field_style(input_props='outlined dense', default_classes='w-full')
Source code in niceview/style.py
def set_field_style(style: FieldStyle | None = None, **overrides: Any) -> FieldStyle:
    """
    Set the application-wide field style and return it:

        set_field_style(input_props='outlined dense', default_classes='w-full')
    """
    global _field_style
    _field_style = (style or _field_style).replace(**overrides)
    return _field_style

Texts

ChromeText dataclass

The texts of the chrome. Immutable — derive one with replace() or ChromeText.derived().

Source code in niceview/text.py
@dataclass(frozen=True)
class ChromeText:
    """
    The texts of the chrome. Immutable — derive one with replace() or ChromeText.derived().
    """

    # --- button tooltips ---------------------------------------------------
    add_tooltip: TextValue = 'Add a new item'
    """Tooltip of the Add button."""
    edit_tooltip: TextValue = 'Edit item'
    """Tooltip of the Edit button."""
    delete_tooltip: TextValue = 'Delete selected item'
    """The grid's Delete acts on the selected row."""
    delete_item_tooltip: TextValue = 'Delete this item'
    """The drill-down's Delete acts on the item that is open."""
    refresh_tooltip: TextValue = 'Refresh'
    """Tooltip of the Refresh button."""
    save_tooltip: TextValue = 'Save'
    """Tooltip of the Save button."""
    back_tooltip: TextValue = 'Back'
    """Tooltip of the Back button."""
    search_placeholder: TextValue = 'Search'
    """Placeholder of the search input (EditGridWrapper's search=True)."""

    # --- dialog labels -----------------------------------------------------
    ok_label: TextValue = 'OK'
    """Confirm button label in a dialog."""
    cancel_label: TextValue = 'Cancel'
    """Cancel button label in a dialog."""
    create_label: TextValue = 'Create'
    """Confirm button label of the create dialog."""
    delete_label: TextValue = 'Delete'
    """Confirm button label of a delete confirmation."""

    # --- dialogs -----------------------------------------------------------
    delete_selected_title: TextValue = 'Confirm Deletion'
    """Title of the grid's delete-confirmation dialog."""
    delete_selected_message: TextValue = 'Are you sure you want to delete the selected item *{key}*?'
    """Message of the grid's delete-confirmation dialog; {key} is the selected row's key."""
    delete_item_title: TextValue = 'Delete'
    """Title of the drill-down's delete-confirmation dialog."""
    delete_item_message: TextValue = 'Delete this item? This cannot be undone.'
    """Message of the drill-down's delete-confirmation dialog."""
    invalid_input: TextValue = 'Invalid input'
    """Shown when a value fails validation."""
    unknown_selection: TextValue = 'Unknown selection — no longer in the list'
    """Shown when the selected row is no longer in the collection."""

    # --- notifications -----------------------------------------------------
    item_created: TextValue = 'Item created'
    """Notification after a successful create."""
    item_updated: TextValue = 'Item updated'
    """Notification after a successful update."""
    item_deleted: TextValue = 'Item deleted'
    """Notification after a successful delete."""
    create_cancelled: TextValue = 'Item creation cancelled'
    """Notification when the create dialog is cancelled."""
    update_cancelled: TextValue = 'Item update cancelled'
    """Notification when an update dialog is cancelled."""
    delete_cancelled: TextValue = 'Item deletion cancelled'
    """Notification when a delete confirmation is cancelled."""
    create_error: TextValue = 'Error creating item: {error}'
    """Notification when create fails; {error} is the exception."""
    update_error: TextValue = 'Error updating item: {error}'
    """Notification when update fails; {error} is the exception."""
    delete_error: TextValue = 'Error deleting item {key}: {error}'
    """Notification when a grid's delete fails; {key} and {error} name the row and exception."""
    delete_failed: TextValue = 'Error deleting item: {error}'
    """Deleting the open item in a drill-down, where the key is not worth naming again."""
    save_error: TextValue = 'Error saving change: {error}'
    """Notification when an autosaving form's save fails; {error} is the exception."""
    select_row_first: TextValue = 'Please select a row first!'
    """Notification when an action needs a selected row but none is selected."""
    select_row_to_delete: TextValue = 'Please select a row for deletion!'
    """Notification when Delete is clicked with no row selected."""
    item_not_found: TextValue = 'Item with key {key} not found'
    """Notification when {key} no longer exists in the collection."""
    row_not_found: TextValue = 'Row {key} not found — try again'
    """Notification when a grid row's key no longer exists."""
    conflict: TextValue = 'This item was changed by another user. The list has been refreshed — please edit again.'
    """Notification when a save loses an optimistic-lock conflict to another writer."""
    invalid_value: TextValue = 'Invalid value {value!r}: {errors}'
    """Notification for an invalid value outside a form (e.g. an inline grid edit)."""
    validation_errors: TextValue = 'Cannot save form: validation errors present'
    """Notification when Save is blocked by validation errors."""
    form_refreshed: TextValue = 'Form refreshed'
    """Notification after Refresh reloads a form."""
    form_saved: TextValue = 'Form saved'
    """Notification after a non-autosaving form saves."""

    # --- body labels -------------------------------------------------------
    no_items: TextValue = 'No items yet.'
    """Shown in an empty list view."""
    detail_not_found: TextValue = 'Item {key!r} not found.'
    """Shown in the detail view when {key} no longer exists."""

    # --- fields ------------------------------------------------------------
    required_marker: TextValue = ' *'
    """Appended to the label of a required field. A form may still set None to render none."""
    required_message: TextValue = 'Required'
    """Validation message of an empty required field."""

    def replace(self, **overrides: Any) -> Self:
        """Return a copy with the given texts changed."""
        return dataclasses.replace(self, **overrides)

    @classmethod
    def derived(cls, **overrides: Any) -> 'ChromeText':
        """The application-wide texts with the given ones changed — for a single widget's
        `chrome_text=`, which replaces the default rather than adding to it."""
        return get_chrome_text().replace(**overrides)

add_tooltip class-attribute instance-attribute

add_tooltip: TextValue = 'Add a new item'

Tooltip of the Add button.

edit_tooltip class-attribute instance-attribute

edit_tooltip: TextValue = 'Edit item'

Tooltip of the Edit button.

delete_tooltip class-attribute instance-attribute

delete_tooltip: TextValue = 'Delete selected item'

The grid's Delete acts on the selected row.

delete_item_tooltip class-attribute instance-attribute

delete_item_tooltip: TextValue = 'Delete this item'

The drill-down's Delete acts on the item that is open.

refresh_tooltip class-attribute instance-attribute

refresh_tooltip: TextValue = 'Refresh'

Tooltip of the Refresh button.

save_tooltip class-attribute instance-attribute

save_tooltip: TextValue = 'Save'

Tooltip of the Save button.

back_tooltip class-attribute instance-attribute

back_tooltip: TextValue = 'Back'

Tooltip of the Back button.

search_placeholder class-attribute instance-attribute

search_placeholder: TextValue = 'Search'

Placeholder of the search input (EditGridWrapper's search=True).

ok_label class-attribute instance-attribute

ok_label: TextValue = 'OK'

Confirm button label in a dialog.

cancel_label class-attribute instance-attribute

cancel_label: TextValue = 'Cancel'

Cancel button label in a dialog.

create_label class-attribute instance-attribute

create_label: TextValue = 'Create'

Confirm button label of the create dialog.

delete_label class-attribute instance-attribute

delete_label: TextValue = 'Delete'

Confirm button label of a delete confirmation.

delete_selected_title class-attribute instance-attribute

delete_selected_title: TextValue = 'Confirm Deletion'

Title of the grid's delete-confirmation dialog.

delete_selected_message class-attribute instance-attribute

delete_selected_message: TextValue = "Are you sure you want to delete the selected item *{key}*?"

Message of the grid's delete-confirmation dialog; {key} is the selected row's key.

delete_item_title class-attribute instance-attribute

delete_item_title: TextValue = 'Delete'

Title of the drill-down's delete-confirmation dialog.

delete_item_message class-attribute instance-attribute

delete_item_message: TextValue = (
    "Delete this item? This cannot be undone."
)

Message of the drill-down's delete-confirmation dialog.

invalid_input class-attribute instance-attribute

invalid_input: TextValue = 'Invalid input'

Shown when a value fails validation.

unknown_selection class-attribute instance-attribute

unknown_selection: TextValue = (
    "Unknown selection — no longer in the list"
)

Shown when the selected row is no longer in the collection.

item_created class-attribute instance-attribute

item_created: TextValue = 'Item created'

Notification after a successful create.

item_updated class-attribute instance-attribute

item_updated: TextValue = 'Item updated'

Notification after a successful update.

item_deleted class-attribute instance-attribute

item_deleted: TextValue = 'Item deleted'

Notification after a successful delete.

create_cancelled class-attribute instance-attribute

create_cancelled: TextValue = 'Item creation cancelled'

Notification when the create dialog is cancelled.

update_cancelled class-attribute instance-attribute

update_cancelled: TextValue = 'Item update cancelled'

Notification when an update dialog is cancelled.

delete_cancelled class-attribute instance-attribute

delete_cancelled: TextValue = 'Item deletion cancelled'

Notification when a delete confirmation is cancelled.

create_error class-attribute instance-attribute

create_error: TextValue = 'Error creating item: {error}'

Notification when create fails; {error} is the exception.

update_error class-attribute instance-attribute

update_error: TextValue = 'Error updating item: {error}'

Notification when update fails; {error} is the exception.

delete_error class-attribute instance-attribute

delete_error: TextValue = (
    "Error deleting item {key}: {error}"
)

Notification when a grid's delete fails; {key} and {error} name the row and exception.

delete_failed class-attribute instance-attribute

delete_failed: TextValue = 'Error deleting item: {error}'

Deleting the open item in a drill-down, where the key is not worth naming again.

save_error class-attribute instance-attribute

save_error: TextValue = 'Error saving change: {error}'

Notification when an autosaving form's save fails; {error} is the exception.

select_row_first class-attribute instance-attribute

select_row_first: TextValue = 'Please select a row first!'

Notification when an action needs a selected row but none is selected.

select_row_to_delete class-attribute instance-attribute

select_row_to_delete: TextValue = (
    "Please select a row for deletion!"
)

Notification when Delete is clicked with no row selected.

item_not_found class-attribute instance-attribute

item_not_found: TextValue = 'Item with key {key} not found'

Notification when {key} no longer exists in the collection.

row_not_found class-attribute instance-attribute

row_not_found: TextValue = "Row {key} not found — try again"

Notification when a grid row's key no longer exists.

conflict class-attribute instance-attribute

conflict: TextValue = "This item was changed by another user. The list has been refreshed — please edit again."

Notification when a save loses an optimistic-lock conflict to another writer.

invalid_value class-attribute instance-attribute

invalid_value: TextValue = (
    "Invalid value {value!r}: {errors}"
)

Notification for an invalid value outside a form (e.g. an inline grid edit).

validation_errors class-attribute instance-attribute

validation_errors: TextValue = (
    "Cannot save form: validation errors present"
)

Notification when Save is blocked by validation errors.

form_refreshed class-attribute instance-attribute

form_refreshed: TextValue = 'Form refreshed'

Notification after Refresh reloads a form.

form_saved class-attribute instance-attribute

form_saved: TextValue = 'Form saved'

Notification after a non-autosaving form saves.

no_items class-attribute instance-attribute

no_items: TextValue = 'No items yet.'

Shown in an empty list view.

detail_not_found class-attribute instance-attribute

detail_not_found: TextValue = 'Item {key!r} not found.'

Shown in the detail view when {key} no longer exists.

required_marker class-attribute instance-attribute

required_marker: TextValue = ' *'

Appended to the label of a required field. A form may still set None to render none.

required_message class-attribute instance-attribute

required_message: TextValue = 'Required'

Validation message of an empty required field.

replace

replace(**overrides: Any) -> Self

Return a copy with the given texts changed.

Source code in niceview/text.py
def replace(self, **overrides: Any) -> Self:
    """Return a copy with the given texts changed."""
    return dataclasses.replace(self, **overrides)

derived classmethod

derived(**overrides: Any) -> ChromeText

The application-wide texts with the given ones changed — for a single widget's chrome_text=, which replaces the default rather than adding to it.

Source code in niceview/text.py
@classmethod
def derived(cls, **overrides: Any) -> 'ChromeText':
    """The application-wide texts with the given ones changed — for a single widget's
    `chrome_text=`, which replaces the default rather than adding to it."""
    return get_chrome_text().replace(**overrides)

TextValue module-attribute

TextValue = str | Callable[[], str]

A text: either the string itself, or a callable returning it. The callable is invoked every time the text is rendered, so it can resolve the current client's language.

text_of

text_of(value: TextValue, /, **params: Any) -> str

Resolve a text: call it if it is a callable, then fill in its named placeholders.

Without params the template is returned unchanged, so a text containing braces of its own survives (formatting it would raise).

Source code in niceview/text.py
def text_of(value: TextValue, /, **params: Any) -> str:
    """
    Resolve a text: call it if it is a callable, then fill in its named placeholders.

    Without params the template is returned unchanged, so a text containing braces of its own
    survives (formatting it would raise).
    """
    text = value() if callable(value) else value
    return text.format(**params) if params else text

get_chrome_text

get_chrome_text() -> ChromeText

The current application-wide chrome texts.

Source code in niceview/text.py
def get_chrome_text() -> ChromeText:
    """The current application-wide chrome texts."""
    return _chrome_text

set_chrome_text

set_chrome_text(
    text: ChromeText | None = None, **overrides: Any
) -> ChromeText

Set the application-wide chrome texts and return them. Call with a complete ChromeText, or with keyword arguments to change single texts of the current one:

set_chrome_text(add_tooltip='Neuen Eintrag anlegen', ok_label='Ok')

Widgets read the texts when they render, so this takes effect for everything rendered afterwards — call it once at startup.

Source code in niceview/text.py
def set_chrome_text(text: ChromeText | None = None, **overrides: Any) -> ChromeText:
    """
    Set the application-wide chrome texts and return them. Call with a complete ChromeText, or
    with keyword arguments to change single texts of the current one:

        set_chrome_text(add_tooltip='Neuen Eintrag anlegen', ok_label='Ok')

    Widgets read the texts when they render, so this takes effect for everything rendered
    afterwards — call it once at startup.
    """
    global _chrome_text
    _chrome_text = (text or _chrome_text).replace(**overrides)
    return _chrome_text

Building blocks

What the wrappers are made of. An application rarely calls these — they are here because a custom wrapper that wants to look like niceview's own has to.

Chrome styling: the shared look of everything the wrappers draw around a form, grid or list — the title row, its buttons, the dialogs, the title of an embedded section, and the rows of a ModelList (which is chrome all the way down: the item's own fields are just text in it).

Two axes, because they are orthogonal — every chrome button sits in exactly one place and carries exactly one role:

{place}_button_props  →  shape  →  {role}_button_props

The place is where the button sits ('toolbar' at the top level, 'form' for a widget embedded in a form, 'dialog' in a dialog footer). The shape follows the button itself — icon-only or labelled — and a place may override it. The role is what the button means (add, delete, ok, …) and has the last word.

There is deliberately no base layer below the places: "every button of this application looks like that" is a type statement, and NiceGUI already owns it — ui.button.default_props('dense flat'). niceview only styles what NiceGUI cannot see.

Field styling is the second cascade (FieldStyle below). It is separate because it is keyed by widget category, not by place or role — but it follows the same idea: an application-wide default, a per-form layer, and the field itself.

Merge semantics, readable off the type:

str          additive layer — props merge per key, the later layer wins
str | None   replacing layer — None inherits, '' suppresses, a value replaces
*_classes    replaces wholesale (a CSS class has no key to merge on)

set_chrome_style(toolbar_button_props='dense flat')       # application-wide default
EditGridWrapper.from_list(..., chrome_style=ChromeStyle.derived(tooltips=False))

chrome_row

chrome_row(style: ChromeStyle) -> row

The title row shared by all wrappers. Use as a context manager.

Source code in niceview/style.py
def chrome_row(style: ChromeStyle) -> ui.row:
    """The title row shared by all wrappers. Use as a context manager."""
    return ui.row().classes(style.title_row_classes)

chrome_title

chrome_title(text: str, style: ChromeStyle) -> label

The title label of a wrapper.

Source code in niceview/style.py
def chrome_title(text: str, style: ChromeStyle) -> ui.label:
    """The title label of a wrapper."""
    return ui.label(text).classes(style.title_classes)

chrome_buttons

chrome_buttons(
    style: ChromeStyle, count: int
) -> Iterator[element]

The container for the chrome buttons.

count is how many of them are visible at the same time — not how many the wrapper builds. Quasar styles a button group as one joined control (squared-off inner edges, a shared border), which only says something with a second button to join: a group of one is a button wearing a group's clothes. So one button, or button_group=False, goes into a plain flex container instead.

Source code in niceview/style.py
@contextlib.contextmanager
def chrome_buttons(style: ChromeStyle, count: int) -> Iterator[ui.element]:
    """
    The container for the chrome buttons.

    `count` is how many of them are visible *at the same time* — not how many the wrapper
    builds. Quasar styles a button group as one joined control (squared-off inner edges, a
    shared border), which only says something with a second button to join: a group of one
    is a button wearing a group's clothes. So one button, or button_group=False, goes into a
    plain flex container instead.
    """
    grouped = style.button_group and count > 1
    token = _in_button_group.set(grouped)
    try:
        if grouped:
            with ui.button_group().style(style.button_group_style) as container:
                yield container
        else:
            with ui.element('div').classes(style.button_row_classes) as container:
                yield container
    finally:
        _in_button_group.reset(token)

chrome_button

chrome_button(
    kind: str | None,
    label: str,
    icon: str | None,
    tooltip: str,
    style: ChromeStyle,
    on_click: Callable[..., Any] | None = None,
    place: Place = "toolbar",
) -> button

One chrome button, built from three layers of props: the props of its place, the shape the label asks for (icon-only or labelled), and the props of its kind ('add', 'delete', …). label is the caller's '' (icon only) or its own text.

The shape depends on the button rather than on where it sits, with two exceptions: a place may replace the icon shape (a dialog wants squared-off buttons where a lone toolbar button is round), and inside a button group there is nothing to round, so the layer is skipped there unless the style says otherwise.

kind is None for an application's own action (niceview.FormAction): the roles are a closed vocabulary of what niceview itself means by a button, so an action skips that layer and brings its own props instead. Place and shape still apply — it sits among the others.

Source code in niceview/style.py
def chrome_button(kind: str | None, label: str, icon: str | None, tooltip: str, style: ChromeStyle,
                  on_click: Callable[..., Any] | None = None, place: Place = 'toolbar') -> ui.button:
    """
    One chrome button, built from three layers of props: the props of its `place`, the shape
    the label asks for (icon-only or labelled), and the props of its `kind` ('add', 'delete',
    …). `label` is the caller's '' (icon only) or its own text.

    The shape depends on the button rather than on where it sits, with two exceptions: a place
    may replace the icon shape (a dialog wants squared-off buttons where a lone toolbar button
    is round), and inside a button group there is nothing to round, so the layer is skipped
    there unless the style says otherwise.

    `kind` is None for an application's own action (niceview.FormAction): the roles are a closed
    vocabulary of what niceview itself means by a button, so an action skips that layer and
    brings its own props instead. Place and shape still apply — it sits among the others.
    """
    if label:
        shape = style.labelled_button_props
    else:
        override: str | None = getattr(style, f'{place}_icon_button_props')
        shape = style.icon_button_props if override is None else override
    if _in_button_group.get() and not style.shape_in_group:
        shape = ''
    role = getattr(style, f'{kind}_button_props') if kind else ''
    layers = (getattr(style, f'{place}_button_props'), shape, role)
    button = ui.button(label, icon=icon).props(' '.join(p for p in layers if p))
    if on_click is not None:
        button.on_click(on_click)
    if style.tooltips and tooltip:
        with button:
            ui.tooltip(tooltip).style('width: fit-content')
    return button

chrome_dialog

chrome_dialog(style: ChromeStyle) -> Iterator[dialog]

The shell of every dialog niceview opens: the ui.dialog and the ui.card inside it, entered as the current context. The caller fills the card and awaits the yielded dialog.

Source code in niceview/style.py
@contextlib.contextmanager
def chrome_dialog(style: ChromeStyle) -> Iterator[ui.dialog]:
    """
    The shell of every dialog niceview opens: the ui.dialog and the ui.card inside it, entered
    as the current context. The caller fills the card and awaits the yielded dialog.
    """
    dialog = ui.dialog().props(style.dialog_props).style(style.dialog_style)
    with dialog, ui.card().classes(style.dialog_card_classes):
        yield dialog

chrome_dialog_title

chrome_dialog_title(text: str, style: ChromeStyle) -> label

The title label of a dialog.

Source code in niceview/style.py
def chrome_dialog_title(text: str, style: ChromeStyle) -> ui.label:
    """The title label of a dialog."""
    return ui.label(text).classes(style.dialog_title_classes)

chrome_dialog_buttons

chrome_dialog_buttons(style: ChromeStyle) -> row

The button row of a dialog. Use as a context manager: cancel first, confirm last.

Source code in niceview/style.py
def chrome_dialog_buttons(style: ChromeStyle) -> ui.row:
    """The button row of a dialog. Use as a context manager: cancel first, confirm last."""
    return ui.row().classes(style.dialog_button_row_classes)

chrome_notify

chrome_notify(
    message: str, kind: NotifyKind, style: ChromeStyle
) -> None

Show one of niceview's notifications. Routed to the style's notify hook if the application brought one, otherwise to ui.notify with the style's options.

kind is Quasar's semantic type, not a color — niceview never spells a color of its own.

Source code in niceview/style.py
def chrome_notify(message: str, kind: NotifyKind, style: ChromeStyle) -> None:
    """
    Show one of niceview's notifications. Routed to the style's `notify` hook if the
    application brought one, otherwise to ui.notify with the style's options.

    `kind` is Quasar's semantic type, not a color — niceview never spells a color of its own.
    """
    if style.notify is not None:
        style.notify(message, kind)
        return
    ui.notify(message, type=kind, position=style.notify_position,
              timeout=style.notify_timeout, close_button=style.notify_close_button)

Dialogs

field_stores_model

field_stores_model(field_info: Any) -> bool

True when a modelselect field's declared type is a model itself (a relationship/object, e.g. author: Author), False for a scalar key (e.g. author: str | None). This selects the modelselect mode: object-select (store/return the related object, sync a {name}_id companion) vs. key-select (store the repository key directly in the field).

Source code in niceview/util.py
def field_stores_model(field_info: Any) -> bool:
    """True when a modelselect field's declared type is a model itself (a relationship/object,
    e.g. ``author: Author``), False for a scalar key (e.g. ``author: str | None``). This selects
    the modelselect mode: object-select (store/return the related object, sync a ``{name}_id``
    companion) vs. key-select (store the repository key directly in the field)."""
    field_type = getattr(field_info, 'field_type', None)
    for candidate in (field_type, *get_args(field_type)):
        if isinstance(candidate, type) and issubclass(candidate, pydantic.BaseModel):
            return True
    return False

resolve_repository

resolve_repository(
    repositories: dict, field_name: str, item_type: Any
) -> Any

Look up the repository for a modelselect field.

Repositories may be keyed by field name (preferred: two fields can reference the same model through different collections) or, for backward compatibility, by the related model type (the SQLModel-relationship style). Field name wins. Returns None when neither matches.

Source code in niceview/util.py
def resolve_repository(repositories: dict, field_name: str, item_type: Any) -> Any:
    """Look up the repository for a modelselect field.

    Repositories may be keyed by **field name** (preferred: two fields can reference the same
    model through different collections) or, for backward compatibility, by the related **model
    type** (the SQLModel-relationship style). Field name wins. Returns None when neither matches.
    """
    if field_name in repositories:
        return repositories[field_name]
    if item_type is not None and item_type in repositories:
        return repositories[item_type]
    return None

meta_option

meta_option(
    item_type: type,
    kwargs: Any,
    key: str,
    default: Any,
    *,
    meta_key: str | None = None,
) -> Any

Resolve a wrapper option from (descending priority) kwargs, the model's Meta, or default.

Pops key from kwargs: an explicitly passed value (even None) wins over Meta, mirroring how ModelForm reads its own Meta options. meta_key reads a differently named Meta attribute (e.g. a collection wrapper's title kwarg defaulting from Meta.title_plural).

kwargs is typed Any rather than dict: callers pass their **kwargs: Unpack[SomeTypedDict] directly, and a TypedDict is not assignable to dict[Any, Any] under mypy (it may reject arbitrary key mutation), even though .pop() on an optional key is safe at runtime.

Source code in niceview/util.py
def meta_option(item_type: type, kwargs: Any, key: str, default: Any, *, meta_key: str | None = None) -> Any:
    """Resolve a wrapper option from (descending priority) kwargs, the model's Meta, or default.

    Pops `key` from `kwargs`: an explicitly passed value (even None) wins over Meta, mirroring
    how ModelForm reads its own Meta options. `meta_key` reads a differently named Meta attribute
    (e.g. a collection wrapper's `title` kwarg defaulting from `Meta.title_plural`).

    `kwargs` is typed `Any` rather than `dict`: callers pass their `**kwargs: Unpack[SomeTypedDict]`
    directly, and a TypedDict is not assignable to `dict[Any, Any]` under mypy (it may reject
    arbitrary key mutation), even though `.pop()` on an optional key is safe at runtime.
    """
    meta = getattr(item_type, 'Meta', None)
    value = getattr(meta, meta_key or key, default) if meta is not None else default
    return kwargs.pop(key, value)

maybe_await async

maybe_await(result: Any) -> Any

Await result if it is awaitable, pass it through otherwise.

Calling an async def handler returns a coroutine; dropping it does nothing at all except emit a RuntimeWarning, which is the worst way for a click handler to fail. Every place that invokes a caller-supplied callback directly (rather than through NiceGUI's handle_event, which does the same thing for callbacks that take an event argument) goes through here, so def and async def are equally valid there.

Source code in niceview/util.py
async def maybe_await(result: Any) -> Any:
    """
    Await `result` if it is awaitable, pass it through otherwise.

    Calling an `async def` handler returns a coroutine; dropping it does nothing at all except
    emit a RuntimeWarning, which is the worst way for a click handler to fail. Every place that
    invokes a caller-supplied callback directly (rather than through NiceGUI's `handle_event`,
    which does the same thing for callbacks that take an event argument) goes through here, so
    `def` and `async def` are equally valid there.
    """
    return await result if inspect.isawaitable(result) else result

confirm_dialog async

confirm_dialog(
    title: str,
    message: str,
    *,
    ok_label: str | None = None,
    cancel_label: str | None = None,
    ok_role: str = "ok",
    chrome_style: ChromeStyle | None = None,
    chrome_text: ChromeText | None = None,
) -> bool

Show a confirmation dialog. Returns True if confirmed, False if cancelled.

ok_role picks the role layer of the chrome cascade for the confirm button rather than a color: 'delete' makes it negative, and an application that restyles its delete buttons restyles this one with them.

Usage: if not await confirm_dialog('Delete Device', f'Delete {name!r}? Irreversible.', ok_label='Delete', ok_role='delete'): return

Source code in niceview/util.py
async def confirm_dialog(
    title: str,
    message: str,
    *,
    ok_label: str | None = None,
    cancel_label: str | None = None,
    ok_role: str = 'ok',
    chrome_style: ChromeStyle | None = None,
    chrome_text: ChromeText | None = None,
) -> bool:
    """Show a confirmation dialog. Returns True if confirmed, False if cancelled.

    `ok_role` picks the role layer of the chrome cascade for the confirm button rather than a
    color: 'delete' makes it negative, and an application that restyles its delete buttons
    restyles this one with them.

    Usage:
        if not await confirm_dialog('Delete Device', f'Delete {name!r}? Irreversible.',
                                    ok_label='Delete', ok_role='delete'):
            return
    """
    style = chrome_style or get_chrome_style()
    text = chrome_text or get_chrome_text()
    with chrome_dialog(style) as dialog:
        chrome_dialog_title(title, style)
        ui.markdown(message)
        with chrome_dialog_buttons(style):
            chrome_button('cancel', cancel_label or text_of(text.cancel_label), None, '', style,
                          lambda: dialog.submit(False), place='dialog')
            chrome_button(ok_role, ok_label or text_of(text.ok_label), None, '', style,
                          lambda: dialog.submit(True), place='dialog')
    return await dialog

input_dialog async

input_dialog(
    title: str,
    *,
    label: str,
    placeholder: str = "",
    value: str = "",
    validator: Callable[[str], bool | Awaitable[bool]]
    | None = None,
    error_message: str | None = None,
    chrome_style: ChromeStyle | None = None,
    chrome_text: ChromeText | None = None,
) -> str | None

Show an input dialog. Returns the entered string, or None if cancelled.

The validator may be sync or async — async is what you want when the answer lives elsewhere ("is this name still free?"), and it gates the OK button just as a sync one does.

Usage: name = await input_dialog('Create Project', label='Project Name', placeholder='my-project', validator=is_valid_filename, error_message='Only letters, digits, _ - + allowed') if name is None: return # cancelled create_project(name)

Source code in niceview/util.py
async def input_dialog(
    title: str,
    *,
    label: str,
    placeholder: str = '',
    value: str = '',
    validator: Callable[[str], bool | Awaitable[bool]] | None = None,
    error_message: str | None = None,
    chrome_style: ChromeStyle | None = None,
    chrome_text: ChromeText | None = None,
) -> str | None:
    """Show an input dialog. Returns the entered string, or None if cancelled.

    The validator may be sync or async — async is what you want when the answer lives elsewhere
    ("is this name still free?"), and it gates the OK button just as a sync one does.

    Usage:
        name = await input_dialog('Create Project', label='Project Name',
                                   placeholder='my-project', validator=is_valid_filename,
                                   error_message='Only letters, digits, _ - + allowed')
        if name is None:
            return  # cancelled
        create_project(name)
    """
    style = chrome_style or get_chrome_style()
    text = chrome_text or get_chrome_text()
    message = error_message or text_of(text.invalid_input)
    with chrome_dialog(style) as dialog:
        chrome_dialog_title(title, style)
        # A sync validator goes into Quasar's validation dict unchanged. An async one cannot:
        # NiceGUI's ValidationDict is sync-only, so it is wrapped in a ValidationFunction,
        # which does accept awaitables.
        validation: ValidationFunction | ValidationDict | None
        if validator is None:
            validation = None
        elif helpers.is_coroutine_function(validator):
            async def validation(value: str) -> str | None:  # type: ignore[no-redef]
                return None if await validator(value) else message  # type: ignore[misc, union-attr]
        else:
            validation = {message: validator}  # type: ignore[dict-item]
        inp = ui.input(label=label, placeholder=placeholder, value=value, validation=validation)
        with chrome_dialog_buttons(style):
            chrome_button('cancel', text_of(text.cancel_label), None, '', style,
                          lambda: dialog.submit(None), place='dialog')

            async def on_ok() -> None:
                if validator is not None and not await maybe_await(validator(inp.value)):
                    # return_result=False: an async validation function has no synchronous
                    # answer to give, and we only call this for the error message anyway.
                    inp.validate(return_result=False)
                    return
                dialog.submit(inp.value)
            chrome_button('ok', text_of(text.ok_label), None, '', style, on_ok, place='dialog')
    return await dialog

submit_dialog async

submit_dialog(
    title: str,
    message: str,
    buttons: tuple[str, ...] | list[str] = ("Cancel", "OK"),
    *,
    chrome_style: ChromeStyle | None = None,
) -> str | None

Show a dialog with a title, message and buttons; returns the text of the pressed button, or None if the dialog was dismissed (e.g. Escape key). Buttons can be prefixed with a character for formatting and to set the color: - '|': space before button (also in combination with color) - '1': primary - '2': secondary - 'a': accent - 'd': dark - '+': positive - '-': negative - 'i': info - 'w': warning

Usage: result = await submit_dialog('Title', 'Message', ['|dCancel', 'OK'])

result is the button text "Cancel" or "OK" (without prefixes), or None

Source code in niceview/util.py
async def submit_dialog(title: str, message: str, buttons: 'tuple[str, ...] | list[str]' = ('Cancel', 'OK'),
                        *, chrome_style: ChromeStyle | None = None) -> str | None:
    """Show a dialog with a title, message and buttons; returns the text of the
       pressed button, or None if the dialog was dismissed (e.g. Escape key).
       Buttons can be prefixed with a character for formatting and to set the color:
       - '|': space before button (also in combination with color)
       - '1': primary
       - '2': secondary
       - 'a': accent
       - 'd': dark
       - '+': positive
       - '-': negative
       - 'i': info
       - 'w': warning

       Usage:
       result = await submit_dialog('Title', 'Message', ['|dCancel', 'OK'])
       # result is the button text "Cancel" or "OK" (without prefixes), or None
       """

    style = chrome_style or get_chrome_style()
    with chrome_dialog(style) as dialog:
        chrome_dialog_title(title, style)
        ui.markdown(message)
        with chrome_dialog_buttons(style):
            for button in buttons:
                if button.startswith('|'):
                    ui.space()
                    button = button[1:]
                s2prop = { '1': 'color=primary', '2': 'color=secondary',
                        'a': 'color=accent', 'd': 'color=dark',
                        '+': 'color=positive', '-': 'color=negative',
                        'i': 'color=info', 'w': 'color=warning',}
                if button[0] in s2prop:
                    prop = s2prop[button[0]]
                    button = button[1:]
                else:
                    prop = None
                # The chrome cascade styles the button; the prefix, being the most specific
                # source, has the last word on its color.
                element = chrome_button('ok', button, None, '', style,
                                        lambda msg: dialog.submit(msg.sender.text), place='dialog')
                if prop:
                    element.props(prop)
    return await dialog