Introducing rbx: React, Bulma, 👟

By Devin Fee

rbx is a new UI framework for React built on top of the Bulma CSS flexbox framework. It’s massively powerful, unassumingly light (<10kb), and endlessly extensible. The gold standard for a UI Framework is one provides not too little and not too much. I think rbx nails that. 🏆

To get started quickly, read the beautiful docs, or npm i rbx.

It’s core feature is the as prop – allowing any component to render as any other component. Think of it as syntactic sugar for higher-order-components.

Every component also supports ref forwarding. Therefore, you can build upon the awesome power of other packages in your React ecosystem.

Major features:

  • comprehensive Bulma implementation
  • written in TypeScript
  • endlessly extensible
  • minimal, yet feature rich
  • unopinionated (plays well with other packages)

Give it a whirl

A note on composition…

It’s expected that you’ll end up finding these components to be semantic (e.g. Navbar.Burger is a … burger for the Navbar), but generally un-stitched. For example, it takes a few more lines to compose a fully featured Navbar than in other UI Frameworks, but this is a feature not a bug (hint: read on, but this buys users a lot of power).

The reasoning behind this is that rbx was designed to give you and your opinions preference when building. In the language of Atomic Design, you could consider this to be a collection of atoms and molecules. You don’t need to stitch your own layouts together to get started, but when you get down to it, you’ll find that you’re naturally composing re-usable patterns with this framework.

When I set out to build rbx, I’d already written the TypeScript typings for react-bulma-components, but was unsatisfied with them. I found many bugs and errors that existed solely because that framework wasn’t typed. In my perspective, the only way to build a first class, modern UI Framework for JavaScript in 2018/2019 is with TypeScript.

I also wanted the package’s users to have incredible documentation, and tests that are informative to use.

In short, I hard forked react-bulma-components and promptly re-wrote the package (completely, at least four times!) in an effort to slim it down to the core essence of what I believe a UI Framework ought to be — a cohesive set of simple components that can (and should) be a great starting point for users.

Having used other UI Frameworks like ant-design, react-bootstrap, and react-semantic-ui, I was weary of opinionated design architectures. For example, while antd’s form controls are nice, an integration with a best-of-breed form library like Formik becomes difficult. These UI Frameworks are clingy and controlling. Practically speaking, that makes users’ relationships with them a bit tedious.

The first goal was to build a UI Framework that was neither too-little nor too-much.

Perhaps this is because I’m not a native of the JavaScript world, but an immigrant to it, that I see comprehensive, switch-army knife solutions as a red flag. They’re fast at getting you 80% of the way there, but immensely troublesome when you need to finish your work. You end up either becoming a zealot of the package architect’s world-view on everything from design to form implementation to icons, or figure out how to strip away enough of the bloat to be productive again.

rbx, then, comes with no icons(!), and only a few components have state. Even those components with state can be easily controlled (via their managed prop) and can instead directed by external state management tools, like redux or mobx (or anything else).

The second goal was to use a very strict configuration to force clean code.

When I seriously began with JavaScript, I found myself a victim of foolish mistakes. So I decided I to get some help from tooling. The UI Framework isn’t opinionated, but I’ve come to rely heavily on third party tools to keep the code on the rails.

To stay in the lane and create a stable package for users this package utilizes an opinionated code formatter (prettier), a robust test suite (jest and enzyme), and a very strict TSLint config that prevents simple, subtle, and yet consequential mistakes.

The third goal was to build on documentation and examples as first-class citizens of the package – just like tests.

Simply put, tests exist alongside the code (in __tests__ folders), docs exist alongside the code (in __docs__ folders), and examples are located at the root in the examples directory.

Upon each build by the continuous integration server (TravisCI), documentation must compile and the examples must pass their tests, too. This is so that the examples and documentation remain up-to-date.

The process from a build perspective is straight-forward, but perhaps untraditional:

  1. run unit tests (tests in **/__tests__/*.test.tsx?)
  2. build a distribution (to dist)
  3. test the examples (in examples) against the new build
  4. build the docs into a production build

Any of those steps can fail, and if they do, it will fail the entire build.

The fourth (and final) goal was to make rbx simple and customizable.

Bulma is already comprehensive. If you want to customize it via SASS, you can. rbx will support you – and help you maintain great typing support in TypeScript and regular JS (via PropTypes).

For example, if you want to introduce a new color to the palette, it’s about 10 lines of JavaScript (and another 10 in TypeScript if you’re using that language). Check the examples folder for with-customization.

This also works for adding sizings to individual components, introducing new breakpoints, and much much more.

In my 90 days of building rbx, much was challenging. But, by far, the hardest part was getting packages to play nicely with themselves and each other.

Here are a few examples:

  • Babel 7 had trouble properly parsing normal TypeScript syntax that was key to my package until 7.2.0. Even then, I was forced to enable --isolatedModules to get other tooling to work (i.e. no re-exporting of types). 🧐
  • VSCode wouldn’t show all my TSLint errors until I abandoned the VSCode TypeScript extension in favor of Microsoft’s typescript-tslint-plugin. 👀
  • Greenkeeper likes to update package-lock.json files, but unfortunately it’s difficult to identify errors when builds are successful, but the underlying tools fail in development. 🙄
  • NPM Scripts are great for simple tasks, but become very difficult to manage for complex builds (and Gulp ended up being too much of a hassle…back to Makefiles). 😐
  • Rollup is great for building a UMD distribution, but I found that using it to build ESM code from TypeScript code was hassle than it was worth (unless you want everything to be a top-level export). 🙁
  • TypeScript’s path mapping feature is awesome, but if you compile your code it won’t convert those paths during transpilation. In short, if you’re building a library, you can’t rely on that feature and you should just use relative paths for everything in. 🤮
  • Inferring types and documentation with react-docgen-typescript might work for simple use-cases. But in reality, it falls over pretty quick. I ended up just building my own PropsTable for documentation.
  • Bulma is great 99.9% of the time, but even it has problems – like using non-standard HTML attributes (which React ultimately strips away).

The key to building rbx with TypeScript was diving in head-first and learning to swim along the way. At a high-level, TypeScript bills itself as an incrementally-adoptable solution, but in practice it feels like an all-or-nothing language.

As you grow in TypeScript and add knowledge and tools to your toolbelt, you’ll be reluctant to pick up too much, too quickly. However most features of the TypeScript toolbox are actually critical – and bugs spring up when you cheat (for example, using any as a type). You’ll quickly realize that any benefit or gain when using TypeScript is rapidly diminished by untyped or partially untyped code.

In building a publicly-consumable UI Framework, every error, warning, and intermittent issue that I put off ended up coming back to haunt me as I attempted to release rbx. And there were many. Ultimately, these were bugs that needed to be fixed, but it often required significant re-factoring and re-implementation to figure out what was wrong. And occasionaly, use of the great git bisect.

Having a good community is critical to building a good piece of software, and I’m especially thankful to the help of the #typescript community on Reactiflux (especially Jessica – thank you).

The architecture paid off, and I’m really happy with rbx and in particular it’s core: the ForwardRefAsExotic. I’ve built a package in a style that ought to be the standard for how UI Frameworks for React are created – minimal, unopinionated, yet exhaustively extendable.

I admit, the success of this package depends on the willingness of users to consume a UI Framework that has a narrow scope — they’ll need to bring their own favorite packages (like Formik) and icons (like Font Awesome). For better or worse, this probably means that I’ll leave some beginners behind and appeal to the more intermediate and advanced crowds. However, I believe this is actually a great UI Framework for beginners too. The examples in the root of the repo show just how simple and powerful this framework really is.

I’d love for your help in contributions, and I welcome all support. Thanks!