Setting `table.pagination` after construction paginates the rows but leaves the pagination controls hidden
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
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.
The constructor derives a second prop — nicegui/elements/table.py:75-76 on 3.15.0:
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):
@pagination.setter
def pagination(self, value: dict) -> None:
self._props['pagination'] = valueA table built without pagination starts at hide-pagination = True, and nothing ever clears it.
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 fromFound 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.
Source: zauberzeug/nicegui