Back to projects

Software · In progress

mk-ui: Element Collection

My own element collection. 23 live components built on eight tokens and one accent, shipped as a single stylesheet you can link with no build step. Retrace is its first real consumer.

Year
2026
Role
Solo build
Stack
CSS, JavaScript, React, Node

What it is

A collection of the interface elements I keep rebuilding, in one place, live and interactive. Twenty three of them so far, each with its own page showing its variants, its sizes, where it gets used, and the motion behind it.

Browse it at ui.michaelkujr.me.

The constraint that shapes everything

Eight colour values and one accent. That is the whole palette, and every element is built from it.

Working inside that limit is what forces the interesting decisions, because you cannot solve a problem by adding a colour. When state has to be legible and you have one accent, a status indicator becomes a dot plus a word rather than a red dot, which is also the version that works for someone who cannot distinguish the colours. The constraint and the accessible answer turn out to be the same answer.

Changing the accent changes the whole system at once. On Retrace’s review tool, that means the accent controls what a cleared insurance claim looks like.

Motion is the character

The house style is that nothing sits raised while it is idle. A card rests flat inside a hairline and earns its shadow on hover. A button lifts two pixels and its trailing arrow walks three. There is no glow, no fill flash, no shadow at rest.

That rule produces a useful test for whether an interaction is honest. A card that lifts on hover but is not clickable is telling the user something untrue, so the lift is opt-in through a modifier rather than the default.

The same thinking runs through the animated pieces. A meter grows to its value instead of appearing at it, which costs one frame and makes the number feel measured rather than asserted, and that matters most when the number is a model’s confidence score. A toast’s exit is deliberately shorter than its entrance: arriving should announce itself, leaving should not.

Build on the platform where the platform is better

Several elements are thin layers over native browser features, because the native version is genuinely better and the reimplementation is where accessibility goes to die.

The modal is built on the <dialog> element, which supplies the focus trap, the inert background, and Escape-to-close for free. The only behaviour added on top is closing on a backdrop click, because the backdrop is a pseudo-element and a click there actually lands on the dialog itself. The slider is a restyled native range input, so keyboard, touch, and screen readers all work without a line of JavaScript.

The drawer is the modal’s classes plus one. It is the same thing arriving from a different edge, and treating it as a separate component would have been the mistake.

How it is built

The whole thing is a registry. registry.json describes every element, and the build generates one real page per element from it, so each has a proper URL rather than a client-side route or a query string. The stylesheet layers concatenate into a single dist/mk-ui.css, 36 kB, which is committed so you can link it straight from a Jinja template or a static report with no build step at all.

There are zero dependencies. The build script imports Node builtins and nothing else, which is the point of shipping a stylesheet rather than a framework. An optional React layer wraps the classes for projects that want components.

CI fails if the committed element pages drift from the registry. Because the generated pages are committed on purpose, editing the registry and forgetting to rebuild would deploy a site quietly missing the new element, and nothing else would catch it.

Why it exists

I kept rewriting the same button. More usefully, having a collection meant that when Retrace needed a review interface, styling it was a matter of using classes that already existed. Twenty five of them, with no literal colour, font, or spacing value left in the page.

That is the real test of a design system and it is the reason this is not just a gallery: something else depends on it, so the abstractions had to survive contact with a problem they were not designed for.

What is still open

There are no tests, which is the honest gap. ideas.json tracks about sixty elements across twelve categories against the twenty three built, so the roadmap is longer than the collection. And the React layer is thinner than the CSS layer, since most consumers so far have wanted the stylesheet.