Skip to content

Typing: ui.dictionary.__init__ uses invariant dict[str, UIElement] and rejects dict[str, ui.checkbox] #10631

Closed

Description

@vbrozik

Describe the bug

marimo.ui.dictionary.__init__ currently types elements as:

dict[str, UIElement[Any, Any]]

This causes static type checkers (Pylance/Pyright) to reject valid subclass dictionaries like dict[str, mo.ui.checkbox] because dict is invariant.

Observed runtime signature:

(self, elements: 'dict[str, UIElement[Any, Any]]', *, label: 'str' = '', on_change: 'Callable[[dict[str, object]], None] | None' = None) -> 'None'

Error message

Pylance/Pyright:

Argument of type dict[str, checkbox] cannot be assigned to parameter elements of type dict[str, UIElement[Any, Any]] ... Consider switching from dict to Mapping.

Why this is problematic

  • Runtime behavior is correct.
  • Users must add casts like cast(dict[str, Any], elements).
  • UIElement appears internal/non-public, so precise public typing is hard to express externally.

Proposed fix

Use a covariant input type for constructor parameter, e.g.:

Mapping[str, UIElement[Any, Any]]

Will you submit a PR?

  • Yes

Environment

  • marimo: 0.24.0
  • Python: 3.14 (uv-managed)
  • Type checker: Pylance/Pyright

Code to reproduce

from __future__ import annotations

from collections.abc import Iterable, Mapping
import marimo as mo

class Checkboxes(mo.ui.dictionary):
    def __init__(
        self,
        options: Iterable[str] | Mapping[str, str],
        preselected_options: Iterable[str] | None = None,
    ):
        pre = set(preselected_options) if preselected_options else set()
        elements: dict[str, mo.ui.checkbox]
        if isinstance(options, Mapping):
            elements = {
                option: mo.ui.checkbox(label=label, value=(option in pre))
                for option, label in options.items()
            }
        else:
            elements = {
                option: mo.ui.checkbox(label=option, value=(option in pre))
                for option in options
            }
        super().__init__(elements)  # type error

Activity

  1. added theissue type on Aug 23, 2026
  2. changed the title [-]Typing: ui.dictionary.__init__ uses invariant dict[UIElement] and rejects dict[str, ui.checkbox][/-] [+]Typing: ui.dictionary.__init__ uses invariant dict[str, UIElement] and rejects dict[str, ui.checkbox][/+] on Aug 23, 2026
  3. Light2Dark commented on Aug 24, 2026

    @Light2Dark
    Member

    We'd be happy to accept a contribution. You may also want to look at updating the function below too.

    def validate_and_clone(
        elements: dict[str, UIElement[U, V]],
    ) -> dict[str, UIElement[U, V]]:
    

    ui.batch also has the same issue, you may want to do a pass on similar elements like dictionary

  4. roridemonslayer commented on Aug 26, 2026

    @roridemonslayer

    I'd like to take this on. Plan: change dictionary.__init__'s elements param to Mapping[str, UIElement[Any, Any]], do the same for validate_and_clone's input parameter (keeping its return type as dict since it builds a fresh one), and apply the same covariant-input fix to ui.batch. Will open a PR with tests shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions