Table Molecules
Available via @stackmates/ui-interactive/molecules
ColumnHeader
Sortable column header with CVA alignment variants and optional inline filter. Renders a <th> with SortIndicator atom.
Click “Name” to cycle sort direction.
Name | Email | Amount |
|---|---|---|
| Jane Doe | jane@co.com | $1,200 |
Sort: false
ColumnHeaderProps
| Prop | Type | Default | Description |
|---|---|---|---|
| title* | string | — | Column title text |
| sortable | boolean | false | Enable sorting |
| sortDirection | 'asc' | 'desc' | false | false | Current sort state |
| onSort | () => void | — | Sort trigger callback |
| filterable | boolean | false | Show inline filter input |
| align | 'left' | 'center' | 'right' | 'left' | Text alignment |
Installation
import { ColumnHeader } from '@stackmates/ui-interactive/molecules';Accessibility
- Sets aria-sort when sorted
- Adds role="button" and tabIndex when sortable
- Keyboard activation via Enter and Space
ColumnFilter
Minimal inline filter input for a table column. Stops click propagation to avoid triggering column sort.
Value: (empty)
ColumnFilterProps
| Prop | Type | Default | Description |
|---|---|---|---|
| title* | string | — | Filter label |
| value | string | '' | Current value |
| onChange* | (value: string) => void | — | Change handler |
| placeholder | string | — | Input placeholder |
Installation
import { ColumnFilter } from '@stackmates/ui-interactive/molecules';Accessibility
- Click propagation stopped to prevent sort interference
- Native text input with full keyboard support
RowActions
Dropdown menu triggered by a vertical ellipsis button. Generic over row data type TData. Supports icons, destructive styling, and separators.
RowActionsProps
| Prop | Type | Default | Description |
|---|---|---|---|
| row* | TData | — | Row data passed to action handlers |
| actions* | RowAction<TData>[] | — | Actions with label, onClick, icon?, destructive?, separatorBefore? |
| triggerVariant | 'ghost' | 'outline' | 'ghost' | Trigger button style |
Installation
import { RowActions } from '@stackmates/ui-interactive/molecules';
import type { RowAction } from '@stackmates/ui-interactive/molecules';Accessibility
- Trigger has aria-label="Row actions"
- Stop-propagation prevents row selection
- Built on Radix DropdownMenu with keyboard nav
TablePagination
Pagination controls with first/prev/next/last buttons, page size selector, and summary text showing item range or selection count.
TablePaginationProps
| Prop | Type | Default | Description |
|---|---|---|---|
| pageIndex* | number | — | Current page (0-based) |
| pageSize* | number | — | Items per page |
| pageCount* | number | — | Total pages |
| onPageChange* | (pageIndex: number) => void | — | Page change handler |
| totalItems | number | — | Total count for summary |
| onPageSizeChange | (size: number) => void | — | Page size change handler |
Installation
import { TablePagination } from '@stackmates/ui-interactive/molecules';Accessibility
- Each button has descriptive aria-label
- Buttons disabled at boundaries
- Summary provides range context
SearchFilterBar
Debounced search input + dropdown filters (status, priority, type) + date range + action buttons. The primary toolbar for table search and filtering.
State: {"query":""}
SearchFilterBarProps
| Prop | Type | Default | Description |
|---|---|---|---|
| filters* | SearchFilters | — | Current filter state: { query, status?, priority?, type? } |
| onFiltersChange* | (filters) => void | — | Filter change callback |
| config | FilterConfig | — | statusOptions, priorityOptions, typeOptions, searchPlaceholder |
| primaryActions | ActionButton[] | — | Buttons on the right of search row |
| debounceMs | number | 300 | Search debounce delay |
Installation
import { SearchFilterBar } from '@stackmates/ui-interactive/molecules';
import type { FilterConfig, SearchFilters } from '@stackmates/ui-interactive/molecules';Accessibility
- Search debounced to reduce screen reader noise
- Filter dropdowns are keyboard navigable
- Clear all button resets all filters
BulkActionsToolbar
Multi-select action bar for bulk delete (with confirmation), status/priority updates, and custom actions. Hides when selectedCount is 0.
BulkActionsToolbarProps
| Prop | Type | Default | Description |
|---|---|---|---|
| selectedCount* | number | — | Number of selected items |
| onClearSelection* | () => void | — | Deselect all handler |
| onBulkDelete | () => Promise<void> | — | Delete with confirmation dialog |
| onBulkStatusUpdate | (status: string) => Promise<void> | — | Status update dropdown |
| config | BulkActionsConfig | — | entityLabel, showDelete, statusOptions, priorityOptions |
Installation
import { BulkActionsToolbar } from '@stackmates/ui-interactive/molecules';Accessibility
- Clear selection has aria-label
- Delete shows confirmation dialog
- Spinner announces processing state
TableToolbar
Unified toolbar composing SearchFilterBar and BulkActionsToolbar. Switches mode automatically: search when nothing selected, bulk actions when selectedCount > 0.
TableToolbarProps
| Prop | Type | Default | Description |
|---|---|---|---|
| filters* | SearchFilters | — | Filter state for SearchFilterBar |
| onFiltersChange* | (filters) => void | — | Filter change handler |
| selectedCount | number | 0 | When > 0, switches to bulk actions mode |
| onClearSelection | () => void | — | Enables bulk actions mode |
| filterConfig | FilterConfig | — | SearchFilterBar configuration |
| bulkActionsConfig | BulkActionsConfig | — | BulkActionsToolbar configuration |
Installation
import { TableToolbar } from '@stackmates/ui-interactive/molecules';Accessibility
- Mode switch preserves focus context
- Inherits a11y from both sub-toolbars
DataImportExport
Import/export dropdown with file upload, format selection (CSV, JSON, XLSX), optional drag-and-drop, template download, and result dialog.
DataImportExportProps
| Prop | Type | Default | Description |
|---|---|---|---|
| onExport* | (format: 'csv'|'json'|'xlsx') => void | — | Export handler with format |
| onImport* | (file: File) => Promise<ImportResult> | — | Import handler |
| exportCount | number | — | Items available for export |
| config | DataImportExportConfig | — | entityLabel, acceptedFileTypes, maxFileSizeMB, exportFormats |
| onDownloadTemplate | () => void | — | Shows template download option |
Installation
import { DataImportExport } from '@stackmates/ui-interactive/molecules';Accessibility
- Hidden file input has aria-label
- Spinner provides loading feedback
- Result dialog announces import outcome
ExportMenu
Export format selection with dropdown or inline button variants. Shows format icons, labels, and optional descriptions. Supports disabled and loading states.
Dropdown variant
Inline variant
Last exported: (none)
ExportMenuProps
| Prop | Type | Default | Description |
|---|---|---|---|
| onExport* | (format: string) => void | — | Callback when an export format is selected |
| formats* | ExportFormat[] | — | Available formats with key, label, optional icon and description |
| variant | 'dropdown' | 'inline' | 'dropdown' | Display as dropdown menu or inline buttons |
| buttonLabel | string | 'Export' | Custom label for the dropdown trigger button |
| disabled | boolean | false | Disable all export actions |
| loading | boolean | false | Show loading state |
Installation
import { ExportMenu } from '@stackmates/ui-interactive/molecules';
import type { ExportFormat } from '@stackmates/ui-interactive/molecules';Usage
Report download menu
<ExportMenu
onExport={(format) => downloadReport(format)}
formats={[
{ key: 'pdf', label: 'PDF', description: 'Best for printing' },
{ key: 'csv', label: 'CSV', description: 'For data analysis' },
]}
buttonLabel="Download Report"
/>Accessibility
- Built on Radix DropdownMenu with full keyboard navigation
- data-testid="export-menu" on the trigger for test targeting
- Disabled state prevents interaction and visually mutes the button
Accordion
Expandable disclosure sections built on Radix Accordion. Supports single or multiple open items with animated expand/collapse transitions.
Accordion / AccordionItem / AccordionTrigger / AccordionContent
| Prop | Type | Default | Description |
|---|---|---|---|
| type* | 'single' | 'multiple' | — | Single or multiple items open at a time |
| value | string | string[] | — | Controlled open item(s) |
| defaultValue | string | string[] | — | Initially open item(s) (uncontrolled) |
| collapsible | boolean | false | Allow closing all items (type="single" only) |
Installation
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@stackmates/ui-interactive/molecules';Usage
FAQ section
<Accordion type="single" collapsible>
<AccordionItem value="q1">
<AccordionTrigger>Question one?</AccordionTrigger>
<AccordionContent>Answer one.</AccordionContent>
</AccordionItem>
</Accordion>Accessibility
- Built on Radix Accordion with WAI-ARIA disclosure pattern
- Trigger has aria-expanded and aria-controls
- Content uses aria-hidden when closed
- Keyboard navigation: Space/Enter to toggle, Arrow keys between items
Carousel
Content carousel powered by Embla Carousel. Supports looping, horizontal/vertical orientation, keyboard navigation, and previous/next controls.
CarouselProps
| Prop | Type | Default | Description |
|---|---|---|---|
| orientation | 'horizontal' | 'vertical' | 'horizontal' | Scroll direction |
| opts | CarouselOptions | — | Embla options (loop, align, etc.) |
| plugins | CarouselPlugin | — | Embla plugins (autoplay, etc.) |
| setApi | (api: CarouselApi) => void | — | Receive Embla API for programmatic control |
Installation
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from '@stackmates/ui-interactive/molecules';Accessibility
- role="region" with aria-roledescription="carousel"
- Slides have role="group" + aria-roledescription="slide"
- Arrow keys scroll, nav buttons have sr-only labels
Resizable
Split-panel layout with draggable handles built on react-resizable-panels. Supports horizontal and vertical directions with minimum size constraints.
ResizablePanelGroup / ResizablePanel / ResizableHandle
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | 'horizontal' | 'vertical' | 'horizontal' | Split direction |
| defaultSize | number | — | Panel default size as percentage (ResizablePanel) |
| minSize | number | — | Panel minimum size as percentage (ResizablePanel) |
| withHandle | boolean | false | Show grip icon (ResizableHandle) |
Installation
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@stackmates/ui-interactive/molecules';Accessibility
- Handle is focusable and keyboard-operable
- withHandle adds visible grip for discoverability
- Arrow keys resize panels when handle is focused