#6235·nicegui

Setting `table.pagination` after construction paginates the rows but leaves the pagination controls hidden

Author: evnchnCreated Aug 3, 2026Updated Aug 23, 2026
Labelsbug

First Check

  • I added a very descriptive title here.
  • This is not a security issue.
  • This is not a Q&A. I am sure something is wrong with NiceGUI or its documentation.
  • I used the GitHub search to find a similar issue and came up empty.

Example Code

python
from nicegui import ui

rows = [{'id': i} for i in range(20)]

ui.label('A: ui.table(rows=rows, pagination=5)')
a = ui.table(rows=rows, pagination=5).classes('w-64')

ui.label('B: t = ui.table(rows=rows); t.pagination = {"rowsPerPage": 5}')
t = ui.table(rows=rows).classes('w-64')
t.pagination = {'rowsPerPage': 5}          # <-- the only difference

# visible in the browser; also checkable without one:
for label, table in [('A', a), ('B', t)]:
    print(label, {k: table._props[k] for k in ('pagination', 'hide-pagination')})

ui.run()

Description

Filed by Claude Code on @evnchn's behalf.

TL;DR:

Both tables above paginate to 5 rows. Only table A gets a pagination bar.

Table B shows 5 of its 20 rows with no controls to reach the other 15 — the constructor derives hide-pagination from the pagination argument, but the property setter never updates it.

There is a workaround (t.props('hide-pagination=false')), so this is surprising rather than fatal.

Cause

The constructor derives a second prop — nicegui/elements/table.py:75-76 on 3.15.0:

python
self._props['hide-pagination'] = pagination is None
self._props['pagination'] = pagination if isinstance(pagination, dict) else {'rowsPerPage': pagination or 0}

The setter writes only pagination, leaving hide-pagination untouched, and does not apply the int{'rowsPerPage': n} normalisation either (table.py:396-397):

python
@pagination.setter
def pagination(self, value: dict) -> None:
    self._props['pagination'] = value

A table built without pagination starts at hide-pagination = True, and nothing ever clears it.

Measured in a real browser (Chromium via Playwright, nicegui 3.15.0 from PyPI)

20 rows, rowsPerPage = 5, counting tbody tr and testing .q-table__bottom for visibility:

build path body rows rendered pagination bar visible
ui.table(rows=rows, pagination=5) 5 True
t = ui.table(rows=rows); t.pagination = {'rowsPerPage': 5} 5 False
then t.props('hide-pagination=false') 5 True (workaround)

The example above also prints this on startup, so the divergence is checkable without a browser:

A {'pagination': {'rowsPerPage': 5}, 'hide-pagination': False}
B {'pagination': {'rowsPerPage': 5}, 'hide-pagination': True}

A screenshot of the two tables side by side is available if useful.

Scope of the sweep this came from

Found by comparing, for every ui.* element, the state produced by ui.X(param=v) against ui.X(); x.param = v. 42 constructor parameters across all element classes have a same-named settable property; 36 were exercised (1 has no usable test value, 5 need mutually-required constructor arguments).

table.pagination was the only pair where the two write paths produce a behavioural difference. The other divergence found was cosmetic and is not reported: the validation setter leaves error-message: None in the props where the constructor leaves the key absent, which is equivalent for Quasar.

NiceGUI Version

3.15.0

Python Version

3.12.11

Browser

Chrome

Operating System

macOS

Additional Context

The setter is annotated value: dict, so passing an int is already out of contract and I have not treated that as part of the bug — the report is about the dict case, which is in contract and still wrong.