كل المنشورات / React وNext.js / №08
بلا تصميم أم بتصميم جاهز؟ لماذا بنيت مكتبة React كل جزء فيها قابل للاستبدال
المكتبات ذات التصميم الجاهز تقاوم التصاميم الخاصة، والمكتبات بلا تصميم تجعلك تعيد كتابة الشيفرة نفسها في كل مشروع. كيف تقف slotsmith بين الاثنين، فلكل جزء قابل للاستبدال بديل جاهز يراعي إمكانية الوصول، وما الذي كان صعباً في بنائها.
هذه مكوّنات slotsmith الحقيقية تعمل أمامك، وليست صورة. فعّل وضع الأشعة لترى الأجزاء القابلة للاستبدال.
| Pagination bar | Yousef | منجز | |
| Skeleton state | Noor | مراجعة | |
| Error state | Yousef | منجز | |
| Empty state | Lina | منجز | |
| Sticky header | Omar | مراجعة |
هذا الجزء مبني بمكوّن DataTable من مكتبتي slotsmith
هذا المقال مكتوب بالإنجليزية، وملخصه بالعربية في الأعلى.
Headless libraries had me rewriting the same table markup on every project, and styled libraries fought my designs. slotsmith is my attempt at a middle path.
Every React developer eventually hits the same wall with UI components.
At that point you usually have two options, and both hurt.
Option 1: a styled library
MUI, Ant Design, Chakra. You install it, drop in a <Table>, and it works in five minutes.
Then the designer sends the real mockup. The header needs a different border, the rows need a hover state that doesn’t match the library’s, and the empty state has to look like your product. Now you’re overriding CSS selectors you didn’t write, against a DOM structure you don’t control. You’re fighting the library instead of using it.
Option 2: a headless library
TanStack Table, for example. It gives you the logic (sorting, filtering, pagination) and no markup at all. You get total control.
You also have to write everything: the <table>, every <th>, the sort button and its icon, the aria-sort attribute, the empty state, the loading state, keyboard focus. Then on the next project you write it all again.
I wanted something in between: the control of headless, without starting from zero.
The idea: every part is a slot, and every slot has a finished fallback
So I built slotsmith, a headless React and TypeScript component library with one rule:
Every part of a component is a slot you can replace. Everything you don’t replace is finished, accessible HTML.
A data table, for example, is split into named parts such as Table, HeaderCell, Row and Cell. If you pass your own part, slotsmith uses it. If you don’t, a complete default renders.
You only replace what your design needs, and nothing else.
What it looks like
With no slots at all, you get a working, accessible table:
import { DataTable } from 'slotsmith/data-table'
<DataTable data={projects} columns={columns} />When the design needs it, you replace just the parts you care about. Slots take plain DOM props, so styling them is just your own CSS (Tailwind here) on native tags:
const slots = {
Table: (props: ComponentProps<'table'>) => (
<table {...props} className="w-full border-collapse text-start" />
),
Row: (props: ComponentProps<'tr'>) => (
<tr
{...props}
className={cn('cursor-pointer hover:bg-neutral-100', props.className)}
/>
)
}
<DataTable data={projects} columns={columns} slots={slots} />Everything I didn’t replace, including the header cells, sorting behaviour and accessibility attributes, keeps working.
This is the table that runs the projects page on my own portfolio. It’s a slotsmith DataTable wearing my own parts.
The hard parts
The idea is simple. Making it hold up was not.
1. Slots that feel native
If a slot has a custom prop API, every replacement turns into a translation exercise. So slot props are typed as the native element’s props: a Row slot receives what a <tr> receives. You spread {...props} and add your styles, and TypeScript checks it all.
The catch is that your replacement must pass the props through. If you drop {...props}, you also drop the event handlers and ARIA attributes the library relies on. That’s the one rule users have to learn, so the docs say it loudly.
2. Theming four components at once
slotsmith has four components today: a data table, an autocomplete, a file uploader and a date picker. Each one had its own CSS variables, and a site that wanted to re-theme them had to set each set separately.
In 1.8, I added a shared token layer. Every component reads its own variables first and falls back to a common --ss-* layer, so one set of tokens re-themes everything:
body,
body :is(.sac, .sfu, .sdt, .sdp) {
--ss-surface: var(--panel);
--ss-text: var(--ink);
}There’s a small trick in that selector. slotsmith sets its defaults on :root and on the component class. body is a closer ancestor than :root, and body .sdt has higher specificity than .sdt, so the site’s tokens always win without a single !important.
3. Renaming a public API without breaking people
Early on, the data table used .rdt class names and --rdt-* variables, and it was published as its own package, @slotsmith/table. As the library grew, that naming stopped making sense.
So in 1.8:
.rdt/--rdt-*became.sdt/--sdt-*, matching the other components@slotsmith/tablewas deprecated in favour of the singleslotsmithpackage, with each component as its own entry point (slotsmith/data-table, and so on)
The lesson: CSS class names and variables are public API just as much as props are. Treat a rename like any breaking change.
4. Accessibility in the defaults, not the docs
The whole promise is that the parts you don’t replace are finished. That means accessibility can’t be a docs page telling you what to add. It has to be built into the fallbacks:
- The date picker grid moves with the arrow keys
- The autocomplete lets you remove a selected tag with Backspace
- The data table keeps its sorting state readable by screen readers
When you should not use slotsmith
If a styled library’s look already matches your design, use it. It’s faster.
If you want to own every element and you enjoy building from zero, a pure headless library is the better fit.
slotsmith is for the middle: you have a real design to match, and you’d rather replace three parts than write twenty-one.
Try it
npm i slotsmith- Docs: slotsmith.dev
- GitHub: tmzm/slotsmith
- npm: slotsmith
It’s open source (ISC licence) and works with React 18 and 19. There’s also slotsmith-ai, a companion MCP server so coding agents can use the library correctly.
What’s the component you’ve rebuilt the most times? I’d like to hear which one should come to slotsmith next.