Skip to content
← Case studies

Design system — building 100+ components in Storybook on React Aria and moving every Doccle app onto them

I built the component system for Doccle's Next.js and TypeScript apps on React Aria Components, a headless library that handles behaviour and accessibility and leaves the design to us. I wrote most of its 100+ components, including every complex one, so teams now assemble features from ready parts and ship them faster.

I chose that path for the business: rather than write our own library, I took a core the community already tests and maintains. As the library's owner, I then led every app onto it, which by my estimate cut duplicated UI code three to five times.

Context

Doccle, a document platform used in Belgium by 2.5 million people, was moving its apps to Next.js and TypeScript while building new features on them, and those features had to ship fast. Since every one of them needed buttons, forms and dialogs, our components set the pace for the whole team.

From 2022 to 2026 I owned the component library. I made its technical decisions, set how its components are written and reviewed every change to it, alongside my own feature work with our designer, our QA engineers and the frontend team. The library is written in React and TypeScript on React Aria Components, with Storybook for its docs and CSS variables for its themes.

Problem

The old pattern library had been written by backend developers on old approaches. Over the years its components had become outdated, unsupported and full of custom logic that was hard to maintain.

Yet every new feature still had to be built on it. Each time we extended a component or added one, we wrote keyboard, focus and browser behaviour by hand and then fixed whatever broke, so keeping it alive was not realistic.

I first tried to modernise it from the inside, with a new date picker built on a popular plugin. It broke server-side rendering in our apps, which showed me that patching would not be enough: we needed a new foundation.

Meanwhile, the cost kept growing. Features took longer, every app rebuilt the same components in its own slightly different way, and accessibility had to be added by hand to every component.

Objectives

For the business, the goal was faster features, not a component project for its own sake. We needed one set of accessible components that every app reuses and designers can see, built on a foundation we didn't have to maintain ourselves.

We also had to move to it without pausing feature work. We would know it worked when teams built features from ready components and the old library left the apps.

Research

Before choosing a foundation, I needed to know what it would have to carry. Going through our components and the new designs, I found that buttons and text were simple, while date pickers, selects, menus and dialogs held the real cost. They came in many variants, with complex logic for when to open and close and how they work with each other, and on top of that came keyboard, focus and screen-reader behaviour.

With that picture, I weighed the effort, the risk and the responsibility each path would put on us. I wrote the comparison up, so the team could follow the reasoning and not just the conclusion.

A comparison on paper wasn't enough, so before committing I tested the choice on our hardest component, the date picker. The working prototype showed that the library could handle our most complex case, and that its ready components saved far more work than building from its low-level parts. That prototype became my main argument when I brought the choice to the team.

Options

That work left three real ways to go, each with its cost:

  • A styled library such as MUI or Chakra. Many ready components, but built around their own design, so ours would have to bend to theirs and we would spend our time overriding their styles.
  • Our own components. All the logic would be on us — cross-browser behaviour, responsive layouts, different languages — which means a lot of testing, bugs and custom logic to maintain, exactly as the old library had shown, and our time spent on components instead of features.
  • A headless library: Radix, Headless UI or React Aria Components. Behaviour and accessibility without styles, with a core built and maintained by a community across many projects. The catch: plenty of our own work would still sit on top, and the most complete of them was still in alpha.

The first would have cost us our design and the second our time, while only the third let us keep both.

Design

I chose a headless library, and of the three I chose React Aria Components, built by Adobe, because it had the most components and a large community that contributes to it regularly. The hardest part of every component was therefore written and fixed by people who work on exactly that.

That didn't make the work easy, though. It moved the work to where it was worth something to us: our components, our design and the logic our product needs.

Taking the alpha risk

The frontend developers doubted the choice because the library was still in alpha, so I showed them the working prototype and the comparison, and took the risk on myself.

I pinned the library's version, so that no update could break us by surprise. When the date picker failed in some mobile browsers, I shipped a temporary workaround and removed it once the library fixed the bug. I also did every upgrade myself, from alpha to a stable release, adapting our styles whenever the library changed.

Building on top of React Aria

On that foundation I built most of the components anew, including every complex one. I styled and combined the library's parts and wrote our own logic where our product needed it, such as a money field or a date picker that joins a calendar with a typed date. I also built our own components around those parts; those in turn became the base for the next ones, so a single shared popup now serves menus, selects and date pickers.

I also made it possible to open a dialog, a notification or a loading indicator from anywhere in the app with one call, and nested dialogs close in the right order. Grouping the components by what they do gave designers and developers one map of the library.

Moving every app

I planned the move so that feature work never stopped. Every component got a status — modernise, replace or delete — and old and new versions ran side by side, so each app could move one screen at a time. Every breaking change went out in its own release.

As code reviewer, I held each change to the conventions, and I presented the system at internal dev talks. When we later moved into a monorepo together, I led moving the components there and replacing the old ones in every app, and I set up publishing Storybook to Chromatic.

Design tokens and white label

Doccle also embeds a widget on partner companies' sites, where it has to look like the partner rather than like Doccle. For that, I built the design tokens from a few base values — colours, fonts and the base font size — and calculated every other colour and size from them.

A partner overrides a handful of values, and every component follows, in light and dark themes. With theme presets in Storybook, we could see any component in a partner's colours before it shipped.

Results

The design system now has more than 100 components, documented in Storybook, where designers and developers see the same thing. Developers assemble features from ready, tested components, so features come together faster, and because every app uses the same components, a fix in one reaches all of them.

The forms work with a keyboard and a screen reader, and the same components carry partners' brands. By my estimate, duplicated UI code shrank three to five times, and feature work never paused for the move.

React Crash CourseNew to React? My course takes you from zero to your first app, step by step

Looking back

It was worth it, and starting in alpha was not a mistake. The headless library saved us a great deal of time: the community maintains its core and fixes its bugs, while we spent our time on the design, the components and the features the business needed.

Had I written our own library, we would still be fixing browser behaviour; with MUI or Chakra, we would be fighting their styles. The decisions held up as well: the groups I set became the libraries of the monorepo, and the tokens let the same components carry other brands.

What I'm looking for

A senior full-stack, senior frontend or frontend lead role in a product company or startup. I'm based in Wrocław, Poland (CET) and work remotely.

What people say

Thomas AnciauxLead Front-End Engineer at Doccle · LinkedIn

Dmytro has solid knowledge and extensive experience with TypeScript, React and Next.js, and a good grasp of building maintainable, well-structured interfaces.

He delivers clean code consistently, with a lot of attention to the details. Dmytro took ownership of his work, thought through edge cases, and could be counted on to follow through on what he shipped.

Wesley MostienLead Front-End + Node.js Developer, managed Dmytro directly · LinkedIn

I worked with Dmytro on the same project, where we collaborated closely on the frontend side. Dmytro is a highly skilled frontend developer with strong experience and a solid technical mindset.

He consistently delivered features with good quality, paid attention to clean architecture, and cared about maintainable code. He was also easy to collaborate with, communicated clearly, and contributed positively to the team.

Dmytro is just great to work with. Very knowledgeable and keeps working at his craft.

Alexander ShelestovEx CTO · LinkedIn

It was a hard time when we had to choose and implement new tools: JS framework, CSS classes naming conventions, project structure, testing, etc.

Dmytro started as an ordinary frontend developer and just after a few weeks led the whole process as a leader. Dmytro found a way to compare the different approaches and choose the right one for our project.

Etienne AdriaenssenEntrepreneur, Dmytro's client · LinkedIn

I worked with Dmytro on a healthcare startup called DasLab. He joined very early on and was a great asset in our growth. He showed a real expertise in Vue.js, large codebase management and engineering processes.

On the management side too he was fantastic, he helped me build a team in Ukraine and together we delivered over 1m Covid Tests.

Phil O'MalleyChief Technology Officer · LinkedIn

Dmytro is a strong UI JS Developer who has made valuable contributions to his Scrum since joining the Travelport Locomote Development group. He has been a strong proponent within the group for UI Framework improvements and refactoring and has worked closely with his team and with the UI Architect on this effort.