{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "@id": "https://www.vidyasource.com/blog/lessons-learned-react-component-library-typescript/",
  "url": "https://www.vidyasource.com/blog/lessons-learned-react-component-library-typescript/",
  "mainEntityOfPage": "https://www.vidyasource.com/blog/lessons-learned-react-component-library-typescript/",
  "headline": "Lessons Learned from Building a React Component Library with TypeScript",
  "description": "Lessons learned, and not only about tech, from building a React component library for government.",
  "datePublished": "2021-10-11T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "Neil Chaudhuri",
    "jobTitle": "President",
    "url": "https://www.linkedin.com/in/neil-chaudhuri/",
    "sameAs": "https://www.linkedin.com/in/neil-chaudhuri/"
  },
  "publisher": {
    "@id": "https://www.vidyasource.com/#organization"
  },
  "image": "https://www.vidyasource.com/img/blog/react-ts.png",
  "keywords": [
    "React",
    "TypeScript",
    "Chakra UI",
    "Accessibility",
    "Vite",
    "Jest",
    "React Testing Library",
    "Storybook",
    "Open source",
    "Government"
  ],
  "articleBody": "Component libraries are all the rage. Shopify, Salesforce, IBM, and even the [United States government](https://designsystem.digital.gov/components/overview/)\nhave joined countless other organizations and businesses in building component libraries. They're the subject of blog posts,\npodcasts, and YouTube tutorials. All that's left is a [Ken Burns documentary](https://kenburns.com/the-films/) on the subject.\n\nIn fact, I am a software architect and senior engineer, and I currently lead the development of a React component library that will be the basis for the UIs for a\nprominent US government agency. I want to share with you my lessons learned in project management, communications,\naccessibility, engineering, and testing to build something that will impact the lives of millions. And the ups and downs of it all.\n\nSo what's the big deal with component libraries?\n\n## The Design System\n\nIt doesn't start with a component library; it starts with a design system. The Nielsen Norman Group defines design systems\n[this way](https://www.nngroup.com/articles/design-systems-101/):\n\n> A design system is a complete set of standards intended to manage design at scale using reusable components and patterns.\n\nA design system enumerates the standards and practices that comprise the premier UX for consumers of your brand. It expresses\nthe nomenclature every team should use in communications to break down silos and avoid the impulse from [Conway's Law](https://www.melconway.com/Home/Conways_Law.html).\nThere are basic rules about colors, typography, spacing, and so on. All of these core principles become the basis for larger\ncomponents--explicit ones like buttons and date pickers and subtler ones like grid systems.\n\nOur UX team develops and maintains our design system. Like software, it evolves; it's versioned; and it's collaborative. There are conversations\namong the UX designers and with me and other architects and engineers on the program about what makes sense and what is feasible.\nAre nested dropdowns necessary? Do we have time to create our own perfect `Datepicker`? Or do we try to customize something open source?\nHow do we feel about disabled buttons, and if we think they make sense, how\ncan we overcome common pitfalls like poor [contrast ratios](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast)?\n\nStuff like that. We use the language of [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/), which deconstructs\nweb interfaces into entities ranging from \"atoms\" to \"pages,\" as a common nomenclature to describe the goals of the design system.\n\nThe challenge, and probably the hardest part of building a component library for us, is the tooling. Partly because of the preferences of the UX team and partly because\nof constraints on our development environment due to the sensitive nature of our work, we have not been able to\nstreamline automation for versioning UX wireframes or translating them into artifacts engineers can use to build. As a result,\nwe work with wireframes that are cumbersome to understand. In order to even view them, we either need to install the tool on our\nmachines, which costs more licenses and imposes a burden on developer experience (DX), or we need to wade through literally hundreds of static asset files\nwith a custom browser plugin. Neither is an optimal experience. Beyond that, it's a manual process to track consistency between the design system and\nthe component library as both evolve.\n\nI never said it was pretty, but it isn't all bad either.\n\n## The Value of a Component Library\n\nThe design system is a set of core principles independent of implementation details. You can choose to implement these principles\nand make them real for UI engineers with whatever technology you choose.\n\nFor us, that's React. Our React components generate a lot of value for the program.\n\n### Consistency\n\nOur component library enforces our design system across our development teams. Using the components all but guarantees\na UI will be consistent with our brand and provide our users the best, most intuitive experience. Developers can feel\nconfident they are using components vetted with the UX team, which frees them up to work on the specific use cases of their\nservices rather than cross-cutting concerns like consistency with the design system.\n\nThe library also maximizes the likelihood that our UIs pass visual testing by our UX team. This is important as violations slow down our delivery cadence\nand ability to get feedback.\n\n### Accessibility\n\nRelated to consistency is accessibility, which is a first-class priority for our component library. Accessibility, commonly known as [#a11y](https://www.a11yproject.com/),\nis more than just empowering the visually impaired. It also means empowering people who experience difficulty with hearing,\nmotion, dexterity, or anything else. It means empowering *everyone*.\n\nThe program is required by contract and\n[by law](https://www.access-board.gov/law/ra.html#section-508-federal-electronic-and-information-technology) to produce UIs that\nare accessible--specifically [508 compliance](https://www.section508.gov/tools/playbooks/technology-accessibility-playbook-intro/).\nThat said, accessibility is far more than a professional obligation; it is my personal priority. It is very important to me that\neverything I build is intuitive for every user.\n\nI will elaborate on this shortly, but our component library is built for accessibility. Development teams\ncan trust the accessibility of the individual components, and as I said before, focus on their own use cases. Of course you\nare probably thinking in terms of accessible dropdowns and autocompletes and datepickers, which we have, but we also\nprovide helper [Semantic HTML](https://developer.mozilla.org/en-US/docs/Glossary/Semantics#semantics_in_html) components.\nFor example, the library features `Section`, which represents the `section` [HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)\nas you would imagine, and `SectionGrid`, which is a `section` element endowed with our design system grid.\n\nOf course, the component library can only take developers part of the way to full accessibility, but it's nice not to have to start from 0.\n\n### Reusability\n\nWe have worked very hard to provide intuitive APIs for our components, but the task is trickier than you might think. The\nAPIs need to impose enough opinion so that consumers don't violate the design system but allow enough freedom for the components\nto support a wide range of use cases. For our `Button` component, that is easy enough. For layout components like\n`Card` and `Page`, it's tougher. The reusability that results has made individual teams and the entire program so much more productive.\n\nWe also go out of our way to endow our components with as little functionality as possible. Component APIs offer props that enable library\nconsumers on the development teams to supply behavior. For an obvious example, developers supply `onClick` behavior to the\n`Button` component. We have more complex components that need to maintain their own state,\nbut we try to minimize that where possible. This provides a clean separation of concerns, which makes testing our components much easier,\nand anyone who has been in the game long enough knows that strong testability makes for strong reusability.\n\n### Encapsulation\n\nThere will be more about this shortly, but we do not build our components from scratch. Rather, we customize existing open source\ncomponents and map our APIs to theirs. This abstracts the implementation details of the component from our development teams.\nFor example, we use [react-datepicker](https://github.com/Hacker0x01/react-datepicker) as the basis for our own `DatePicker`,\nbut if we decide to swap it out for a different one, our consumers will be none the wiser.\n\n## Component Stack\n\nAs I mentioned, we build our component library with React, which is what we recommended but is also, for our risk-averse\ngovernment customer, the safe choice given its backing by Facebook, [its market penetration](https://insights.stackoverflow.com/survey/2021#section-most-popular-technologies-web-frameworks),\nand [its popularity](https://insights.stackoverflow.com/survey/2021#most-loved-dreaded-and-wanted-webframe-want).\n\nBut React is the easy part. Let's look at other parts of the component stack.\n\n### TypeScript\n\nWhen we started building the component library, I considered TypeScript essential for two reasons. By enforcing type safety during\ndevelopment and at build time, we catch bugs much faster, which from a project management standpoint is much cheaper. More importantly,\nbuilding our APIs in TypeScript is a huge help to library consumers on application development teams by facilitating code\ncompletion in their IDEs and type checking in *their* builds.\n\nLet me also mention that some of our TypeScript APIs require [ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) values\nto promote accessibility if we can't derive them ourselves from other props.\n\n### Chakra UI\n\nI mentioned earlier that our components are built on open source components, and most of them are built on\n[Chakra UI](https://chakra-ui.com/). There are many other open source component libraries out there, but Chakra UI\nis my favorite by far. The primary reasons are its first-class commitment to accessibility and the intuitive APIs of its\ncomponents built with TypeScript. As you can probably infer, Chakra UI is an inspiration to me when building our own\ncomponent library on top of it.\n\nChakra UI also offers a powerful [theme customization API](https://chakra-ui.com/docs/theming/customize-theme) we leverage heavily\nto apply the principles of our design system to Chakra components via dedicated theme files that separate the styling from\nfunctionality. This separation of concerns makes it easier to reason about our code and makes the files themselves\na lot lighter.\n\nChakra UI also features with some helpful hooks like [useDisclosure](https://chakra-ui.com/docs/hooks/use-disclosure) that come in handy.\n\nIf you use Chakra UI for your own component library, you will probably need some alias imports to deal with name collisions.\nFor example, we call our button components, to no one's surprise, `Button`, but so does Chakra UI. So we do this:\n\n~~~js\nimport { Button as ChakraButton } from \"@chakra-ui/react\"\n~~~\n\n## Engineering\n\nOf course the fun part is building a React component library. This post is long enough, so I can't get into every detail. But\nI do want to address some of the key aspects you might want to consider when you build your own.\n\n### Workflow\n\nWhen we first began building the component library, we needed to move quickly because development teams were waiting on us\nto start building their UIs. Our management tasked me and several developers to get something done in a few sprints at nearly\na full time commitment.\n\nWe got the initial design system specification from the UX team and got to work. After those first few sprints, we had built enough components\nto allow teams to get going. The problem is that all of us resumed our normal duties with no time allocation for the library. This\nmeant that whenever the UX team designed new components or developers found bugs in existing components, there was a bottleneck\nbecause no one was dedicated to upgrading the library. I and others got to it when we could, but the absence of a dedicated team\nwas a problem.\n\nAnother problem is the initial lack of communication within the UX team itself and among the UX team, developers, and me. In their creative zeal,\nfar too often they provided wireframes to some developers inconsistent with wireframes provided to others,\nor they provided wireframes featuring components that weren't in the library.\nDevelopment teams assumed they *were* in the library and estimated accordingly. As you might expect, they were unhappy when\nthey discovered the components didn't exist, which impacted their ability to deliver on schedule. They let me know it, and frankly they\nhad every right to be unhappy. I knew we had to improve our process.\n\nTo that end, we made some changes. We established a Microsoft Teams channel to encourage communication by eliminating\nthe ceremony of meetings and even E-mails. We also decided that development teams will build new components initially, and if\nother teams will benefit, the library will absorb them, with tweaks as needed to APIs or implementations, to support broader\napplicability across the program. Then the team that built the component first will replace their implementation with\nthe library's when ready. While this means teams have to devote more time to developing components, it's transparent, and there is no bottleneck.\n\nThis is an evolving workflow. There is always room for improvement.\n\n### Component structure\n\nOur components in TypeScript take three forms.\n\nThe simplest components look like this:\n\n~~~js\nexport const TimePicker = (p: TimePickerProps) => {\n    ...\n}\n~~~\n\nOur `TimePicker` component has no children, so it's as straightforward as it gets. It's just a function!\n\nIf the component has children, it still isn't too bad:\n\n~~~js\nexport const Card: React.FC<CardProps> = p => {\n    ...\n}\n~~~\n\nReact's `FC` type (for `FunctionComponent`) includes a `children` prop implicitly. We could also\ndeclare it just as we do `TimePicker` but explicitly add a `children` prop of type `ReactNode` to `CardProps`. I prefer `FC`\nbecause it very clearly signifies the presence of `children` to library consumers and because the type parameter lets me enjoy\nsome type inference. Notice how I don't have to specify the type of `p` because it's implicit from the type parameter `CardProps`.\n\nStill, not too bad, right?\n\nThe last kind of component is a little complicated--form components. Our developers use [React Hook Form](https://react-hook-form.com/),\nand like every other form library I've used, it uses `ref`s to maintain form state. This means our components\nneed to provide a way to accept a `ref` and delegate it to their children.\n\nMost React engineers don't know this because they don't have to, but React provides a function for exactly this purpose called\n`forwardRef`, and we use it like this:\n\n~~~js\nexport const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(p, ref) {\n    ...\n}\n~~~\n\nLet me try to break this down.\n\nA [higher-order function](https://www.oreilly.com/library/view/functional-programming-in/9781492048633/ch04.html) is\na function that takes functions as parameters or returns a function. Here `forwardRef` takes that `Button` function that renders the component as a parameter.\nThanks to `forwardRef`, development teams can pass refs to the form components in our library, which we pass along though that function parameter\nto our rendered implementation. The type parameters to `forwardRef` provide type safety and inference. The type\nof `p` is `ButtonProps`, and the `ref` will be hooked onto a `HTMLButtonElement`.\n\nIn the end, it's a little complicated and a fair bit of ceremony, but the result is pretty simple--a form component that accepts\na `ref` from the caller so form libraries can work with it as needed.\n\n### Directory Structure\n\nWhen considering how to lay out your source code, it comes down to your team's preference, but as I posted recently:\n\n> There is a lot of commentary on how we should lay out source code in React. If you take two &quot;things&quot; (functions, classes, #TypeScript interfaces, etc.), the higher the frequency that changing one changes the other, the closer they should be together.\n\nWhat does that really mean in practice?\n\nSimple. When it comes to our component library, this means organizing code dedicated to a particular component in the same\ndirectory and even in some cases the same file. This is how we do it at a high level.\n\n![Button component directory layout](https://www.vidyasource.com/img/blog/rcl-button.png)\n\nOur `Button.tsx` contains the `ButtonProps` interface, related types, and of course the component itself. Meanwhile, I love\nhow Chakra UI allows us to separate theming from behavior, so the colors, spacing, font family, icon sizes, focus behavior, and other button\ndetails defined by our design system are in `ButtonTheme.ts`, a different file in the same directory.\n\nFinally, although we could keep our tests and stories (more on these later) in the same directory, we prefer organizing them\nin their own subdirectories. I guess I've seen too much Marie Kondo.\n\n### TypeScript Config\n\nI come from a background in [statically and strongly typed programming languages](https://stackoverflow.com/questions/2690544/what-is-the-difference-between-a-strongly-typed-language-and-a-statically-typed)\nlike Java and Scala. While I understand longtime JavaScript engineers balk at types, I find types make me extremely productive.\nAs a result, our TypeScript config is very strict. In particular from our `tsconfig.json`:\n\n~~~json\n{\n...\n  \"compilerOptions\": {\n    ...\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitAny\": true,\n    ...\n  },\n...\n}\n~~~\n\nAs for building the library for application development teams, we scope our `tsconfig.json` this way:\n\n~~~json\n{\n...\n  \"include\": [\n    \"src/**/*\"\n  ],\n  \"exclude\": [\n    \"**/__stories__/*\",\n    \"**/__test__/*\"\n  ],\n...\n}\n~~~\n\nAll our components, stories, and tests are in the `src` directory, but we only want the components when we build the library.\nThis is why we exclude the `__stories__` and `__test__` directories inside each component directory.\n\n### Static Analysis and Code Formatting\n\nLike everyone else, we rely on eslint and Prettier, and we don't do anything particularly special. Still, I do want to mention a couple of things.\n\nFirst is `eslint-plugin-jsx-a11y`. We use [this eslint plugin](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y)\nto automate verification of the accessibility of our component library. It checks the JSX of our components for obvious\nviolations. This is as far as we can go with automation, but we complement `eslint-plugin-jsx-a11y` with manual\nauditing in Storybook I will discuss shortly.\n\nThere might be something gnawing at the experienced engineers reading this. In the `tsconfig.json` above, we exclude our\nstories and tests because they don't belong in the build. Still, you know we should\napply the same quality standards to story code and test code as we do to production code. Code is code.\n\nTo do this, we [extend](https://www.typescriptlang.org/tsconfig#extends) `tsconfig.json` in a file called `tsconfig.eslint.json`,\nreplacing the `exclude` field with an empty array, and configure `eslint` to use *that*. This tells `eslint` (and therefore Prettier)\nto include *everything* in the `src` folder in its analysis with identical TypeScript configuration. This means, for example, we can't cheat\nby using an implicit `any` in our stories or tests either.\n\n### Builds\n\nWe run our builds with [Vite](https://vitejs.dev/). That may seem counterintuitive since Vite is the build tool for [Vue](https://vuejs.org/)\nwhile our library is built with React, but Vite is actually agnostic. In fact, it amazed me how little configuration we needed.\nIt basically just worked. Our Vite config is almost identical to the [example in the documentation](https://vitejs.dev/guide/build.html#library-mode).\nJust like the example, our build produces two bundle formats--`es` and `umd`--and it works fast.\n\nAs you may know, TypeScript builds feature two phases, type checking and transpilation to JavaScript. Type checking by `tsc`,\nthe TypeScript compiler, is *very* slow, so while it is very important, you should do it rarely. We only do it via\nthe IDE in real time as we code or when we build the library for production--and break the build if type checking fails.\n\nWe have a dedicated `typecheck` script in our `package.json` that looks like this:\n\n~~~json\n{\n  \"scripts\": {\n    ...\n    \"typecheck\": \"tsc --p tsconfig.eslint.json --skipLibCheck --sourceRoot src --noEmit\",\n    ...\n  }\n}\n~~~\n\nNote that we use `tsconfig.eslint.json` to typecheck everything.\n\nMeanwhile, transpiling your TypeScript source code to JavaScript is faster than type checking, but so is reading Tolstoy. Transpiling\nwith `tsc` or Babel is still not fast. However, the transpiler [esbuild](https://esbuild.github.io/) is written in Go, a language [built for speed](https://www.vidyasource.com/blog/scala-go/),\nand Vite uses it under the hood. Because we are transpiling constantly to see what's happening in Storybook, it's crucial that the process be fast. Thanks to esbuild,\nVite does exactly what we need.\n\nOur production build, versioned with [Semantic Versioning](https://semver.org/), includes [declaration files](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html)\nfor each component and an `index.d.ts` file enumerating all components. These improve DX by enabling developers' IDEs to perform\nfast code completion. We also provide the [theme file](https://chakra-ui.com/docs/theming/customize-theme) we use for our own components\nso that developers can apply the same theme to theirs. Our CI/CD pipeline publishes the library to a private NPM registry, which\nallows appropriately configured `npm` installations on developer machines to fetch the library with a conventional `npm install`.\nThe `package.json` file accompanying the library contains all the peer dependencies they will need to use the library so `npm`\ncan grab them, and for convenience it also contains the version of the design system it is built with for developers to track.\n\nIt also contains configurations to define which files to package in the library and how consumers can import modules:\n\n~~~json\n{\n...  \n  \"files\": [\n    \"dist\"\n  ],\n  \"types\": \"./dist/index.d.ts\",\n  \"main\": \"./dist/components.umd.js\",\n  \"module\": \"./dist/components.es.js\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/components.es.js\",\n      \"require\": \"./dist/components.umd.js\"\n    }\n  }\n...\n}\n~~~\n\nOne last thing to note about the build. Although Vite of course provides minifying and other production readiness capabilities,\nwe don't use them. We bundle the component library completely \"raw.\" We find this helps developers debug their applications\nand report bugs (in those rare cases we make mistakes) with specificity. When they run their own builds, their tooling will apply\nminifying, tree shaking, and all other production processing to all their code and dependencies including the component library.\n\n## Testing\n\nAs I mentioned before, we limit the functionality of our components to the bare minimum necessary to add value. Still,\ncomponents are code, and our consumers have expectations of our code. This means we need to test our components as much\nas we can and where it makes sense.\n\nTesting is a controversial topic. On Tech Twitter, engineers are more than happy to let you know why you are wrong\nto test your code in a different way than they do. I can only describe what works for us and why we think so while also\nstipulating that our methods are subject to change as we get better at this.\n\nOur approach is heavily inspired by this [Storybook blog post](https://storybook.js.org/blog/how-to-actually-test-uis/). In it,\n[Varun Cachar](https://twitter.com/winkerVSbecks) describes different types of testing, when each is appropriate, and which tools\nmake sense for which types based on the experiences of several large-scale engineering teams.\n\n### Storybook\n\nStorybook is crucial to the development and testing of the component library for us, and it's indispensable documentation for our users.\n\nDuring development, we use it in a couple of ways. If the component is simple, then it's nice to have your code and Storybook\nside by side and watch your changes render as you make them with hot reload. On the other hand, when we aren't clear on\nwhat the API for a component should be, it's nice to write a few [stories](https://storybook.js.org/docs/react/get-started/whats-a-story)\nto work out the DX for it. Experienced engineers might recognize this approach as analogous to\n[Test-Driven Development (TDD)](https://www.agilealliance.org/glossary/tdd/).\n\nWe apply our design system custom theme in Chakra UI to every story in `preview.jsx`:\n\n~~~js\nexport const decorators = [Story => <ChakraProvider theme={theme}>{Story()}</ChakraProvider>]\n~~~\n\nDuring testing, we also use Storybook in multiple ways. For example, because we take a mobile first approach to our components,\nwhich matters for [organisms](https://bradfrost.com/blog/post/atomic-web-design/#organisms) in particular like modals, we configure custom\nbreakpoints like this in `preview.jsx`:\n\n~~~js\nexport const parameters = {\n\tviewport: {\n\t\tviewports: {\n\t\t\txs: {\n\t\t\t\tname: \"XS\",\n\t\t\t\tstyles: {\n\t\t\t\t\theight: \"568px\",\n\t\t\t\t\twidth: \"320px\",\n\t\t\t\t},\n\t\t\t\ttype: \"mobile\",\n\t\t\t},\n\t\t\tsm: {\n\t\t\t\tname: \"SM\",\n\t\t\t\tstyles: {\n\t\t\t\t\theight: \"896px\",\n\t\t\t\t\twidth: \"480px\",\n\t\t\t\t},\n\t\t\t\ttype: \"mobile\",\n\t\t\t},\n\t\t\tmd: {...},\n\t\t\tlg: {...},\n\t\t\txl: {...},\n\t\tdefaultViewport: \"xs\",\n\t},\n}\n~~~\n\nI mentioned a CI/CD pipeline that builds the library and publishes it to a private registry. It turns out the pipeline also publishes\nour component Storybook to an [Nginx container](https://hub.docker.com/_/nginx) so that the UX team can conduct visual testing on the\ncomponents, and the ability to toggle among viewport sizes is extremely helpful.\n\nIt's also helpful for development teams who use our components to interact with them. Thanks to\n[Storybook Controls](https://storybook.js.org/docs/react/essentials/controls), they can configure components themselves\nto see what happens. Thanks to [Storybook Docs](https://storybook.js.org/addons/@storybook/addon-docs), they can see the code\nand API props that generate each story. So Storybook provides a profound documentation benefit throughout the program.\n\nWe also use Storybook for [composition testing](https://storybook.js.org/blog/how-to-actually-test-uis/) occasionally though\nnot as often as the Storybook team may prefer. For example, we have stories that demonstrate how to integrate our\nform components with React Hook Form, and this exposed issues we had with our `ref`s. Generally though, we don't do a\nlot of composition testing until we need to [reproduce a scenario to fix a bug](https://www.vidyasource.com/blog/code-coverage-is-killing-you)\n(and prove we've fixed it eventually).\n\nWe make heavy use of [storybook-addon-a11y](https://storybook.js.org/addons/@storybook/addon-a11y) to test for accessibility.\nAs you can see from another post by [Varun Cachar](https://twitter.com/winkerVSbecks), who is definitely earning his paycheck,\n[Storybook offers a lot of features for accessibility testing](https://storybook.js.org/blog/accessibility-testing-with-storybook/).\nWe make use of all of them. As I mentioned before, even though we do our best with `jsx-a11y` in the build and Storybook\nvisually to test for accessibility, it is still incumbent upon teams to add [@axe-core/react](https://www.npmjs.com/package/@axe-core/react)\nto *their* builds and perform their own visual tests in order to feel as confident as we can that we are providing the\nbest possible experience to all our users.\n\nFinally, while Storybook has been invaluable for us and I recommend it strongly, I would be remiss if I didn't mention\nsome gotchas. Storybook uses a lot of the same libraries we all use for theming, Markdown, and other things. When there are\nlibrary conflicts between your version and theirs, bad things happen. For example, we got hit with the same conflict\non [Emotion](https://emotion.sh/docs/introduction) as this [issue on GitHub](https://github.com/storybookjs/storybook/issues/15879).\nTo its credit, the Storybook team releases frequently. If nothing else, make sure you use identical versions of Storybook and all its addons\nand that you upgrade as soon as possible when updates are available.\n\nStorybook is also well aware of the \"DivOps\" revolution in JavaScript build tooling [and is positioning itself accordingly](https://storybook.js.org/blog/storybook-for-webpack-5/).\nThis is exciting since Webpack had a good run but feels more and more like the past, and we wanted to use Vite with Storybook.\nWe installed [storybook-builder-vite](https://storybook.js.org/blog/storybook-for-vite/) knowing it's experimental\nto see how it would work for us. Overall, it makes our Storybook builds fast just as we hoped. Still, when you consider\n`storybook-builder-vite` is raw, community-led by great engineers who have already given the community so much with their limited time and\ncan't address every issue, and the general brittleness of Storybook I mentioned, your mileage may vary. Here is our\nVite-related Storybook configuration in `main.js`:\n\n~~~js\nmodule.exports = {\n\t...\n\tcore: {\n\t\tbuilder: \"storybook-builder-vite\"\n\t},\n\tviteFinal: async config => {\n\t\treturn {\n\t\t\t...config,\n\t\t\tplugins: ...,\n\t\t\toptimizeDeps: {\n\t\t\t\t...config.optimizeDeps,\n\t\t\t\tentries: [`${path.relative(config.root, path.resolve(__dirname, \"../src\"))}/**/__stories__/*.stories.@(ts|tsx)`],\n\t\t\t},\n\t\t}\n\t},\n}\n~~~\n\n### React Testing Library\n\nIf you have read any of my posts on testing, you know that I think our industry writ large gets testing wrong. We test some\nthings too much. We test other things too little. We don't always know the purpose of our tests. And worst of all,\nbecause of perverse incentives, [we write tests to check a box](https://www.vidyasource.com/blog/code-coverage-is-killing-you).\n\nI mentioned earlier that it has been a priority to endow our components with as little behavior as possible. Aside from the fact\nsimpler code is easier to maintain and understand, this approach means fewer surprises for our consumers and less for us to test.\n\nOr so I thought.\n\nOur program has a mandatory minimum of 80% code coverage for our applications, and for reasons that don't make a lot of\nsense to me, that also applies to the component library. In my view, only components that maintain internal state offer\nthe complexity that demands the ceremony of formal tests beyond Storybook, but alas, I don't make the rules.\n\nReact Testing Library has become the *de facto* standard for [interaction testing](https://storybook.js.org/blog/how-to-actually-test-uis/)\nin React, and of course we use it for our own tests. But how could we write tests as quickly as possible to limit the impact\nof the code coverage standard?\n\nIf you have written tests in any programming language, you understand the concept of \"[test fixtures](https://stackoverflow.com/questions/12071344/what-are-fixtures-in-programming),\"\nthe setup for your tests. For us, that means test fixtures are simply components configured with different props.\n\nBut isn't that exactly what stories in Storybook are?\n\nStorybook offers a feature I love--the ability to import stories into tests written with React Testing Library as fixtures using\n[@storybook/testing-react](https://storybook.js.org/addons/@storybook/testing-react). Without it, we would need to duplicate\nthe same code as stories in Storybook and fixtures in tests. The autocompletion is great too thanks to the\nTypeScript support built into `@storybook/testing-react`.\n\nOne last thing I want to mention is, as you might guess given how much I have emphasized it in this post, accessibility. All of our\ntests in React Testing Library use `getByRole` and `findByRole` selectors. We do this because it is a way to build implicit accessibility testing\ninto our interaction tests as [the documentation describes](https://testing-library.com/docs/queries/about#priority).\nAfter all, if we are unable to locate the component we wish to test by its ARIA role, that all but guarantees it isn't accessible.\nAnd if it isn't accessible, I don't care if it \"works\" because it doesn't work for everyone.\n\nAside from all that, our tests work exactly as you would expect if you know React Testing Library. Here is an example of a simple test\nconveying everything I described:\n\n~~~js\n...\nimport {\n\tDefaultMediumPrimaryButton,\n    ...\n} from \"../__stories__/Button.stories\"\n\ntest(\"Button primary display works\", () => {\n\tconst onClickMock = jest.fn()\n\n\trender(<DefaultMediumPrimaryButton onClick={onClickMock} />)\n\n\tconst button = screen.getByRole(\"button\", { name: \"Primary\" })\n\n\tuserEvent.click(button)\n\texpect(onClickMock).toHaveBeenCalledTimes(1)\n})\n~~~\n\n---\n\nI know this is a lot, and it might have been slightly more entertaining as an audiobook. Still, I hope I conveyed the\nvalue in design systems and component libraries and the lessons we learned in project management, communications,\naccessibility, engineering, and testing to build something that will impact the lives of millions. I hope you can\ndo the same...but better.\n\nNow go take a nap. You earned it."
}