Principles for writing great shareable React components

Howdy! It’s been a minute. Things have been fun and busy with my day job, which has cut into my blogging.

I’ve been working in a design house at Microsoft supporting designers and engineers across a couple dozen major enterprise apps. Our team’s job is to make it easy for full stack engineers to implement the visual designs accurately. We do this through a library of shared React components focused solely on presentational logic known as ‘admin-controls.’ Since the immediate UXE team including myself is just 3 full time people, we have banked on an open-inner source model. The ‘enablement’ culture is strong at Microsoft, so maintaining and contributing to a shared library is a great way to do it.

Turns out building shareable code is a different mindset than shipping end user features. We’ve had to coach a lot of new contributors along the best path to keep the quality bar high. We eventually nailed down some principles that we can point new contributors to before they get started and then refer to in pull requests.

I thought it might be worth sharing those principles with a broader audience, because… well, I’m kind of excited about the work we do.

They are intentionally a little vague on specifics since there are many patterns that ladder up to each principle. [I’ve also added a little context and color for the broader audience.]


1)      Focus on presentation

Our components are focused on enabling a prescribed user experience. Our components don't concern themselves with data transactions or manipulations, just the visual presentation, animations, accessibility and the DOM.

[Stay in your lane bro! This keeps us focus on our core mission, leverage our biggest strength, and not get in the way of other mechanical aspects of an app.]

2)      Minimize assumptions and opinions

Our components should be usable by as many teams and apps as possible. Keeping them generic makes them more adoptable and helps us focus on that core mission, presentation.

We see this idea crop up in a few places; state management and UI context. We want to use the lowest common denominator for state management if necessary; React's state and context API's. We don't want to assume that one component is used within another (even if they are obviously related).

[The only assumptions we can safely make is that they’re using React 16+ and Fluent]

Opinions are expensive to maintain, defend, and usually discourage folks from taking up our components. We should be very careful about forming any intentionally or unintentionally. If we do want to have an opinion, it should be easily overridable so it’s not an all or nothing deal. Similarly, we don't want to concern ourselves with guarding from unintended use. We give our consumers an open foundation and trust them to do the rest.

3)      Follow Fluent

It should be really easy to use our components among Fluent components. Our API’s should have similar shapes, patterns and naming conventions.

[All apps that consume admin-controls also consume the Fluent (formerly Fabric) component library. Fluent components are small and atomic. admin-controls compose and add to Fluent. We basically see ourselves as Fluent’s kid brother. We wanna be cool like them. We take it as a sign of success when developers think we’re part of that team because our API’s are so similar.]

4)      Accessibility and performance are top priorities

Accessibility and performance are part of admin-controls charter. They are not optional and are considered part of the expected behavior when used as intended.

[Again, leveraging what we’re good at. This also adds to our value proposition, big time. Teams LOVE that we make accessibility easy for them]

5)      Prefer a simple, consistent API shape

The developer experience is as important as the end user experience. Our components should be as easy and intuitive to work with as possible. Being uniform from one component to the next helps developers learn and adopt our library quickly.

[This one may be one of the hardest ones to get right. Most junior devs can build the thing. They’re excited about all the gymnastics they had to do to pull it off. A senior dev will make it simple, elegant, easy to follow and most importantly, easy to support. It takes a little more time and thought to simmer a complex API into something simple for others to understand.


It’s important to remember that no one is forced to use our stuff. We only succeed if we make developers lives better. Working with simple components is way more fun than complex ones.]

6)      Build trust through consistent quality

It's important that we deliver quality components, the first time. Earning and maintaining developer trust is a key element to increasing our impact. Developers will take our work more quickly if they are confident in it. Any bug churn and breaking changes are at odds with that goal. Being thorough with our testing and staying focused on quality, even if it takes longer, helps us build that trust.

[This tends to be another big obstacle for contributors. The solution is simply slowing down to think beyond the immediate use case. The slow speed catches folks off guard sometimes. Many of our contributors are feature devs working on a tightly regimented schedule. We are (thankfully) a little insulated since we are an internally facing team. A little schedule slippage is okay if it means a hardier component.

It (generally) takes 2-3x as long for a developer to contribute their first component in our library as they think it’s going to take. The biggest time suck tends to be learning the patterns that make our components highly flexible. We’ve found tactful ways to handle this tension. Acknowledging that they’re under pressure, responding quickly to PR’s and offering to talk to a PM for them are great ways to keep contributors from getting frustrated. This process does improve as they learn.

Go slow to go fast]

7)      Leave things open for customization and extension

We want to guide the golden path to design coherence. Enablement > Enforcement. If a team wants to deviate, we should let them. Specifically, this means keeping styles exposed and opening up overridable render areas with default behaviors.

[You never know when a feature is going to throw a ninja cat where you only initially envisioned a list. Developers LOVE taking components that they can customize and build on top of]

8)      Expose everything, seamlessly

It should be super easy for developers to talk to sub components. This keeps things open, and allows our consumers to take updates from our dependencies with very little friction. If Fluent adds a new accessibility fix to their Button, we want to let our consumers talk to it without having to be a middle man.

[This one is all about minimizing our opinions and maintenance costs. ]


Thanks for reading! Let me know what you think! Any you disagree with? Any concepts you think we should add?