Skip to content

feat: mo.ui.matrix()#8354

Merged
akshayka merged 23 commits intomainfrom
aka/ui-matrix
Feb 19, 2026
Merged

feat: mo.ui.matrix()#8354
akshayka merged 23 commits intomainfrom
aka/ui-matrix

Conversation

@akshayka
Copy link
Contributor

@akshayka akshayka commented Feb 18, 2026

This PR adds two new public APIs:

  1. mo.ui.matrix(), which adds a reactive matrix component inspired by @koaning's wigglystuff matrix input
  2. mo.ui.vector(), which is analogous to matrix but for vectors

Signature remarks

  • An initial value for the matrix is required. This value can be a Python list of lists, with each list a row of the matrix. It can also be a NumPy array.
  • Minimum and maximum values can be provided, as well as a step size. These can be scalars or ArrayLike objects.
  • The matrix can be constrained to be symmetric with a keyword argument
  • The matrix can be disabled, either entirely or on an element wise basis with a boolean array mask. The latter provides a convenient way to constrain a matrix to be (for example) upper triangular, diagonal, or banded.
  • The precision at which to display the entries is chosen intelligently on the user's behalf, but can be overriden

Python usage

With Python built-in types:

mat = mo.ui.matrix([[1, 0], [0, 1]])
matrix.value

With NumPy:

mat = mo.ui.matrix(np.eye(2))
np.asarray(matrix.value)

The value, bounds, step, and disabled arguments can optionally be NumPy
arrays, interpreted elementwise.

mat = mo.ui.matrix(
    np.zeros((3, 3)),
    min_value=np.full((3, 3), -10.0),
    max_value=np.full((3, 3), 10.0),
    step=np.full((3, 3), 0.5),
)

value type

The element's value is a list of scalar (int or float) lists for matrix, and a flat list for vector. This is readily converted to a NumPy array by the user.

Implementation

mo.ui.vector reuses the frontend implementation of ui.matrix, promoting its input to a 2D array

Media

image
ui-matrix.mp4

@vercel
Copy link

vercel bot commented Feb 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Feb 19, 2026 3:55am

Request Review

@github-actions github-actions bot added the documentation Improvements or additions to documentation label Feb 18, 2026
@akshayka akshayka added the enhancement New feature or request label Feb 18, 2026
@koaning
Copy link
Contributor

koaning commented Feb 18, 2026

Not 100% sure, but it might also be fun to play around with __add__-like dunder methods so that a user could also do things like widget @ matrix? We can also choose to ignore this for now and add that later if we want. It feels like it could be fun, but it is also easy to overdo operators in Python.

@koaning
Copy link
Contributor

koaning commented Feb 18, 2026

With regards to the name, do we really want to call it matrix? Technically we may also want to declare 1D arrays here? So maybe mo.ui.ndarray? Not 100% sure.

@manzt
Copy link
Collaborator

manzt commented Feb 18, 2026

So maybe mo.ui.ndarray? Not 100% sure.

To me, this naming would suggest I could pass in n-dimensional arrays (well beyond 1D and 2D).

@akshayka akshayka marked this pull request as ready for review February 18, 2026 17:00
@akshayka akshayka requested review from dmadisetti and manzt and removed request for Light2Dark and manzt February 18, 2026 17:00
@akshayka
Copy link
Contributor Author

Not 100% sure, but it might also be fun to play around with add-like dunder methods so that a user could also do things like widget @ matrix? We can also choose to ignore this for now and add that later if we want. It feels like it could be fun, but it is also easy to overdo operators in Python.

Maybe in the future? I see the appeal. But historically we've chosen not to implement overrides (e.g., for mo.ui.slider) in favor of being explicit.

With regards to the name, do we really want to call it matrix?

I can add a convenience mo.ui.vector() that reuses the frontend plugin under the hood

manzt
manzt previously approved these changes Feb 18, 2026

_name: Final[str] = "marimo-matrix"

def __init__(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be overkill to consider using @overload on __init__ for some nice ergnomics/typing behavior? e.g. .value returns np.ndarray when constructed from an ndarray, list[list[Numeric]] when constructed from a list:

_T = TypeVar("_T")

class matrix(UIElement[list[list[Numeric]], _T]):
    @overload
    def __init__(self: matrix[np.ndarray], value: np.ndarray, ...) -> None: ...
    @overload
    def __init__(self: matrix[list[list[Numeric]]], value: list[list[Numeric]], ...) -> None: ...

    def _convert_value(self, value: list[list[Numeric]]) -> _T:
        if self._return_numpy:
            return np.asarray(value)
        return value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not overkill on the implementation. But I wonder about the ergonomics. Is it confusing for value's type at runtime to depend on what was passed in?

Copy link
Contributor Author

@akshayka akshayka Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we did support returning NumPy out, then we might also need to support returning as a torch.Tensor, jax.Array, tensorflow.Tensor, since these are all arraylike objects that should work with the matrix constructor. I am mildly worried this may add too much complexity.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i honestly had the same thought. We'd need some kind of narwhals abstraction which i'm not sure there is for arrays.

this current api seems the most forward thinking.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment below, there's already an interop standard

Comment on lines +89 to +95
// Local display value – always tracks the latest visual state.
// When debounce is true we update this locally during drag and only
// call setValue on pointer-up.
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: useEffect sync causes an extra render. I think we could just read the prop directly when not dragging instead. Not a big deal though.

const [draft, setDraft] = useState(value);
const internalValue = activeCell != null ? draft : value;

And then setDraft in various handlers.

@akshayka
Copy link
Contributor Author

@dmadisetti @manzt I've added mo.ui.vector for 1D arrays, and have updated the PR description to describe the new API.

The flex-based layout couldn't right-align numbers within columns
because each cell sized to its content independently. Switching to an
HTML
[`table`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/table)
gives automatic column-width equalization, so `text-right` on each
`<td>` properly aligns numbers like numpy output. It is also
semantically more appropriate here since a matrix is tabular data.

Also adds `tabular-nums` for consistent digit widths.

| before | after |
   |-------------|---------|
| <img
src="https://github.com/user-attachments/assets/99fad98b-8c5e-47e5-98c2-b64cd94d612a"
/> | <img
src="https://github.com/user-attachments/assets/5f090179-73a8-4388-8d72-484cedd3e295"
/> |





https://github.com/user-attachments/assets/e08b905e-73a8-4f1e-8f78-f7b7b67b4acd
args=args,
on_change=None,
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think defining an __array__/__array_interface__ would be ideal (for our own apis as well like mo.image) https://numpy.org/doc/stable/user/basics.interoperability.html#the-array-interface-protocol

Copy link
Collaborator

@dmadisetti dmadisetti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the api looks great, mirrors slider which is awesome.

One comment on interop, which I think manzt mentioned as well (https://numpy.org/doc/stable/user/basics.interoperability.html#the-array-interface-protocol)


_name: Final[str] = "marimo-matrix"

def __init__(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment below, there's already an interop standard

@akshayka
Copy link
Contributor Author

@dmadisetti for inter-op, we would still need to hardcode the original value's data type, and hardcode its constructor when returning the matrix via the value attribute, no? That's not hard for numpy, torch, jax, but it feels cumbersome and not very maintainable long term.

@akshayka akshayka merged commit 6e6d801 into main Feb 19, 2026
35 of 43 checks passed
@akshayka akshayka deleted the aka/ui-matrix branch February 19, 2026 18:40
manzt added a commit that referenced this pull request Feb 19, 2026
When `mo.ui.vector` changes shape (e.g. toggling `transpose`), the
component crashed because `internalValue` state held the old shape while
props like `disabled` already had the new shape, causing out-of-bounds
access (`disabled[1]` on a 1-row array).

Replace the `useEffect`-synced `internalValue` with a simpler
`draft`/`displayValue` pattern: use local draft state only during active
drag, otherwise read directly from the `value` prop. This avoids the
one-render-behind stale-state problem entirely.

Ref: #8354 (comment)
manzt added a commit that referenced this pull request Feb 19, 2026
When `mo.ui.vector` changes shape (e.g. toggling `transpose`), the
component crashed because `internalValue` state held the old shape while
props like `disabled` already had the new shape, causing out-of-bounds
access (`disabled[1]` on a 1-row array).

Replace the `useEffect`-synced `internalValue` with a simpler
`draft`/`displayValue` pattern: use local draft state only during active
drag, otherwise read directly from the `value` prop. This avoids the
one-render-behind stale-state problem entirely.

Ref: #8354 (comment)
LiquidGunay pushed a commit to LiquidGunay/marimo that referenced this pull request Feb 21, 2026
This PR adds two new public APIs:

1. `mo.ui.matrix()`, which adds a reactive matrix component inspired by
@koaning's wigglystuff matrix input
2. `mo.ui.vector()`, which is analogous to `matrix` but for vectors

## Signature remarks

* An initial value for the matrix is required. This value can be a
Python list of lists, with each list a row of the matrix. It can also be
a NumPy array.
* Minimum and maximum values can be provided, as well as a step size.
These can be scalars or `ArrayLike` objects.
* The matrix can be constrained to be symmetric with a keyword argument
* The matrix can be disabled, either entirely or on an element wise
basis with a boolean array mask. The latter provides a convenient way to
constrain a matrix to be (for example) upper triangular, diagonal, or
banded.
* The precision at which to display the entries is chosen intelligently
on the user's behalf, but can be overriden

## Python usage

With Python built-in types:

```python
mat = mo.ui.matrix([[1, 0], [0, 1]])
```

```python
matrix.value
```

With NumPy:

```python
mat = mo.ui.matrix(np.eye(2))
```

```python
np.asarray(matrix.value)
```

The value, bounds, step, and disabled arguments can optionally be NumPy
arrays, interpreted elementwise.

```python
mat = mo.ui.matrix(
    np.zeros((3, 3)),
    min_value=np.full((3, 3), -10.0),
    max_value=np.full((3, 3), 10.0),
    step=np.full((3, 3), 0.5),
)
```

## `value` type

The element's `value` is a list of scalar (int or float) lists for
`matrix`, and a flat list for `vector`. This is readily converted to a
NumPy array by the user.

## Implementation

`mo.ui.vector` reuses the frontend implementation of `ui.matrix`,
promoting its input to a 2D array

## Media

<img width="786" height="331" alt="image"
src="https://github.com/user-attachments/assets/d66afbd7-9cf3-4f5b-a728-3a25e82b592b"
/>


https://github.com/user-attachments/assets/c5bf1ff5-3cfc-4cd0-9254-42a5364f4941

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Trevor Manz <trevor.j.manz@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants