Data table
A table with sorting, selection, pagination, server data, tree rows, virtual rows and row reorder.
| Due | ||
|---|---|---|
| Ship v2 | Lena | Oct 3 |
| Fix export | Omar | Oct 6 |
| Audit forms | Sofia | Oct 9 |
| Plan Q4 | Yuki | Oct 14 |
Every part is a slot; what you don't replace still looks finished.
npm i slotsmithpnpm add slotsmithyarn add slotsmithbun add slotsmith| 1042 | Lena Park | 3 | Shipped |
| 1043 | Omar Haddad | 1 | Packing |
| 1044 | Sofia Reyes | 5 | Shipped |
| 1045 | Yuki Tanaka | 2 | Paid |
| 1046 | Amir Nasser | 4 | Packing |
The same table in a few well-known design systems; yours works the same way. Only the components prop changes.
| Lena Park | Engineer | Platform | Active | 2021-03-08 | |
| Omar Haddad | Designer | Product | Active | 2020-11-16 | |
| Sofia Reyes | Engineer | Payments | Away | 2022-01-24 | |
| Yuki Tanaka | Manager | Platform | Active | 2019-06-03 | |
| Amir Nasser | Analyst | Growth | Active | 2023-02-13 |
landing/swap-fallback.tsx
import { DataTable } from "slotsmith/data-table";
import { columns, people } from "../shared/people";
export default function Swap() {
return (
<DataTable
data={people}
columns={columns}
enableRowSelection
defaultPagination={{ pageIndex: 0, pageSize: 5 }}
/>
);
}import { DataTable } from "slotsmith/data-table";
import { shadcnComponents } from "../adapters/data-table/shadcn"; changed line
import { columns, people } from "../shared/people";
export default function Swap() {
return (
<DataTable
data={people}
columns={columns}
enableRowSelection
defaultPagination={{ pageIndex: 0, pageSize: 5 }}
components={shadcnComponents} changed line
/>
);
}import { DataTable } from "slotsmith/data-table";
import { muiComponents } from "../adapters/data-table/mui"; changed line
import { columns, people } from "../shared/people";
export default function Swap() {
return (
<DataTable
data={people}
columns={columns}
enableRowSelection
defaultPagination={{ pageIndex: 0, pageSize: 5 }}
components={muiComponents} changed line
/>
);
}import { DataTable } from "slotsmith/data-table";
import { chakraComponents } from "../adapters/data-table/chakra"; changed line
import { columns, people } from "../shared/people";
export default function Swap() {
return (
<DataTable
data={people}
columns={columns}
enableRowSelection
defaultPagination={{ pageIndex: 0, pageSize: 5 }}
components={chakraComponents} changed line
/>
);
}import { DataTable } from "slotsmith/data-table";
import { antdComponents } from "../adapters/data-table/antd"; changed line
import { columns, people } from "../shared/people";
export default function Swap() {
return (
<DataTable
data={people}
columns={columns}
enableRowSelection
defaultPagination={{ pageIndex: 0, pageSize: 5 }}
components={antdComponents} changed line
/>
);
}<element> parts take DOM props; {widget} parts take semantic props.Every part of every component is one of two kinds. The kind tells you what your replacement receives.
<Row>Element partsAn element part renders one DOM element. It gets plain DOM props, with state as data-* attributes, so a library's primitive drops in as it is.
import TableRow from "@mui/material/TableRow";
import { DataTable } from "slotsmith/data-table";
import { columns, people } from "../shared/people";
// Row is an element part: TableRow takes the <tr> props as is.
export default function ElementPart() {
return (
<DataTable
data={people}
columns={columns}
components={{ Row: TableRow }}
/>
);
}| Kind | Receives | Drop-in for |
|---|---|---|
Element parts<Root><Table><Row><Cell> | Plain DOM props, with state as data-* attributes | Any library's primitives, unchanged: shadcn/ui, MUI or Chakra, for example |
Widget parts{Checkbox}{Pagination}{Empty} | Semantic props (checked, pageIndex, setPageSize) | Your components, through a few-line adapter |
Each panel is the component itself, with its built-in parts. Open one for its guides and API.
A table with sorting, selection, pagination, server data, tree rows, virtual rows and row reorder.
| Due | ||
|---|---|---|
| Ship v2 | Lena | Oct 3 |
| Fix export | Omar | Oct 6 |
| Audit forms | Sofia | Oct 9 |
| Plan Q4 | Yuki | Oct 14 |
Combobox and select in one component.
A file picker with uploads, tiles, validation, record editing and virtual lists.
A date picker for single days, ranges and multiple dates, with bounds, blocked days and presets.
Keep your design system, whichever it is, and your data layer. Here are a few well-known libraries; each opens the page that shows it working.
Every label comes from a locale pack. Switch the language: the labels change, and the table mirrors for right-to-left.
| Lena Park | Berlin | Paid |
| Omar Haddad | Amman | Shipped |
| Sofia Reyes | Madrid | Packing |
| Yuki Tanaka | Osaka | Paid |
| Amir Nasser | Tehran | Shipped |
import { useState } from "react";
import { DataTable, type DataTableColumnDef } from "slotsmith/data-table";
import { SlotsmithProvider, useSlotsmithLocale } from "slotsmith/provider";
import { ar } from "slotsmith/locales/ar";
import { fa } from "slotsmith/locales/fa";
type Order = { id: string; customer: string; city: string; status: string };
const LANGUAGES = [
{ locale: "en-US", lang: "en", name: "English" },
{ locale: "ar", lang: "ar", name: "العربية" },
{ locale: "fa", lang: "fa", name: "فارسی" },
] as const;
type Locale = (typeof LANGUAGES)[number]["locale"];
const packs = [ar, fa];
/** The toggle's name, in the page's language. */
const GROUP_LABEL: Record<string, string> = { en: "Language", ar: "اللغة", fa: "زبان" };
const copy: Record<Locale, { headers: Record<"customer" | "city" | "status", string>; rows: Order[] }> = {
"en-US": {
headers: { customer: "Customer", city: "City", status: "Status" },
rows: [
{ id: "1", customer: "Lena Park", city: "Berlin", status: "Paid" },
{ id: "2", customer: "Omar Haddad", city: "Amman", status: "Shipped" },
{ id: "3", customer: "Sofia Reyes", city: "Madrid", status: "Packing" },
{ id: "4", customer: "Yuki Tanaka", city: "Osaka", status: "Paid" },
{ id: "5", customer: "Amir Nasser", city: "Tehran", status: "Shipped" },
{ id: "6", customer: "Nadia Karimi", city: "Dubai", status: "Packing" },
],
},
ar: {
headers: { customer: "العميل", city: "المدينة", status: "الحالة" },
rows: [
{ id: "1", customer: "لينا بارك", city: "برلين", status: "مدفوع" },
{ id: "2", customer: "عمر حداد", city: "عمّان", status: "تم الشحن" },
{ id: "3", customer: "صوفيا رييس", city: "مدريد", status: "قيد التجهيز" },
{ id: "4", customer: "يوكي تاناكا", city: "أوساكا", status: "مدفوع" },
{ id: "5", customer: "أمير ناصر", city: "طهران", status: "تم الشحن" },
{ id: "6", customer: "نادية كريمي", city: "دبي", status: "قيد التجهيز" },
],
},
fa: {
headers: { customer: "مشتری", city: "شهر", status: "وضعیت" },
rows: [
{ id: "1", customer: "لنا پارک", city: "برلین", status: "پرداختشده" },
{ id: "2", customer: "عمر حداد", city: "امان", status: "ارسالشده" },
{ id: "3", customer: "سوفیا رییس", city: "مادرید", status: "در حال بستهبندی" },
{ id: "4", customer: "یوکی تاناکا", city: "اوساکا", status: "پرداختشده" },
{ id: "5", customer: "امیر ناصر", city: "تهران", status: "ارسالشده" },
{ id: "6", customer: "نادیا کریمی", city: "دبی", status: "در حال بستهبندی" },
],
},
};
/** The table in the provider's language, inside an element that takes its direction. */
function Orders({ locale }: { locale: Locale }) {
const { dir } = useSlotsmithLocale();
const { headers, rows } = copy[locale];
const columns: DataTableColumnDef<Order>[] = [
{ accessorKey: "customer", header: headers.customer },
{ accessorKey: "city", header: headers.city },
{ accessorKey: "status", header: headers.status },
];
return (
<div dir={dir} lang={locale}>
<DataTable data={rows} columns={columns} defaultPagination={{ pageIndex: 0, pageSize: 5 }} pageSizeOptions={[5, 10]} />
</div>
);
}
export default function Languages() {
// Starts in the page's language when an outer provider sets one.
const page = useSlotsmithLocale().code;
const label = GROUP_LABEL[page.split("-")[0]!] ?? "Language";
const [locale, setLocale] = useState<Locale>(page.startsWith("ar") ? "ar" : page.startsWith("fa") ? "fa" : "en-US");
return (
<div className="languages-demo">
<div role="group" aria-label={label}>
{LANGUAGES.map((language) => (
<button
key={language.locale}
type="button"
lang={language.lang}
aria-pressed={locale === language.locale}
onClick={() => setLocale(language.locale)}
>
{language.name}
</button>
))}
</div>
<SlotsmithProvider locale={locale} locales={packs}>
<Orders locale={locale} />
</SlotsmithProvider>
</div>
);
}slotsmith-ai is an MCP server. It gives your coding agent every prop, slot and label, and copies ready-made adapters into your project. Claude Code adds it with one command; Cursor reads .cursor/mcp.json; VS Code reads .vscode/mcp.json.
claude mcp add slotsmith -- npx -y slotsmith-ai mcp{
"mcpServers": {
"slotsmith": {
"command": "npx",
"args": ["-y", "slotsmith-ai", "mcp"]
}
}
}{
"servers": {
"slotsmith": {
"type": "stdio",
"command": "npx",
"args": ["-y", "slotsmith-ai", "mcp"]
}
}
}{ tests: 1761, suites: 20, gzip: "38.5 KB", license: "ISC", react: ">=18" }
Yes. Every entry point starts with "use client", so you can import a component straight into a Server Component file. It renders on the server and hydrates on the client.
No. Each component ships a plain CSS stylesheet, themed with CSS custom properties. Tailwind is only needed if your own design system uses it.
Yes. Every part is a slot: pass your design system's button, checkbox or select and it replaces the fallback. The Adapters pages show shadcn/ui, MUI, Chakra, Ant Design and Radix Themes as examples.
Each component has its own entry point. The data table entry is 38.5 KB gzipped, not counting React and TanStack Table, which it imports; slotsmith has no dependencies of its own. The Trust page lists every entry.
Yes. The peer dependency range is react >=18, so React 18 and React 19 both work.
ISC. You can use slotsmith in commercial and closed-source projects.
npm i slotsmith