Skip to content

Team management ​

A complete application screen, built only from library components, with fictional in-memory data and no backend.

The component pages show one thing at a time. This shows what the library looks like when the pieces have to work together: a list with search, filters, sorting, selection and paging that stay coherent with each other, a dialog that validates, a destructive confirmation, feedback that follows an action, and every state a real list reaches.

It is functional. Search narrows the results, the filters combine, sorting and paging survive each other, editing validates and updates the row, Cancel discards, and Remove only affects the demo data. Everything resets with Reset demo.

Team members

22 people across 4 teams.

Demo controls
Density
Appearance
Fields
StatusLast activeRow actions
Ama Boatengama.boateng@example.com
OwnerPlatformActive2 hours ago
Aoife Brennanaoife.brennan@example.com
AdminDesignActive6 hours ago
Chidi Okaforchidi.okafor@example.com
EditorSupportActive3 hours ago
Cristina Rossicristina.rossi@example.com
EditorDesignActive30 minutes ago
Diego Salazardiego.salazar@example.com
ViewerSupportInvitedNever
Fatima Zahrafatima.zahra@example.com
EditorSupportActive8 hours ago
Hanna Öberghanna.oberg@example.com
ViewerSupportActive5 hours ago
Isabela Duarteisabela.duarte@example.com
EditorDesignActiveYesterday
Janek Nowakjanek.nowak@example.com
ViewerPlatformActive2 days ago
Kwame Mensahkwame.mensah@example.com
AdminSupportInvitedNever

The whole workflow is reachable from the keyboard ​

Tab to the search field, type, Tab to the filters and choose with the arrow keys, Tab into the table, sort a column with Enter, select rows with Space, and open a row's editor from its action button. The dialog takes focus on its first field, holds Tab inside itself, closes on Esc, and hands focus back to the control that opened it. The destructive confirmation lands on Cancel, never on the button that deletes.

What it demonstrates, and where each piece is documented ​

On screenComponent
Location and page titleNbBreadcrumbs (no page yet)
Search and filtersNbTextInput, NbSelect
The listNbDataTable
PagingNbPagination
StatusNbBadge
EditingNbModal
Destructive confirmationNbConfirm
Success and failure feedbackNbToaster

Decisions worth copying ​

Filters compose, and an empty result is its own state. Combining a role with a status asks for the intersection and is allowed to return nothing. That is a different sentence from "nobody has been added yet", so the table says something different, and the way out ("Clear filters") is in the toolbar directly above it.

One empty surface, not two. An earlier version put an NbEmptyState under the table while the table showed its own default "No data to display", which stacked two different empty messages on one result. The table is the thing that is empty, so the table is what speaks.

Selection narrows with the results. Filter to one team, select three people, clear the filter, press Remove: without reconciliation you delete rows the user never saw. Narrowing the results narrows the selection with it.

Paging is clamped, not trusted. Deleting the last row of the last page leaves the page number past the end. Unclamped, that renders an empty page with a working "previous" button, which reads as data loss.

Icon-only row actions carry names. Each one is labelled with the person it acts on ("Edit Ama Boateng"), so a screen reader user hears which row a button belongs to instead of "button, button" twenty-two times.

Long values truncate rather than reflow. Names and addresses are clipped with an ellipsis so a long one cannot change the row height.

A custom cell has to take part in density. The Member cell stacks a name over an address, which made it 48px tall and pinned the row height there: the density control moved the table's own token and changed nothing visible. Compact now drops the second line and shrinks the avatar, so the control means something. A cell that ignores density silently overrides it.

Where the primary action goes ​

One primary action per screen or section, as a single filled button in the header of the thing it acts on. Never a row of buttons underneath the list.

A filled button stranded below a table reads as a footer for the page rather than the control for that list, and it is the last thing a person looking for "how do I add one" will find. The heading is where they are already looking, because it is what told them which list they are in.

The rest follows from there:

  • Everything else stays quiet. On this screen "Reset demo" is secondary because it is not what the screen is for. Neither button is coloured by status, because a control's colour says what it does, not how things are.
  • danger is for destroying data, and nothing else. It is used exactly twice here, both times on a control that deletes.
  • A section is a screen for this purpose. A page made of several panels gets one primary action per panel, in each panel's own header, rather than one button at the bottom standing in for all of them.

This is about actions on a region inside a page. For actions on the page as a whole, the frame owns the answer: they belong in topbar-right, and explicitly not in fixedbar or the sidebar. See Anatomy: what each region is for.

Demo controls ​

The strip below the title is dashed on purpose: it is not part of the product screen. It switches the table between its ready, loading and error states, between compact and comfortable density, between square and rounded appearance, and between the default field treatment and the classic-fields opt-out, so the same screen can be compared in each without leaving the page.

This example is the test

If a component needed changing to make this screen work, the component was changed, not this page. There are no private selectors here and no styles that compensate for a component shipping something different. Anything you can see in it, you can build.