Skip to main content

Table

This table differs from a markdown table in that it supports clicking or selecting rows, provides built-in search, sort, filter and group by. If you simply want to display a non-interactive table of information, use a markdown table.

The name attribute indicates how to reference this component in the query arguments: q.args.<name-attr>.

You can see the API for ui.table or check the interactive example in Tour app.

Basic table

table-0

q.page['example'] = ui.form_card(box='1 1 3 3', items=[
ui.table(
name='table',
columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
],
rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
]
)
])

With column alignment

With this option, you can align the values of a particular column to left, right or center. This is optional and the default alignment is set to left.

table-1

q.page['example'] = ui.form_card(box='1 1 5 3', items=[
ui.table(
name='table',
columns=[
ui.table_column(name='first_name', label='First Name', align='center'),
ui.table_column(name='last_name', label='Last Name', align='right'),
ui.table_column(name='username', label='Username', align='left'),
ui.table_column(name='company', label='Company'),
],
rows=[
ui.table_row(name='row1', cells=['John', 'Doe', 'johndoe', 'Stephens LLC']),
ui.table_row(name='row2', cells=['Alice', 'Smith', 'alicesmith', 'Walker and Sons']),
ui.table_row(name='row3', cells=['Bob', 'Adams', 'bobadams', 'Frank Ltd']),
]
)
])

With selection

If multiple is set to False (default), each row in the table is clickable. When a cell in the column with link=True (defaults to first column) is clicked or the row is doubleclicked, the form is submitted automatically and q.args.table_name is set to [row_name], where table_name is the name of the table, and row_name is the name of the row that was clicked. Note that doubleclicking the row if single selection is active results in submission as well.

If multiple is set to True, each row in the table is selectable. A row can be selected by clicking on it. Multiple rows can be selected either by shift+clicking or using marquee selection. When the form is submitted, q.args.table_name is set to [row1_name, row2_name, ...] where table_name is the name of the table, and row1_name, row2_name are the name of the rows that were selected. Note that if multiple is set to True, the form is not submitted automatically and one or more buttons in the form are required to trigger submission.

table-2

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(name='table', multiple=True, columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

When multiple is specified, there is an option to control when should row checkboxes be visible. By default, they are only visible on hover. Available options are always, on-hover, hidden.

table-3

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(name='table', checkbox_visibility='always',
multiple=True, columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

With preselection

If you want to see some rows preselected, use the values attribute. Note that if this parameter is set, multiple selections will be allowed (multiple=True implicitly).

table-4

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(name='table', values=['row1'], columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

As stated above, the table provides a built-in search also. Activation consists of specifying any column as searchable. This way one can control which columns should affect searched results.

table-5

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(name='table', columns=[
ui.table_column(name='name', label='Name', searchable=True),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

With filter

Similar to search if you want to take advantage of a built-in filter, just specify a column as filterable. This will render a small chevron next to a column name which expands after clicking, giving you the option to check any of the unique column values and filter the table. We advise using filtering only on columns that consist of a limited set of values, e.g. statuses like RUNNING, PENDING. If you specified it on a column with any arbitrary text column, there would be too many filtering checkboxes which would be hard to navigate for your users.

table-6

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(name='table', columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='status', label='Status', filterable=True),
], rows=[
ui.table_row(name='row1', cells=['John', 'Employed']),
ui.table_row(name='row2', cells=['Alice', 'Unemployed']),
ui.table_row(name='row3', cells=['Bob', 'Employed']),
])
])

With group by

Another cool feature of the Wave table is group by. All it takes is to specify a groupable option on the table and a dropdown will render with columns on which a user can group by data.

table-7

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(name='table', groupable=True, columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

With download

Want to allow your users to download the data you just showed them via table? No problem! Simply specify downloadable prop.

table-8

q.page['example'] = ui.form_card(box='1 1 3 3', items=[
ui.table(name='table', downloadable=True, columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

With reset

If you take advantage of all these built-in features, it might be a good idea to provide your users with an escape hatch when they search/filter/group by too much and would like to get back to original view with ease. That's exactly what resettable is for.

table-9

q.page['example'] = ui.form_card(box='1 1 3 3', items=[
ui.table(name='table', resettable=True, columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

Setting width and height

By default, the table tries to fit all horizontal available space and use as much vertical space as needed. For tables with > 10 rows, the initial height is 500px.

In some cases though, it might be desirable to control the dimensions yourself via width and height attributes. Both accept CSS units, however % values for height might not work as you think (especially in flex layout) so we discourage its use in favor of more static units like px or rem.

table-10

q.page['example'] = ui.form_card(box='1 1 3 3', items=[
ui.table(name='table', width='200px', height='200px', columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='surname', label='Surname'),
], rows=[
ui.table_row(name='row1', cells=['John', 'Doe']),
ui.table_row(name='row2', cells=['Alice', 'Smith']),
ui.table_row(name='row3', cells=['Bob', 'Adams']),
])
])

With tags

Use tags to emphasize a specific value, usually an enum value like a certain state for example. For multiple tags in a single row use , as a delimiter.

table-11

q.page['example'] = ui.form_card(box='1 1 3 3', items=[
ui.table(
name='table',
columns=[
ui.table_column(name='text', label='Process'),
ui.table_column(name='tag', label='Status',
cell_type=ui.tag_table_cell_type(
name='tags',
tags=[
ui.tag(label='FAIL', color='$red'),
ui.tag(label='DONE', color='#D2E3F8', label_color='#053975'),
ui.tag(label='SUCCESS', color='$mint'),
]
))
],
rows=[
ui.table_row(name='row1', cells=['Process1', 'FAIL']),
ui.table_row(name='row2', cells=['Process2', 'SUCCESS,DONE']),
ui.table_row(name='row3', cells=['Process3', 'DONE']),
])
])

With text-overflow

By default, text that does not fit into the table cell is signaled to users with ellipsis (...). However, you can change this with an overflow prop. Available options are:

  • 'tooltip': shows the whole text in a tooltip when hovering over it
  • 'wrap': wraps the long text on multiple lines

table-12

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(name='table', columns=[
ui.table_column(name='name', label='Name'),
ui.table_column(name='about', label='About', cell_overflow='wrap'),
], rows=[
ui.table_row(
name='row1',
cells=['John', 'John is the former employee of the year.']
),
ui.table_row(
name='row2',
cells=['Alice', 'Alice is an ambicious newcommer.']
),
ui.table_row(
name='row3',
cells=['Bob', 'Bob is in our company for almost 35 years']
),
])
])

With groups

If groupable prop does not suit your needs, you can specify your custom groups with the groups attribute.

table-13

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(
name='table',
columns=[
ui.table_column(name='task', label='Task'),
ui.table_column(name='priority', label='Priority')
],
groups=[
ui.table_group("Assigned to Bob", [
ui.table_row(name='row1', cells=['Task1', 'Moderate']),
ui.table_row(name='row2', cells=['Task2', 'High'])
]),
ui.table_group("Assigned to John", [
ui.table_row(name='row3', cells=['Task3', 'High']),
ui.table_row(name='row4', cells=['Task4', 'Low']),
ui.table_row(name='row5', cells=['Task5', 'Very High'])
])
])
])

With collapsed groups

Groups are shown in a collapsed state by default. With the collapsed attribute you can change this behavior.

table-14

q.page['example'] = ui.form_card(box='1 1 3 4', items=[
ui.table(
name='table',
columns=[
ui.table_column(name='task', label='Task'),
ui.table_column(name='priority', label='Priority')
],
groups=[
ui.table_group("Assigned to Bob", [
ui.table_row(name='row1', cells=['Task1', 'Moderate']),
ui.table_row(name='row2', cells=['Task2', 'High'])
], collapsed=False),
ui.table_group("Assigned to John", [
ui.table_row(name='row3', cells=['Task3', 'High']),
ui.table_row(name='row4', cells=['Task4', 'Low']),
ui.table_row(name='row5', cells=['Task5', 'Very High'])
])
])
])

With pagination

Paginated table differs from regular table in its ability to handle large amounts of data (100k+ rows). Instead of sending all the rows at once, pagination allows you to send just a specific chunk of data needed for the current page. The size of this chunk is defined as rows_per_page.

table-15

q.page['example'] = ui.form_card(box='1 1 3 6', items=[
ui.table(
name='table',
columns=[ui.table_column(name='name', label='Name')],
rows=[
ui.table_row(name='row1', cells=['John']),
ui.table_row(name='row2', cells=['Alice']),
ui.table_row(name='row3', cells=['Bob']),
ui.table_row(name='row4', cells=['John']),
ui.table_row(name='row5', cells=['Alice']),
ui.table_row(name='row6', cells=['Bob']),
ui.table_row(name='row7', cells=['John']),
ui.table_row(name='row8', cells=['Alice']),
ui.table_row(name='row9', cells=['Bob']),
ui.table_row(name='row10', cells=['John']),
],
# Add pagination attribute to make your table paginated.
pagination=ui.table_pagination(total_rows=20, rows_per_page=10),
# Register events to listen, all of these have to be handled by developer.
events=['page_change']
)
])

Check the complete example with all the supported features like sort/filter/search/download/reset.

With actions

You can group multiple actions in a context menu for each row by setting cell_type to ui.menu_table_cell_type and providing the commands you want. Use q.args.<command_name> to identify which command was clicked. Since every cell in the actions column will have the same content (an icon that opens a menu) you can simply specify an empty string as a placeholder so that the cells are provided in the correct order.

See the example to learn how to handle the actions in a real-world scenario.

table-16

commands = [
ui.command(name='details', label='Details', icon='Info'),
ui.command(name='delete', label='Delete', icon='Delete'),
]

q.page['example'] = ui.form_card(box='1 1 3 3', items=[
ui.table(
name='table',
columns=[ui.table_column(name='actions', label='Actions', cell_type=ui.menu_table_cell_type(name='commands', commands=commands))],
rows=[ui.table_row(name='first_row', cells=[''])]
)
])

Markdown support

Leverage the power and versatility of Markdown by setting cell_type to markdown_cell_type. To open links in a new tab use target='_blank'.

table-17

q.page['example'] = ui.form_card(box='1 1 3 6', items=[
ui.text_xl(content='Table with Markdown'),
ui.table(
name='table',
columns=[
ui.table_column(name='description', label='Description', min_width='200',
cell_type=ui.markdown_table_cell_type(target='_blank')),
ui.table_column(name='markdown', label='Markdown',
cell_type=ui.markdown_table_cell_type(target='_blank')),
],
height='450px',
rows=[
ui.table_row(name='row1', cells=['Normal text', 'Hello World!']),
ui.table_row(name='row2', cells=['Bold text', 'This is a **bold** text.']),
ui.table_row(name='row3', cells=['Italicized text', 'This is a _italicized_ text.']),
ui.table_row(name='row4', cells=['Link', '<http://wave.h2o.ai>']),
ui.table_row(name='row5', cells=['Absolute link with label', '[Wave website](http://wave.h2o.ai/)']),
ui.table_row(name='row6', cells=['Relative link with label', '[Go to /wave](/wave)']),
ui.table_row(name='row7', cells=['Email', '<fake@email.com>']),
ui.table_row(name='row8', cells=['Code', '``inline code``']), # change to monospaced font
]
)
])