Dawson Goodell logo

Maelstrom-Futurism

Learnings and Explanation


Maelstrom-Futurism is a React component design system intended for the rapid creation of modern web applications.

The internet is absolutely loaded with component libraries and styling systems, so why build my own? The primary issue with all of those component libraries is very simple - they are not my component library.

There is something to be said for doing something just because you can or just because you want to. Maelstrom-Futurism is one of those projects. I've used Chakra UI, Cloudscape, and MUI, and they were all fine.

I've also made my own component libraries in a professional capacity. While working at AWS, I led a redesign project for a training tool that required the creation of a new component library that would be shared between 4–6 teams. The component library is still in use at AWS, but there were several decisions I wanted to explore.

Decisions I wanted to revisit

Styling: Emotion vs. CSS modules

While I like the flexibility of Emotion / CSS-in-JS for creating shared themes, it requires project changes for use in Next.js and other frameworks. Initially I set out to use only SASS and CSS modules for styling. However, dynamic styling adjustments proved just too useful and too tempting. As a result, MF uses Emotion for styling.

When I eventually tackle this problem again, I intend to make the base packages, core and layout, with just CSS modules while keeping Emotion where it's truly required (like in button, which has some fancier effects).

Monorepo management

Because my first component system was built at Amazon, it favored a multiple package approach with custom scripts to tie it all together. For MF, I initially chose Lerna to manage a monorepo. It worked fine, but added a layer of complexity to every build. npm's native workspaces feature provides the same functionality with no additional overhead. MF now uses workspaces instead of Lerna.

Opinionated vs. overridable

It is very hard to make a component that covers all possibilities. You have to either set an opinionated style and a single "golden path" or you have to create some kind of utility system that allows overriding component behavior. I initially wanted to build something that was extremely flexible, but as I designed with my actual use cases in mind, I found that I didn't want flexibility. In fact, I didn't want to think about options at all! What I wanted was just to drop the component into my project and build.

The final philosophy for MF ended up being "extremely opinionated" - with overrides available. MF components are built to look nice, have a few little flourishes (I particularly like the ripple effect on buttons), and be otherwise locked in.

Learnings

LLM driven design has some gotchas

While AI tools like Claude Design and Google Stitch can hammer out an entire design in no time at all, the final products lack some internal consistency, and keen eyes have no problem determining that the work is AI generated.

Some recurring issues that I encountered - even on Opus 5.5 - include:

  • Inconsistent font sizes - for instance, the same kind of hint text between two different components might be generated as 11px or 12px.
  • Unnecessary color changes - as the design is generated across components, the palette tends to grow.
  • Excessive overrides and specificity - the CSS generated in these design tools is quite a bit heavier than what a person would write. It often reapplies styles already set by parent elements or as browser defaults.

Most of these problems are handleable via judicious review of generated code, but that's a lot of extra work for a developer. The better fix is to get your hands dirty and do the initial setup by hand. For example, use Claude or Stitch or whatever tool to generate some appearance files, then grab tokens from the design and add them to a token file. Build some base styles. Use design.md (or the appropriate file for your harness) to enforce rules around using those tokens for styling.

Finally, I wrote some skill files that scan for styles that are nearly identical or very long.

Tests are a life-saver

Tests are an important part of any programming project, but they look very different in a component library when compared to a distributed system.

The components here should be minimally intertwined, which ensures that changes in one component don't send ripples through the library. Since MF was a low-stakes personal project, I initially skipped writing any tests. However, regressions did eventually become a problem and snapshot tests were added to components that broke.

Most of the library is still testless and relies on good rules around logic isolation and human review to keep regressions out of the codebase.

<< Return to Home