A team members table with search, filters, sorting, row selection with bulk actions, pagination, a row menu, editing in a drawer and a delete confirmation.
The screen behind "Settings → Team" in most SaaS apps, inside the app's shell: header, sidebar (an icon rail on tablets, a menu drawer on phones) and the content area. Everything works: filter by role, team and status, sort any column, select rows across pages, change roles in bulk, edit someone in a drawer, invite a new member and delete with a confirmation. Changes are kept in local state until you reload the page.
minimal · light · #3fa34d
FernwayAP
Team members
Invite people, set what they can do and remove access when they leave.
Search and filters. The search box matches names and emails as you type. Role and status are Selects with an "All" option; team is a Combobox, so you can type to find it in a longer list. On a phone, the filters move into a bottom drawer behind a Filters button that counts the active ones. Clear filters appears when any filter is on, and the line above the table says how many members match.
Sorting. Every column with data sorts on click: ascending, descending, then back to unsorted. The table starts sorted by name.
Selection and bulk actions. Rows have checkboxes, and the header one selects every row that matches the filters, on every page. With a selection, a bar offers Change role, Suspend and Delete. Changing a filter clears the selection, so a bulk action never reaches rows you can't see.
Pagination. Ten rows per page. A new filter goes back to page 1.
Row menu. Edit, Suspend or Reactivate, and Delete for one member.
Edit and invite in a drawer. The same form edits a member or invites a new one. It checks the name, the email format and that no one else uses the email.
Delete with a confirmation. An AlertDialog names who is being deleted and what happens next. Every change ends with a toast.
Narrow screens. Below 720px the Team and Last active columns are dropped; on a phone, role and status move under the name. If it still doesn't fit, the table scrolls sideways.
The table has a name, and sortable headers are buttons that set aria-sort, so screen readers say which column is sorted and in which order.
Every checkbox and row menu is named after its member ("Select Aline Prado", "Actions for Aline Prado"); the selection count and the "Showing 12 of 18" line are live regions.
The drawer and the dialog trap focus and return it to the menu that opened them. After a delete, the row is gone, so focus moves to the page heading instead.
When the drawer form fails, focus moves to the first field with an error, and the error is read with it.
The Code tab shows the whole screen as one file, with @camaleo/react and lucide-react as its only dependencies (DataTable brings TanStack Table with it). The members are fixed data: replace them with your API, and move the filtering and paging to the server when the list gets long (DataTable takes pageCount, totalRows and controlled sorting for that).