Tables - National Design System

Structured data presentation with built-in sorting, row selection, responsive scrolling, and pagination for datasets of any size

Standard Table

The default table layout with striped rows, hover highlighting, and rounded borders

Name Email Role Status
Ahmed Al-Rashidi ahmed.rashidi@gov.sa Senior Developer Active
Fatima Al-Harbi fatima.harbi@gov.sa UX Designer Active
Sara Al-Dosari sara.dosari@gov.sa Marketing Lead Active
Layla Al-Qahtani layla.qahtani@gov.sa HR Specialist Active

Sortable Table

Click any column header to cycle through ascending, descending, and original order

Department
Employees
Budget
Status
Information Technology 25 5,625,000 SAR Active
Human Resources 12 3,000,000 SAR Active
Sales 18 4,500,000 SAR Under Review
Marketing 8 2,250,000 SAR Planning

Table with Feedback Icons

Status columns using feedback icons for quick visual scanning of row states

Ticket ID Customer Issue Type Priority Status
#TK-001
Omar Al-Ahmad Login Issue Low
#TK-002
Layla Al-Mansouri Performance Medium
#TK-003
Yusuf Al-Kindi Data Loss High
#TK-004
Aisha Al-Farisi Feature Request Low

Table with Selection

Row checkboxes with a select-all header for bulk operations. The header checkbox shows an indeterminate state when some rows are selected.

Name Email Department Status
Hassan Al-Mukhtar hassan.almukhtar@moi.gov.sa Engineering Active
Nadia Al-Khatib nadia.alkhatib@moi.gov.sa Design Active
Tariq Al-Sudairi tariq.alsudairi@moi.gov.sa Marketing Pending
Zara Al-Habib zara.alhabib@moi.gov.sa Sales On Leave

Center Aligned Table

Center-align all cell content when the data benefits from symmetrical presentation

Quarter Revenue Growth Status
Q1 2024 2,450,000 SAR +12% On Track
Q2 2024 2,780,000 SAR +13.5% On Track
Q3 2024 2,610,000 SAR -6.1% At Risk
Q4 2024 3,100,000 SAR +18.8% On Track

Loading State

A shimmer animation on table cells indicates data is being fetched

Name Email Department Status
Loading... Loading... Loading... Loading...
Loading... Loading... Loading... Loading...
Loading... Loading... Loading... Loading...

Responsive Table

All tables are responsive by default. JS auto-wraps every nds-table in an nds-table-wrapper with horizontal scroll on overflow. Add nds-mask to opt into gradient fade masks on the overflow edges. Use --max-width to constrain wrapper width and --min-width to lock the table's minimum width. If --min-width is not set, the JS auto-calculates it from the table's natural content width so cells never shrink.

Employee ID Full Name Email Address Department Position Start Date Status
EMP-001 Ahmed Al-Rashidi ahmed.rashidi@gov.sa Engineering Senior Developer 2023-01-15 Active
EMP-002 Fatima Al-Harbi fatima.harbi@gov.sa Design UX Designer 2023-02-15 Active
EMP-003 Sara Al-Dosari sara.dosari@gov.sa Marketing Marketing Lead 2023-03-15 Active
EMP-004 Layla Al-Qahtani layla.qahtani@gov.sa Human Resources HR Specialist 2023-04-15 Active

Table with Pagination

Large datasets can be paginated using the data-auto-pagination attribute. Add nds-page-item class to each <tr> in <tbody> and set --per-page on the content wrapper.

Built-in Features

Auto-initialization

Every .nds-table on the page is automatically wrapped in a responsive scroll container. Opt into gradient fade masks with nds-mask.

Column Sorting

Columns cycle through ascending, descending, and original order. Numbers, dates, and text are detected and sorted appropriately.

Row Selection

Header checkbox toggles all rows with indeterminate state support. Selected rows receive a distinct background highlight that persists across striped rows.

Scroll Awareness

Add nds-mask to fade the overflow edges with a gradient that updates as the user scrolls to indicate more content in either direction. The mask clips descendants to the wrapper, so avoid it on tables with dropmenus, tooltips, or other overflowing popovers.

Keyboard Accessible

Sort headers are focusable buttons that respond to Enter and Space. Interactive elements within cells receive visible focus rings.

Striped Rows

Alternating row backgrounds and hover highlighting are applied automatically for easier scanning of large datasets.

Loading State

Add data-state="loading" to show a shimmer animation across all cells while data is being fetched.

Programmatic Control

Access sort state, reset sorting, and reinitialize tables after dynamic content changes through the NDS.Tables namespace.

Usage Guidelines

Best Practices

  • Use tables for structured, comparable data where users need to scan across rows and columns. For simple key-value pairs, use a Definition List instead
  • Do not use tables for page layout or displaying Cards in a grid. Use the Grid layout for that
  • Choose compact tables for dense administrative data (logs, inventories, audit trails) and standard tables when rows contain rich content like tags, avatars, or action buttons
  • Enable sorting only on columns with meaningful sort order. Status columns with tags are poor candidates for sorting
  • Add row selection when the interface supports bulk operations (delete, export, assign). Pair the table with an action bar that appears when rows are selected
  • Set --max-width when placing a table in a narrow container or side panel to trigger the responsive scroll wrapper early
  • Use pagination for datasets over 15-20 rows. Showing too many rows slows rendering and makes scanning harder
  • Keep header labels short and descriptive. Avoid abbreviations that require explanation
  • Place the most important identifier column (name, ID, title) first. Put action buttons or status indicators in the last column
  • Add a <caption> element for screen readers when the table's purpose is not clear from surrounding headings

Modifier Classes

ClassDescription
nds-compactReduces row height to 48px. Override with --table-row-height for custom values
nds-maskApplies gradient fade masks on the overflow edges when the table scrolls horizontally. Off by default. Note: mask clips descendants to the wrapper and breaks overflowing UI like dropmenus, tooltips, and popovers that escape table bounds
nds-sortableEnables column sorting. Use nds-col-header with nds-sort-btn nds-icon-only inside sortable <th> elements
nds-centerCenter-aligns all cell content across the table
nds-col-headerFlex container inside <th> that holds the label and actions side by side
nds-col-actionsContainer for action buttons (sort, filter, etc.) inside a column header
nds-sort-btn nds-icon-onlySort button class inside column headers that triggers column sorting
nds-page-itemApplied to <tr> elements for client-side pagination (used with nds-paged-content)
table-actionsFlex container for grouping action buttons within a cell
actions-columnShrinks column to fit content width, preventing unnecessary whitespace
checkbox-columnFallback for browsers without :has() support. Apply to <th> and <td> containing checkboxes to fix column width

Data Attributes

AttributeDescription
data-state="sorted-asc"Set on <th> to mark the initial sort column as ascending
data-state="sorted-desc"Set on <th> to mark the initial sort column as descending
data-state="selected"Set on <tr> to visually highlight a selected row. JS toggles this automatically when checkboxes change
data-state="loading"Set on <table> to show the loading shimmer animation
data-auto-paginationSet on <nav class="nds-pagination"> to enable auto-pagination for the preceding nds-paged-content wrapper

CSS Custom Properties

PropertyDefaultDescription
--table-row-height64px (48px in compact)Row height for data cells. Set on the table or use nds-compact for the 48px preset
--max-width100%Maximum width of the responsive scroll wrapper
--min-widthauto-calculatedMinimum width of the table inside the wrapper. Prevents cells from shrinking below content width
--mask-fade-distance48pxWidth of the gradient fade mask on scroll edges
--per-page10Number of rows shown per page when using pagination (set on nds-paged-content)

JavaScript API

The NDS.Tables API provides methods to initialize, sort, and manage table instances. All tables auto-initialize on page load. Call NDS.Tables.reinit() after dynamically adding new tables to the DOM.

// ── Namespace methods ──────────────────────────────── NDS.Tables.init(); // Initialize all tables on the page NDS.Tables.reinit(); // Re-initialize after dynamic content changes NDS.Tables.recheckWidths(); // Recheck responsive wrappers (e.g. after resize) // ── Manual creation ────────────────────────────────── // Create a sortable/selectable controller for a specific table const table = document.querySelector('#myTable'); const instance = NDS.Tables.create(table); // Create a responsive wrapper for a specific table const responsive = NDS.Tables.createResponsive(table); // ── Instance methods (sortable tables) ─────────────── instance.getSortColumn(); // Returns current sort column index (-1 if none) instance.getSortDirection(); // Returns 'asc', 'desc', or null instance.resetSort(); // Clear sorting, restore original row order instance.destroy(); // Remove all event listeners and clean up // ── Instance methods (responsive wrapper) ──────────── responsive.recheckWidth(); // Recheck if scroll is needed responsive.destroy(); // Remove wrapper event listeners // ── Custom events ──────────────────────────────────── // Fires when a column is sorted table.addEventListener('nds:table:sort', (e) => { e.detail.columnIndex; // Sorted column index e.detail.direction; // 'asc', 'desc', or null (reset) e.detail.table; // The <table> element e.detail.button; // The sort button clicked }); // Fires when row selection changes table.addEventListener('nds:table:selection', (e) => { e.detail.selectedCount; // Number of selected rows e.detail.totalCount; // Total number of selectable rows e.detail.selectedRows; // Array of selected <tr> elements e.detail.selectedIndexes; // Array of selected row indexes e.detail.table; // The <table> element });
Was this page useful?
60% of users said Yes from 2843 Feedbacks