Developing and Driving Adoption of Component Libraries

Rate this content
Bookmark

What makes a component library good? In order to create a component library that people want to use you need to navigate tradeoffs between extensibility, ease of use, and design consistency. This talk will cover how to traverse these factors when building a component library in React, how to measure its success, and how to improve adoption rates.

22 min
24 Oct, 2022

Video Summary and Transcription

Today's Talk discusses the importance of a good component API and the balance between rigidity and flexibility. The demo showcases the gradual evolution of a component's configurability while maintaining ease of use. Measuring the effectiveness of a component library involves factors like adoption rate and component coverage. Collecting data and embracing breaking changes are crucial for continuous improvement. Ensuring consumers are updated and on the cutting edge is a responsibility of the library provider.

Available in Español

1. Introduction to Component API

Short description:

Today, we'll discuss what makes a good component API. The API is the most important aspect of a component library. It can range from rigid to flexible, with each having its advantages. A rigid API is easy to use and ensures consistent outputs. It's also easier to implement. Now let's dive into the details.

Hi there, I'm Lachlan and today with my colleague Logan, we'll be talking to you about developing and driving adoption of component libraries. This will be broken up into two parts. First, I'll be speaking about what makes a good component API and second, Logan will be talking about how we measure the success of our own component library and how we use data to inform us of how we can better improve it for our users.

Quick introduction, I'm a tech lead at TikTok working on design systems and a maintainer of our internal component library called Tux. So I spend a lot of time thinking about component libraries and how our users interact with them. So first I'll ask what makes a good component library and from my point of view, the API is easily the most important thing. The way that developers interact with the library.

So what makes a good component API? So I found that APIs sit along a spectrum ranging from rigid to flexible. If you imagine a UI component, for example, a date picker, if we gave it a rigid API, there are some advantages to this. One is that if it's rigid and there are very few ways in which you can use it, it generally is very easy to use because there are very few ways in which it can be misused. Secondly, it has consistent outputs, meaning if multiple teams are using this component, it's very likely that they're using it in the same way and you'll get the same look and feel across the different products. Thirdly, and somewhat selfishly, it is a lot easier to implement a library that has a rigid API compared to one that is truly generic.

2. Approach to API Design

Short description:

We aim to cover the common 90% of use cases with a rigid API, focusing on solving hard problems like accessibility and animation. We recognize that we can't cover 100% of use cases, so we make the remaining 10% easy for teams to handle. If presented with a use case we can't support, we may consider opening up the API, but this can introduce breaking changes and impact consistency and complexity of the library.

On the other extreme, you could build a date picker in a very flexible way. And that would cover more use cases. This is also really important because if a component doesn't fit a user's needs, they might have to build their own or get one from open source. And as soon as they do that, you're potentially sacrificing the consistent look and feel that you're trying to achieve through having a rigid API.

So, our approach is we recognize you don't need to and can't really cover 100% of use cases. So, instead we aim for the common 90% and try to make the remaining 10% easy for teams to do themselves. So, therefore, we start near the left of the spectrum with a really rigid API that focuses on solving the hard problems. For example, accessibility features and animation. I call these problems hard because most front end developers don't have experience solving these problems and would rather spend their time working on things like application logic. We move further right along the spectrum as necessary. If we're presented with a really good use case that we can't support with our current API, we'll consider opening up the API somewhat. But this can involve breaking changes, which Logan will talk about later. We also need to be careful because moving right means we are potentially reducing consistency and increasing complexity of the library itself.

3. Demo of Component API

Short description:

I'd now like to give a demo showing how we can take a very simple component with a very rigid API and gradually make it more configurable without making too many sacrifices to its ease of use and to providing a consistent look and feel. Let's move on to AV2 of our component. This is one way of addressing the problem. So, as you can see here, the props are pretty much exactly the same as V1, but the items additionally take an optional tag prop, which can pretty much convey whatever you need. So, of course, we could make this more complicated, this type. So, you could configure everything about the tags, and you could provide more than one, but this is really just to service the needs of one product team, and if there are 10 or more product teams, all with different requirements, this is going to get really complicated, maybe kind of hard to use. So maybe there's a better way to do this. So let's move on to version 3 of our component. And this uses an API that we use a lot internally at TikTok in our libraries.

I'd now like to give a demo showing how we can take a very simple component with a very rigid API and gradually make it more configurable without making too many sacrifices to its ease of use and to providing a consistent look and feel. So, here on the right-hand side we have a demo component, which is a drop-down ListBox, which I'm sure you've all seen before, and let's have a look at the API.

So, as you can see on the left-hand side, here are the props that our ListBox v1 takes, and importantly it takes something called items, which as you can see up here, is an array of what we call list items. And they can take a label and a value and optionally be disabled. So, as you can see this is pretty easy to use, but you can't really configure anything about it, especially how it looks. And imagine we have a product team who have this flavor picker, and they maybe want to explain to the user why certain item is disabled. Maybe this item is sold out, and they want to convey that to the user somehow instead of just disabling it or hiding it.

So let's move on to AV2 of our component. This is one way of addressing the problem. OK. So, as you can see here, the props are pretty much exactly the same as V1, but the items additionally take an optional tag prop, which can pretty much convey whatever you need. So, as you can see on the right, we can now mark an item as sold out, maybe mark an item as new, and this is really nice. But what if the team needs one item to have more than one tag? An item could be new and also sold out or an item could be popular and new. Or maybe we want to change the color of these tags so they're more identifiable. For example, green could signify some kind of positive connotation, maybe used for new items, while red could be negative, used for sold-out items. So, of course, we could make this more complicated, this type. So, you could configure everything about the tags, and you could provide more than one, but this is really just to service the needs of one product team, and if there are 10 or more product teams, all with different requirements, this is going to get really complicated, maybe kind of hard to use. So maybe there's a better way to do this. So let's move on to version 3 of our component. And this uses an API that we use a lot internally at TikTok in our libraries. So version 3 of our listbox still takes the same items array, but now importantly takes two additional properties. Render button and render item. You might recognize this pattern as render props. We find it really useful. So here let's configure how the button gets rendered. Mm-hmm. And this is basically the most simple way of using the component. But the cool thing about this is the items are a generic type. So you can add pretty much any data to these items, and then use it inside the render method to get really configurable like list items. So I'm going to copy an example I made earlier to show you what I mean.

4. Composing Components and API Design

Short description:

We've added additional properties to the list box component to conditionally show tags. This allows for a more consistent look and feel across components. The generic type inference makes it easy to add new properties. Another example is a list box of employees with avatar pictures and online-offline indicators. This demonstrates how our APIs balance ease of use, consistency, and configurability.

Okay. So you can see here, as well as the label and value, we've now added the additional properties is new and is sold out. And our render method not only shows the label, that also checks if the item is new or if the item is sold out and conditionally shows a tag. This tag is actually a another component in our component library. So it's kind of cool that we are composing already existing components with this list box. So maybe we can get a more consistent look and feel across each of our components. So let's have a look at what it looks like.

Great. So this is pretty neat. And again, because this is a generic type, Product team can just add more properties as they need. So maybe we have is popular on one of these. Maybe the banana is popular. Cool. And maybe if it's popular, we could add a popular tag. And you can see the type inference is great because it's a generic type. So it's really handy.

Yeah, we have a popular item. Very cool. I'll show another example, maybe a product team needs to have a list box of employees. And they need tags really, that maybe they need something like an avatar picture to show what the employee looks like, or maybe even an online-offline indicator. So let me bring up an example that I made earlier. And you can see again, our items basically have now an avatar and an isOnline status. And the render method, if we look at render item, we're also optionally adding an avatar. And again, this is a component for our component library that we're using. So it's really nice, we're able to compose things in this way. So if you have a look on the right, we can mark employees as online. And we can even display them inside the picker button. As you can see up here. So this is an example of how we design our APIs to try to strike a balance between ease of use, consistency, as well as allowing for some configurability. I'll now hand it over to Logan.

5. Measuring Component Library Effectiveness

Short description:

I will discuss how to measure the effectiveness of a component library by considering factors such as flexibility, rigidity, brand unity, developer productivity, and code quality. One primary heuristic we use is adoption rate, which measures how much engineers want to use our components. If the components are not flexible enough, developers may have to build their own, wasting time and lowering adoption rate. On the other hand, if the library is too rigid and complicated, it can also hinder adoption.

Hey, everyone. I'm Logan Rolston, a software engineer at TikTok, and I will be giving the second part of this talk today. So Lachlan already talked to you about what factors and balances we need to consider in order to design a good component library. And I'm going to continue by tackling the salient follow up question. How does one go about quantifying metrics to measure the effectiveness of their component library? We'll go over how to collect these metrics on how your component library is being used in practice, and then talk about how we use this data to drive our decision making and evolve our component APIs at TikTok.

So, brief intro about me. I'm Logan. I work on the Tux component library, which is TikTok's internal UI component library. And some of my work is on the infrastructure surrounding design systems. So, static code analysis tools, like we'll talk about in a second. Linting, code mod, kind of stuff.

So, let's start with how to measure a component library's effectiveness. So, when Lachlan talked about what makes a component library good. He referenced a couple abstract quantities. Flexibility and rigidity. That we can describe a component library in terms of. There's also brand unity, developer productivity, and an increase to code quality. These are all things that we want to find just to optimize. We want to have high developer productivity, high code quality, obviously. But they're hard to measure in practice because they can be quite abstract. So, when it comes to finding heuristics we can use to represent these, one of the primary ones that we use is adoption rate. So, adoption rate's really important because it measures how much an engineer actually wants to use your components. Because let's say your components aren't flexible enough. So, let's say you have a text input and it doesn't have an invalid text state. For example, let's say they entered a password with too few characters or some form data is incorrect or an email is not in the right format or something like that. You want like a red border around it and an error message below it. Let's say you don't have that. Then the developer is going to have to build their own component, which is going to be a huge waste of developer time. And it's also going to lower your adoption rate. And similarly, if you're component library is not rigid enough, it's way too complicated, there's way too many options.

6. Measuring Adoption Rate

Short description:

The adoption rate is a primary heuristic for measuring the effectiveness of a component library. Component coverage, which calculates the ratio of using the right component to total cases, is one of the primary metrics we use. For example, if a file uses tux button three times and a combination of my own button and anchor tag with class name btn-primary nine times, the component coverage for tux button in that file would be 25%. Although accurately determining the number of good and bad cases is challenging, it still provides a reliable heuristic.

The developers are just getting lost in some malays of various configurations, then they're not actually going to use your component and your adoption rate is also going to go down. So adoption rate is a primary heuristic. We know if that goes up, we're doing something right.

So there's actually a variety of ways to measure adoption rate, too. But one of the primary ones we use is component coverage. So component coverage is just the ratio of the number of times somebody is using one of your components to the number of times they should be using your components. So we call the times that they're actually using your components good cases. So we're going to take tux button, our button component, for example, here. So just every time they use the jsx element tux button in the file, that's a good case. They're doing something right. They're using the right component. And then every time they're using something like my own button or an anchor tag with the class name of btn-primary, that's probably going to be a bad case. That's something that they should be using tux button instead for. And then let's say that given a source code file, they use tux button three times and they use a combination of my own button and an anchor tag with class name of btn-primary nine times. So then we're going to say that in this file tux button has a component coverage of 25% because it's three good cases over 12 total cases, so three over 12, 25%. Now one thing to note here is, well it's possible that accurately determine the number of good cases completely. The bad cases is actually a text inference problem. So the logic there is a little fuzzy, so you can never guarantee bad cases is calculated perfectly. But that said, it's still, it can serve as a very accurate heuristic.

7. Metrics for Component Library

Short description:

We can collect various metrics to measure adoption rate, version distribution, styling standards adoption rates, bug fixes or feature requests over time, and linting rule violations related to the component library.

Okay, but that's not the only metrics we're limited to. We can collect a bunch more metrics. For example, another one for adoption rate is we can do adoption rate at the repo level, so we look at all the code bases that we have internally. We say which ones are using TUX, and which ones they should be using TUX, and we can calculate a coverage ratio for that too. We can look at the version distribution, which ones are stuck behind on old versions of TUX and not actually using the most updated components. That's something of interest. We can look at the styling standards adoption rates. For example, if we're using Tailwind atomic CSS classes, how often are people actually using those atomic CSS classes versus styles that are exactly equivalent and should be replaced with that atomic CSS class, so we can enforce some semblance of code style, or brand unity, or whatever construct through here. We can look at the rate of bug fixes or feature requests over time. We can look at linting rule violations that are related to the component library. There's many more metrics that are useful to collect.

8. Collecting Data and Embracing Breaking Changes

Short description:

We collect data on how Tux is being used through TuxScanner, a static code analysis tool. It parses source code files into ASTs and evaluates metrics to generate scores. These scores provide insights into component usage and help us make improvements. At TikTok, we embrace breaking changes to stay nimble and continuously evolve our component library. We streamline our architecture and use a monorepo to facilitate this process.

So how do we actually collect this data? The primary way we do this is through a tool called TuxScanner. TuxScanner is a static code analysis tool that goes in and looks at our individual files of source code without executing them, and measures how Tux is being used in practice and sends this data all off to an API so we can go and analyze how Tux is being used.

So we start here, we have a file of source code, we go and parse it into an AST, which is just a tree representation of the source code, and then we go and we evaluate a set of metrics on that AST, and those will all give us scores. So that could be like 80% component coverage before, or maybe there's 12 deprecated components being used, or something along the lines of that. It'll just collect us a bunch of scores, we'll conglomerate them all together, and send it off to the API so that we can look at how all our code files are using Tux, and filter over time, by platform, by the metric, and whatnot.

So to do a little bit of a deeper dive into how scanner works. So we start by parsing the source code files into ASTs, abstract syntax truths, which is just a tree representation of the source code, and it shows how all the constructs of the language are related. So the JSX elements related to the class name attribute, and have a tree based on that. And this just allows us to go through and evaluate a set of metrics on there. So a metric is just a function that takes in a set of ASTs, it goes in and traverses those ASTs, and produces a set of scores. And the score is just the number of good cases, the things we're doing right, the number of bad cases, the things we're doing wrong and need to improve to improve our score. The call sites of the good and bad cases, which are just the links to the nodes, usually it's JSX elements, but could also be an import declaration or something just to the place in the code where something's going wrong. And then the coverage source, and then we'll conglomerate that all together, we send it off to the API so we can filter by time and by package and whatnot.

Okay, so now that TuckScanner has collected all this data, how do we utilize it? So, at TikTok, things grow quickly, and our component library is no exception. We need to be able to evolve fast in order to keep up with the dramatic rate of change. And we want to be able to do that in order to stay nimble and keep on evolving. So, we need to be able to keep what's working and throw out and redo what isn't. Now, this is a good truism for how code should best be maintained, but it's not often practiced. And that's because introducing all these breaking changes all the time isn't fun for developers to keep up with. It makes their life hard and introduces a lot of maintenance. But we can't look at these things for the lens of being a maintenance burden. We need to always be striving to introduce breaking changes in order to go and reach for that asymptotic platonic form of what a component library should be, and we can't abandon that for the sake of stability. We've got to stay nimble. So we cannot be afraid of introducing breaking changes to our components. In fact, we want to be making them regularly. We got to embrace the breaking changes. So this is easy to say in theory, but it's hard to do in practice. And at TikTok, we've made a concentrated effort to streamline our architecture to facilitate this. So one such example is we use a monorepo. And in a monorepo, almost all of our consumers are on the latest version of Tux.

9. Consumer Updates and Library Evolution

Short description:

Consumers must not be left behind when introducing breaking changes. It is our responsibility at Tux to update our consumers and ensure they are on the cutting edge. We discussed what makes a good library, how to quantify its effectiveness, and how to use data to evolve it.

And this means consumers aren't left behind. Because let's say we introduce a bunch of breaking changes and release version 5 or whatever of Tux, but a lot of consumers are on version 3 because they want to have to go through an update to all the new changes. This means that we're not actually practicing what we're preaching, because if people aren't actually using your new latest cutting edge components, then in practice, they're just all being left behind and it was kind of pointless.

So this means that whenever we introduce a PR that introduces a breaking change as part of that, we've got to go and update the majority of our consumers to use the latest version of Tux. Because the majority of our consumers are always on the latest version of Tux. And then this means an important philosophy that we have to enforce this is the whet of co-ownership. So at Tux, if we make a breaking change to our package, it's our responsibility to update our consumers. Not the consumers' responsibility. So we have to go through and update all the call sites for code mods or whatnot and make sure they are on the cutting edge and that they aren't left behind.

Okay. So a quick summary. We talked about what makes a good library good. We talked about how to quantify a component library's effectiveness and we talked about how to use that data to evolve your library. Thank you very much.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Advanced Conference 2021React Advanced Conference 2021
39 min
Don't Solve Problems, Eliminate Them
Top Content
Humans are natural problem solvers and we're good enough at it that we've survived over the centuries and become the dominant species of the planet. Because we're so good at it, we sometimes become problem seekers too–looking for problems we can solve. Those who most successfully accomplish their goals are the problem eliminators. Let's talk about the distinction between solving and eliminating problems with examples from inside and outside the coding world.
React Advanced Conference 2021React Advanced Conference 2021
47 min
Design Systems: Walking the Line Between Flexibility and Consistency
Top Content
Design systems aim to bring consistency to a brand's design and make the UI development productive. Component libraries with well-thought API can make this a breeze. But, sometimes an API choice can accidentally overstep and slow the team down! There's a balance there... somewhere. Let's explore some of the problems and possible creative solutions.
React Day Berlin 2022React Day Berlin 2022
22 min
Jotai Atoms Are Just Functions
Top Content
Jotai is a state management library. We have been developing it primarily for React, but it's conceptually not tied to React. It this talk, we will see how Jotai atoms work and learn about the mental model we should have. Atoms are framework-agnostic abstraction to represent states, and they are basically just functions. Understanding the atom abstraction will help designing and implementing states in your applications with Jotai
TechLead Conference 2023TechLead Conference 2023
35 min
A Framework for Managing Technical Debt
Top Content
Let’s face it: technical debt is inevitable and rewriting your code every 6 months is not an option. Refactoring is a complex topic that doesn't have a one-size-fits-all solution. Frontend applications are particularly sensitive because of frequent requirements and user flows changes. New abstractions, updated patterns and cleaning up those old functions - it all sounds great on paper, but it often fails in practice: todos accumulate, tickets end up rotting in the backlog and legacy code crops up in every corner of your codebase. So a process of continuous refactoring is the only weapon you have against tech debt.In the past three years, I’ve been exploring different strategies and processes for refactoring code. In this talk I will describe the key components of a framework for tackling refactoring and I will share some of the learnings accumulated along the way. Hopefully, this will help you in your quest of improving the code quality of your codebases.

React Summit 2023React Summit 2023
24 min
Debugging JS
Top Content
As developers, we spend much of our time debugging apps - often code we didn't even write. Sadly, few developers have ever been taught how to approach debugging - it's something most of us learn through painful experience.  The good news is you _can_ learn how to debug effectively, and there's several key techniques and tools you can use for debugging JS and React apps.
React Day Berlin 2022React Day Berlin 2022
29 min
Fighting Technical Debt With Continuous Refactoring
Top Content
Let’s face it: technical debt is inevitable and rewriting your code every 6 months is not an option. Refactoring is a complex topic that doesn't have a one-size-fits-all solution. Frontend applications are particularly sensitive because of frequent requirements and user flows changes. New abstractions, updated patterns and cleaning up those old functions - it all sounds great on paper, but it often fails in practice: todos accumulate, tickets end up rotting in the backlog and legacy code crops up in every corner of your codebase. So a process of continuous refactoring is the only weapon you have against tech debt. In the past three years, I’ve been exploring different strategies and processes for refactoring code. In this talk I will describe the key components of a framework for tackling refactoring and I will share some of the learnings accumulated along the way. Hopefully, this will help you in your quest of improving the code quality of your codebases.

Workshops on related topic

React Advanced Conference 2021React Advanced Conference 2021
174 min
React, TypeScript, and TDD
Top Content
Featured WorkshopFree
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.

The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.

React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.
React Advanced Conference 2021React Advanced Conference 2021
145 min
Web3 Workshop - Building Your First Dapp
Top Content
Featured WorkshopFree
In this workshop, you'll learn how to build your first full stack dapp on the Ethereum blockchain, reading and writing data to the network, and connecting a front end application to the contract you've deployed. By the end of the workshop, you'll understand how to set up a full stack development environment, run a local node, and interact with any smart contract using React, HardHat, and Ethers.js.
React Summit 2022React Summit 2022
136 min
Remix Fundamentals
Top Content
Featured WorkshopFree
Building modern web applications is riddled with complexity And that's only if you bother to deal with the problems
Tired of wiring up onSubmit to backend APIs and making sure your client-side cache stays up-to-date? Wouldn't it be cool to be able to use the global nature of CSS to your benefit, rather than find tools or conventions to avoid or work around it? And how would you like nested layouts with intelligent and performance optimized data management that just works™?
Remix solves some of these problems, and completely eliminates the rest. You don't even have to think about server cache management or global CSS namespace clashes. It's not that Remix has APIs to avoid these problems, they simply don't exist when you're using Remix. Oh, and you don't need that huge complex graphql client when you're using Remix. They've got you covered. Ready to build faster apps faster?
At the end of this workshop, you'll know how to:- Create Remix Routes- Style Remix applications- Load data in Remix loaders- Mutate data with forms and actions
Vue.js London Live 2021Vue.js London Live 2021
169 min
Vue3: Modern Frontend App Development
Top Content
Featured WorkshopFree
The Vue3 has been released in mid-2020. Besides many improvements and optimizations, the main feature of Vue3 brings is the Composition API – a new way to write and reuse reactive code. Let's learn more about how to use Composition API efficiently.

Besides core Vue3 features we'll explain examples of how to use popular libraries with Vue3.

Table of contents:
- Introduction to Vue3
- Composition API
- Core libraries
- Vue3 ecosystem

Prerequisites:
IDE of choice (Inellij or VSC) installed
Nodejs + NPM
JSNation 2023JSNation 2023
174 min
Developing Dynamic Blogs with SvelteKit & Storyblok: A Hands-on Workshop
Featured WorkshopFree
This SvelteKit workshop explores the integration of 3rd party services, such as Storyblok, in a SvelteKit project. Participants will learn how to create a SvelteKit project, leverage Svelte components, and connect to external APIs. The workshop covers important concepts including SSR, CSR, static site generation, and deploying the application using adapters. By the end of the workshop, attendees will have a solid understanding of building SvelteKit applications with API integrations and be prepared for deployment.
React Summit 2023React Summit 2023
106 min
Back to the Roots With Remix
Featured Workshop
The modern web would be different without rich client-side applications supported by powerful frameworks: React, Angular, Vue, Lit, and many others. These frameworks rely on client-side JavaScript, which is their core. However, there are other approaches to rendering. One of them (quite old, by the way) is server-side rendering entirely without JavaScript. Let's find out if this is a good idea and how Remix can help us with it?
Prerequisites- Good understanding of JavaScript or TypeScript- It would help to have experience with React, Redux, Node.js and writing FrontEnd and BackEnd applications- Preinstall Node.js, npm- We prefer to use VSCode, but also cloud IDEs such as codesandbox (other IDEs are also ok)