Data display
Cards, tables, tags, and data presentation components
Components for displaying data in cards, tables, badges, tags, and other visual formats.
Prerequisites
DataTable
Full-featured data table built on TanStack Table with sorting, pagination, and row selection.
Import
import { DataTable } from '@tetherto/mdk-react-devkit/core'
// Types for column definitions
import type {
DataTableColumnDef,
DataTableSortingState,
DataTablePaginationState,
DataTableRowSelectionState,
} from '@tetherto/mdk-react-devkit/core'Basic usage
import { DataTable } from '@tetherto/mdk-react-devkit/core'
import type { DataTableColumnDef } from '@tetherto/mdk-react-devkit/core'
type Miner = {
id: string
name: string
hashrate: number
status: string
}
const columns: DataTableColumnDef<Miner>[] = [
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'hashrate',
header: 'Hashrate',
cell: ({ row }) => `${row.original.hashrate} TH/s`,
},
{
accessorKey: 'status',
header: 'Status',
},
]
function MinersTable() {
return <DataTable columns={columns} data={miners} />
}With sorting and pagination
const [sorting, setSorting] = useState<DataTableSortingState>([])
const [pagination, setPagination] = useState<DataTablePaginationState>({
pageIndex: 0,
pageSize: 10,
})
<DataTable
columns={columns}
data={miners}
sorting={sorting}
onSortingChange={setSorting}
pagination={pagination}
onPaginationChange={setPagination}
/>With row selection
const [rowSelection, setRowSelection] = useState<DataTableRowSelectionState>({})
<DataTable
columns={columns}
data={miners}
rowSelection={rowSelection}
onRowSelectionChange={setRowSelection}
enableRowSelection
/>Styling
.mdk-data-table: Root container.mdk-data-table__header: Header row.mdk-data-table__body: Table body.mdk-data-table__row: Data row.mdk-data-table__cell: Table cell
Card
Flexible container component with optional header, body, and footer slots.
Import
import { Card, CardHeader, CardBody, CardFooter } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
className | Optional | string | none | Additional CSS class |
onClick | Optional | function | none | Click handler (adds clickable styling) |
children | Optional | ReactNode | none | Card content |
Basic usage
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content goes here</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>Default children are automatically wrapped in the body slot:
<Card>
<Card.Header>Title</Card.Header>
This content goes to the body automatically
</Card>Clickable card
<Card onClick={() => navigate('/details')}>
<Card.Header>Click me</Card.Header>
<Card.Body>Navigates on click</Card.Body>
</Card>Styling
.mdk-card: Root container.mdk-card--clickable: Applied whenonClickis provided.mdk-card__header: Header slot.mdk-card__body: Body slot.mdk-card__footer: Footer slot
LabeledCard
Card variant that pairs a tiny label above the body - used for compact stat or metadata blocks. Supports an optional navigation link on the label and a set of layout modifiers.
A generic card container with a header label, an optional navigation link on the label, and a set of layout modifiers. Useful for compact stat or metadata blocks.
Import
import { LabeledCard } from '@tetherto/mdk-react-devkit/core'Props
All props are optional.
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
label | Optional | ReactNode | none | Header content shown above the card body |
children | Optional | ReactNode | none | Card body content |
getNavigateOptions | Optional | (label: string) => { href?: string; target?: string } | none | Returns a link href/target for the label; only activates when label is a plain string |
isDark | Optional | boolean | false | Applies a dark background modifier |
isFullWidth | Optional | boolean | false | Stretches the card to full container width |
isFullHeight | Optional | boolean | false | Stretches the card to full container height |
isRelative | Optional | boolean | false | Sets position: relative on the container |
isScrollable | Optional | boolean | false | Enables vertical scroll on the card body |
hasNoWrap | Optional | boolean | false | Prevents content from wrapping |
hasNoMargin | Optional | boolean | false | Removes default margin |
hasNoBorder | Optional | boolean | false | Removes the card border |
className | Optional | string | none | Additional class for the root element |
Basic usage
<LabeledCard label="Device Overview" isFullWidth>
<p>Content goes here.</p>
</LabeledCard>With a navigation link on the label
<LabeledCard
label="Miners with error"
getNavigateOptions={(label) => ({ href: '/miners?filter=error' })}
>
<MinerList />
</LabeledCard>When label is the string 'Miners with error', an informational tooltip is added automatically, explaining that minor errors not affecting hashrate are excluded.
Accordion
Collapsible content sections built on Radix UI primitives.
Import
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from '@tetherto/mdk-react-devkit/core'Accordion props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
title | Optional | string | '' | Accordion header title |
isOpened | Optional | boolean | false | Initially expanded |
isRow | Optional | boolean | false | Row layout for content |
unpadded | Optional | boolean | false | Remove content padding |
noBorder | Optional | boolean | false | Remove trigger border |
solidBackground | Optional | boolean | false | Solid background color |
showToggleIcon | Optional | boolean | true | Show expand/collapse icon |
toggleIconPosition | Optional | 'left' | 'right' | 'left' | Icon position |
customLabel | Optional | ReactNode | none | Custom label next to title |
onValueChange | Optional | function | none | Callback when state changes |
Basic usage
<Accordion title="FAQ Section">
<p>This content can be expanded or collapsed.</p>
</Accordion>With custom label
<Accordion
title="System Status"
customLabel={<Badge variant="success">Active</Badge>}
showToggleIcon={false}
>
<p>All systems operational.</p>
</Accordion>Multiple items
<AccordionRoot type="multiple">
<AccordionItem value="item-1">
<AccordionTrigger>Section 1</AccordionTrigger>
<AccordionContent>Content 1</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger>Section 2</AccordionTrigger>
<AccordionContent>Content 2</AccordionContent>
</AccordionItem>
</AccordionRoot>Styling
.mdk-accordion: Root container.mdk-accordion--solid-background: Solid background variant.mdk-accordion__item: Individual item.mdk-accordion__trigger: Clickable header.mdk-accordion__content: Collapsible content area
Badge
Numeric or status badge that can wrap content or stand alone.
Import
import { Badge } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
count | Optional | number | 0 | Number to display |
overflowCount | Optional | number | 99 | Max count before showing "99+" |
showZero | Optional | boolean | false | Show badge when count is 0 |
dot | Optional | boolean | false | Show as dot instead of number |
text | Optional | string | none | Custom text content |
color | Optional | ColorVariant | 'primary' | Badge color |
size | Optional | 'sm' | 'md' | 'lg' | 'md' | Badge size |
status | Optional | 'success' | 'processing' | 'error' | 'warning' | 'default' | none | Status variant |
square | Optional | boolean | false | Square badge (no border-radius) |
offset | Optional | [number, number] | [0, 0] | Position offset [x, y] |
Basic usage
// Number badge on content
<Badge count={5}>
<Button>Messages</Button>
</Badge>
// Overflow
<Badge count={100} overflowCount={99}>
<BellIcon />
</Badge>
// Shows "99+"Dot badge
<Badge dot>
<NotificationIcon />
</Badge>Status badge
<Badge status="success" text="Online" />
<Badge status="error" text="Offline" />
<Badge status="processing" text="Syncing" />
<Badge status="warning" text="Warning" />Standalone badge
<Badge count={25} />
<Badge text="NEW" color="primary" square />Styling
.mdk-badge: Badge element.mdk-badge--{color}: Color variant.mdk-badge--{size}: Size variant.mdk-badge--dot: Dot variant.mdk-badge--status: Status variant.mdk-badge-wrapper: Wrapper when wrapping content
Pagination
Page navigation with size selector.
Import
import { Pagination } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
current | Optional | number | 1 | Current page number |
total | Optional | number | 0 | Total number of items |
pageSize | Optional | number | 20 | Items per page |
pageSizeOptions | Optional | number[] | [10, 20, 50, 100] | Page size options |
showSizeChanger | Optional | boolean | true | Show page size dropdown |
showTotal | Optional | boolean | false | Show total count text |
disabled | Optional | boolean | false | Disable pagination |
size | Optional | 'sm' | 'md' | 'lg' | 'sm' | Size variant |
onChange | Optional | (page: number, pageSize: number) => void | none | Callback when the page number or page size changes |
onSizeChange | Optional | (current: number, size: number) => void | none | Callback when the page size changes |
Basic usage
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
<Pagination
current={page}
total={500}
pageSize={pageSize}
onChange={(newPage, newSize) => {
setPage(newPage)
setPageSize(newSize)
}}
/>With total display
<Pagination
current={1}
total={100}
pageSize={20}
showTotal
/>
// Shows "1-20 of 100"Styling
.mdk-pagination: Root container.mdk-pagination__pages: Page buttons container.mdk-pagination__button: Navigation button.mdk-pagination__button--active: Active page.mdk-pagination__total: Total count text.mdk-pagination__size-changer: Page size select
Tag
Removable tag/chip component.
Import
import { Tag } from '@tetherto/mdk-react-devkit/core'Basic usage
<Tag>Default Tag</Tag>
<Tag color="primary">Primary</Tag>
<Tag color="success">Success</Tag>
<Tag onClose={() => handleRemove()}>Removable</Tag>Avatar
User avatar display component with fallback initials.
Import
import { Avatar } from '@tetherto/mdk-react-devkit/core'Basic usage
<Avatar src="/user.jpg" alt="User" />
<Avatar>JD</Avatar>
<Avatar src="/user.jpg" size="lg" />SkeletonBlock
Loading placeholder animation.
Import
import { SkeletonBlock } from '@tetherto/mdk-react-devkit/core'Basic usage
// Text skeleton
<SkeletonBlock className="h-4 w-48" />
// Circle skeleton
<SkeletonBlock className="h-12 w-12 rounded-full" />
// Card skeleton
<Card>
<Card.Body>
<SkeletonBlock className="h-4 w-full mb-2" />
<SkeletonBlock className="h-4 w-3/4" />
</Card.Body>
</Card>EmptyState
Placeholder component for empty data states.
Import
import { EmptyState } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
description | Optional | ReactNode | none | Required. Description text |
image | Optional | 'default' | 'simple' | ReactNode | 'default' | Image/icon to display |
size | Optional | 'sm' | 'md' | 'lg' | 'md' | Size variant |
Basic usage
<EmptyState description="No data available" />
<EmptyState
description="No miners found matching your search"
image="simple"
size="sm"
/>Custom image
<EmptyState
description="No alerts at this time"
image={<BellOffIcon size={48} />}
/>Styling
.mdk-empty-state: Root container.mdk-empty-state--{size}: Size variant.mdk-empty-state__image: Image container.mdk-empty-state__description: Description text
BtcAveragePrice
Displays a formatted BTC/USD price with a configurable label. Shows a dash when the value is absent, non-finite, or negative.
Import
import { BtcAveragePrice } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
price | Optional | number | null | none | BTC price in USD; formatted with grouping and no decimal places. Shows - when null, undefined, non-finite, or negative |
label | Optional | string | 'BTC Average Price' | Label shown before the price |
Basic usage
<BtcAveragePrice price={97_500} />
<BtcAveragePrice price={null} />Typography
Text styling components for headings and paragraphs.
Import
import { Typography } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
variant | Optional | 'heading1' | 'heading2' | 'heading3' | 'body' | 'secondary' | 'caption' | none | Typography variant |
size | Optional | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | none | Text size |
weight | Optional | 'light' | 'normal' | 'medium' | 'semibold' | 'bold' | none | Font weight |
align | Optional | TextAlign | none | Text alignment |
color | Optional | 'default' | 'muted' | 'primary' | 'success' | 'warning' | 'error' | none | Text color variant |
truncate | Optional | boolean | false | Truncate text with ellipsis |
className | Optional | string | none | Custom class name |
Basic usage
<Typography variant="heading1">Operations dashboard</Typography>
<Typography variant="heading2">Sites</Typography>
<Typography variant="body">
Primary site operations are running within expected parameters.
</Typography>
<Typography variant="secondary">Last refreshed 12 seconds ago.</Typography>
<Typography variant="caption" color="muted">Updated 2 minutes ago</Typography>
<Typography weight="semibold" align="center" truncate>Site #1</Typography>Indicator
Status dot for online/offline/warning states.
Import
import { Indicator } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
color | Optional | 'gray' | 'slate' | 'red' | 'amber' | 'yellow' | 'green' | 'blue' | 'purple' | none | Color variant of the indicator dot |
size | Optional | 'sm' | 'md' | 'lg' | none | Size variant of the indicator |
vertical | Optional | boolean | false | When true, adds extra spacing between child elements and stacks them vertically |
onClick | Optional | function | none | Click handler |
children | Optional | ReactNode | none | Label content (text, icons, or multiple elements) |
className | Optional | string | none | Custom class name |
Basic usage
<Indicator color="green">Online</Indicator>
<Indicator color="red">Offline</Indicator>
<Indicator color="amber">Maintenance</Indicator>
<Indicator color="green" size="sm">Running</Indicator>
<Indicator color="green" vertical>
<span>Running</span>
<span>142</span>
</Indicator>CurrencyToggler
Toggle between different currency display formats.
Import
import { CurrencyToggler } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
currencies | Required | (string | CurrencyItem)[] | none | Currencies to toggle between, as codes or CurrencyItem objects |
value | Required | string | none | Currently selected currency code |
onChange | Required | (currency: string) => void | none | Called with the newly selected currency code |
className | Optional | string | none | Custom class name for the root element |
Basic usage
<CurrencyToggler
value={currency}
onChange={setCurrency}
currencies={['USD', 'BTC', 'SAT']}
/>ListViewFilter
Filter controls for list and table views with search and category filtering.
Import
import { ListViewFilter } from '@tetherto/mdk-react-devkit/core'
import type { CascaderValue, LocalFilters } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
options | Required | CascaderOption[] | none | Cascader options for filtering |
onChange | Required | (selections: CascaderValue[]) => void | none | Callback when filters change |
localFilters | Optional | LocalFilters | none | Current filter values as key-value pairs (e.g. { status: ['active'] }) |
filterKey | Optional | string | none | Key to force re-mounting the Cascader when filters change (resets its internal state) |
className | Optional | string | none | Custom class name for the filter button |
Basic usage
const FILTER_OPTIONS = [
{
value: 'type',
label: 'Type',
children: [
{ value: 'Antminer S19XP', label: 'Antminer S19XP' },
{ value: 'Avalon A1346', label: 'Avalon A1346' },
],
},
{
value: 'status',
label: 'Status',
children: [
{ value: 'active', label: 'Active' },
{ value: 'offline', label: 'Offline' },
],
},
]
const [filters, setFilters] = useState<LocalFilters>({})
<ListViewFilter
options={FILTER_OPTIONS}
localFilters={filters}
onChange={(selections) => setFilters(selectionToFilters(selections))}
/>Mosaic
Grid layout component for dashboard widgets and responsive content arrangements.
Import
import { Mosaic, MosaicItem } from '@tetherto/mdk-react-devkit/core'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
template | Required | string[] | string[][] | none | Grid layout template — area names per row that map child MosaicItems to grid regions |
children | Required | ReactNode | none | MosaicItem elements placed into the named grid areas |
columns | Optional | string | string[] | none | Column track sizes (e.g. '1fr 2fr 1fr'); defaults to equal columns derived from the template |
gap | Optional | string | '12px' | Gap between grid cells (any CSS length) |
rowHeight | Optional | string | 'auto' | Height applied to each grid row (any CSS length) |
className | Optional | string | none | Custom class name for the root element |
Basic usage
<Mosaic columns={3} gap={16}>
<MosaicItem>
<Card>Hashrate</Card>
</MosaicItem>
<MosaicItem span={2}>
<Card>Active miners</Card>
</MosaicItem>
<MosaicItem>
<Card>Power usage</Card>
</MosaicItem>
</Mosaic>