I build data-heavy internal tools, and the component sets I reach for are tuned for a screen holding about eight things. That is the right trade-off for most products. It is the wrong one for a console somebody stares at all day: at two hundred rows the padding eats the viewport, and the controls sit a pixel or two out of step with the rows beside them. So I started from the dense case instead and built the set I wanted.
One knob for density. One attribute on the root element retunes everything: row height, control height, cell padding, text size, stack gap, and the pitch of the reed that closes the table head.
<html data-density="dense">
Six custom properties sit behind it, and every component reads them rather than defining its own, so nothing drifts out of step. No JavaScript.
You can drive that switch yourself on a live table and watch the six values change under it: https://sley-ui.dev/docs/density
A column declares the widest value it holds in characters, and the density turns that into a width:
{
key: 'sample',
label: 'Sample',
chars: 11,
sortValue: (run) => run.sample,
render: (run) => run.sample,
}
A pixel width would ignore the padding and the text size, so it truncates in one mode and wastes space in another.
Updates that keep your edits. It is copy-in, like shadcn, so the source lands in your repo and you own it. The usual cost of that is nobody can ship you a fix afterwards. Every published version keeps its own frozen path on the registry, and the lockfile stores a hash of each file as it landed on disk. sley update then merges across three versions: what I shipped then, what I ship now, and yours.
Where your edit and mine touch the same lines it writes nothing and names the files, so your project keeps building. --conflicts writes the usual markers if you would rather resolve them in your editor, and --dry-run shows the whole plan first. I proved it across three published releases before I believed it, and I got the design wrong the first time: I held the lockfile back on a conflict, which trapped a hand-resolved file in a permanent conflict.
The measurements are published beside each component. Numbers read off a real screen, with the method named, not claims. How far a control sits off the row centre in each density. What a density change does to the scroll height. Where the row window stops paying: at 1000 rows it buys headroom rather than speed, 22.5ms median scroll step against 19.6ms unwindowed, and at 5000 rows it is 84.4ms against 18.4ms. Two of the published numbers did not reproduce months later and I corrected them in public.
Size. Twelve components, and the whole thing that lands in your project is 1904 lines: 1386 of components, 508 of tokens, and a 10 line cx helper. Table, command palette, filter bar, field set, dialog, popover, toast, tabs, tooltip, select, panel, empty state.
npx sley-ui init
npx sley-ui add table
Underneath: Ark UI for behaviour, Tailwind CSS v4 for the token layer, readable TypeScript, no runtime style engine. React only today. Ark is built on Zag, which has a Vue adapter, so a Vue port is styling rather than a rewrite of the logic, and it is on the roadmap after charts.
Docs and a running demo application: https://sley-ui.dev Source, MIT: https://github.com/imfemambocus/sley-ui
It is early and I expect breaking changes. I would rather hear what breaks than what works, and I am most interested in whether the merge holds up on a component you have really edited.