Getting started
هذه الصفحة غير مترجمة بعد، وهي معروضة بالإنجليزية.
Getting started with slotsmith takes one package and, if you want the default look, one stylesheet import. Then render a component: DataTable, Autocomplete, DatePicker or FileUploader. Themes, dark mode and languages are optional steps on top.
Install
npm i slotsmithpnpm add slotsmithyarn add slotsmithbun add slotsmithslotsmith needs React 18 or 19. Its TanStack peers are optional and per component, so you install only what the components you import need:
- The data table needs
@tanstack/react-table9. - Virtual rows, through the
virtualprop orVirtualDataTable, also need@tanstack/react-virtual3. - The autocomplete, the date picker and the file uploader need nothing more.
slotsmith itself has no dependencies: the popups are placed by its own positioner.
Styles
Every component renders plain HTML with stable class names, and the stylesheet that styles them is optional. Pick one of three:
- Everything. One import styles all four components.
- One component. Each component has its own sheet,
slotsmith/<component>.css. - None. When you replace every part with your design system’s own, import no stylesheet: the components never reference it.
// Once, at your app's entry: the default look of all four components.
import "slotsmith/styles.css";// Only the components you use. The table's sheet brings the autocomplete's rules
// with it, for its page-size menu.
import "slotsmith/data-table.css";
import "slotsmith/date-picker.css";Theming
The stylesheet reads a small set of shared --ss-* tokens, so a few values restyle all four components at once: set them on :root after the stylesheet. Or import one of the ready-made themes, which set the same tokens. Pick one here to see it on the components:
| Things Fall Apart | 1958 | |
| Season of Migration to the North | 1966 | |
| Beloved | 1987 |
Theming lists every token, each theme, the per-component tokens, Tailwind and styling without the stylesheet.
Dark mode
The stylesheet and every theme have a dark palette. It applies under .dark or [data-theme="dark"], on <html> or on any element above the components, so an app whose dark mode already sets one of those needs nothing more. The preview above follows this site’s theme switch the same way. Following the system setting instead is covered in Theming.
Languages
English is built in. For another language, import its pack and pass it as locale, or give a whole app one with SlotsmithProvider:
import type { ReactNode } from "react";
import { SlotsmithProvider } from "slotsmith/provider";
import { ar } from "slotsmith/locales/ar";
import { fr } from "slotsmith/locales/fr";
// Every slotsmith component inside reads its labels and number formats from
// the provider. English is the default, so it needs no pack.
export function Providers({ language, children }: { language: "en" | "ar" | "fr"; children: ReactNode }) {
return (
<SlotsmithProvider locale={language} locales={[ar, fr]}>
{children}
</SlotsmithProvider>
);
}Each pack is its own entry, so an app loads only the languages it imports. The components follow the CSS direction of the page around them, so a right-to-left language also needs dir="rtl" on <html> or a wrapper. Languages lists every pack and shows custom locales and one-off labels.
One adapter map for the whole app
SlotsmithProvider also takes components: one map per component (dataTable, autocomplete, datePicker, fileUploader), each the same map that component’s own components prop takes. Set an adapter there once and every component below renders with it:
import type { ReactNode } from "react";
import { SlotsmithProvider, type SlotsmithComponents } from "slotsmith/provider";
import { shadcnComponents as dataTable } from "../adapters/data-table/shadcn";
import { shadcnComponents as datePicker } from "../adapters/date-picker/shadcn";
// One map per component, each the same object that component's own
// `components` prop takes. Defined outside any component, so its identity
// never changes between renders.
const components: SlotsmithComponents = { dataTable, datePicker };
// Every data table and date picker inside renders with the adapter's parts.
export function Providers({ children }: { children: ReactNode }) {
return <SlotsmithProvider components={components}>{children}</SlotsmithProvider>;
}Each slot resolves in three layers, the later one winning: the built-in fallback, then the provider’s components, then the component’s own components prop. The same provider can carry locale and locales too. The parts are functions, so in the Next.js App Router the map belongs in the "use client" file that holds the provider. The slot model shows it live.
CommonJS
On ES modules, import { Autocomplete } from "slotsmith" already leaves the other components out of the bundle. CommonJS require() cannot drop anything, so require each component’s own entry:
// CommonJS: require each component's own entry.
const { Autocomplete } = require("slotsmith/autocomplete");
const { DatePicker } = require("slotsmith/date-picker");
const { FileUploader } = require("slotsmith/file-uploader");
const { SlotsmithProvider } = require("slotsmith/provider");
const { ar } = require("slotsmith/locales/ar");
// The table's entries, and the main entry "slotsmith" (it holds the table
// too), also require @tanstack/react-table.
const { DataTable } = require("slotsmith/data-table");slotsmith/autocomplete, slotsmith/date-picker, slotsmith/file-uploader, slotsmith/provider, slotsmith/locale and slotsmith/locales/<code> load without @tanstack/react-table installed. slotsmith/data-table and the main entry slotsmith need it, since the main entry holds the table too.
Next.js and Vite
Every slotsmith entry but the locale packs starts with "use client", so a server component can render a component directly. A provider holding state is a client component of your own, and the packs are imported there: a pack holds functions, which cannot be passed from a server component. In the Next.js App Router that is one file, providers.tsx:
"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
import { SlotsmithProvider } from "slotsmith/provider";
import { ar } from "slotsmith/locales/ar";
export type Language = "en" | "ar";
const LanguageContext = createContext<{ language: Language; toggle: () => void }>({ language: "en", toggle: () => {} });
/** The client boundary: it holds the language and gives every slotsmith component below it the matching pack. */
export function Providers({ initialLanguage, children }: { initialLanguage: Language; children: ReactNode }) {
const [language, setLanguage] = useState(initialLanguage);
const toggle = () => {
const next = language === "en" ? "ar" : "en";
setLanguage(next);
document.cookie = `lang=${next}; path=/; max-age=31536000`;
document.documentElement.lang = next;
document.documentElement.dir = next === "ar" ? "rtl" : "ltr";
};
return (
<LanguageContext value={{ language, toggle }}>
{/* English is the default, so it needs no pack. */}
<SlotsmithProvider locale={language === "ar" ? "ar" : undefined} locales={[ar]}>
{children}
</SlotsmithProvider>
</LanguageContext>
);
}
/** A button that switches between English and Arabic. */
export function LanguageToggle() {
const { language, toggle } = useContext(LanguageContext);
return (
<button type="button" onClick={toggle}>
{language === "en" ? "العربية" : "English"}
</button>
);
}The page stays a server component. Its columns are plain objects, so they can be passed to the table:
import { DataTable, type DataTableColumnDef } from "slotsmith/data-table";
import { LanguageToggle } from "./providers";
type Book = { title: string; author: string; year: number };
const books: Book[] = [
{ title: "Things Fall Apart", author: "Chinua Achebe", year: 1958 },
{ title: "Season of Migration to the North", author: "Tayeb Salih", year: 1966 },
{ title: "Beloved", author: "Toni Morrison", year: 1987 },
];
// Plain objects, so this server component can pass them to the table.
const columns: DataTableColumnDef<Book>[] = [
{ accessorKey: "title", header: "Title" },
{ accessorKey: "author", header: "Author" },
{ accessorKey: "year", header: "Year" },
];
export default function Page() {
return (
<main>
<LanguageToggle />
<DataTable data={books} columns={columns} />
</main>
);
}With Vite there is no server boundary: the provider and the table sit in one component, and the stylesheet is imported in main.tsx.
import { useEffect, useState } from "react";
import { DataTable, type DataTableColumnDef } from "slotsmith/data-table";
import { SlotsmithProvider } from "slotsmith/provider";
import { ar } from "slotsmith/locales/ar";
type Book = { title: string; author: string; year: number };
const books: Book[] = [
{ title: "Things Fall Apart", author: "Chinua Achebe", year: 1958 },
{ title: "Season of Migration to the North", author: "Tayeb Salih", year: 1966 },
{ title: "Beloved", author: "Toni Morrison", year: 1987 },
];
const columns: DataTableColumnDef<Book>[] = [
{ accessorKey: "title", header: "Title" },
{ accessorKey: "author", header: "Author" },
{ accessorKey: "year", header: "Year" },
];
export function App() {
const [language, setLanguage] = useState<"en" | "ar">("en");
// The page's lang and dir follow the language, so the table and the rest of the page mirror together.
useEffect(() => {
document.documentElement.lang = language;
document.documentElement.dir = language === "ar" ? "rtl" : "ltr";
}, [language]);
return (
// English is the default, so it needs no pack.
<SlotsmithProvider locale={language === "ar" ? "ar" : undefined} locales={[ar]}>
<button type="button" onClick={() => setLanguage(language === "en" ? "ar" : "en")}>
{language === "en" ? "العربية" : "English"}
</button>
<DataTable data={books} columns={columns} />
</SlotsmithProvider>
);
}Both examples are small complete apps in the repository, under docs/samples/frameworks/.