Making your React component library meaningful in 2021
Tags: react, designsystem, a11y, cssinjs
Published: 2020-09-13 18:19:04

In the last article we managed to setup our project:

- use [dokz](https://dokz.site/docs) as a documentation engine
- add [stitches](https://stitches.dev/) as a class names generator and manager of class names on components

Now, we're going to use:
- `typescript` to leverage type-safe tokens and props for our component library
- `@react-aria` to make our components accessible

## TypeScript

I'm not going to talk about the benefits of using TypeScript in this article, but I would say that [unfortunately], when your library is super awesome already, this is the only way forward to make it even more enjoyable. And we know our library is going to be the best, so we can start with TypeScript straight away:

```bash
yarn add --dev typescript @types/react
```

And create a `tsconfig.json` (most of the stuff is added by `next`, the base config was copied from [here](https://github.com/tsconfig/bases/blob/master/bases/recommended.json))

```json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "types": ["react"],
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "noEmit": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": ["next-env.d.ts", "lib", "pages"],
  "exclude": ["node_modules"]
}

```

Now we rename our `lib/*.js` files to `.ts(x)` and we're done with the migration. We can now check that we get autocomplete suggesting possible values that we defined in our `stitches.config.ts`:

![Autocomplete suggests values for Box component](https://dev-to-uploads.s3.amazonaws.com/i/e20rzojz5u3z203gjyxs.png)

## Accessibility

Building accessible application is as important as having an elevator in the 9 stories building. You can skip building it, but **you** will hate yourself, people will hate you, and adding it to the existing building is... Well, at the very least is just expensive.

If you want to get familiar with the topic and don't really like to read [specification](https://www.w3.org/WAI/)s, I encourage you to read [Accessible to all on web.dev](https://web.dev/accessible/).

But if you were ever wondering "Why do we need to do it ourselves? Why it's not built into the platform, if the standards are defined?", well, let's discuss it in the comments, I think we're just not there yet. I hope we will get some new API similar to how `Date` [gets improved](https://tc39.es/proposal-temporal/docs/calendar-draft.html) or how [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat) gets new features.

Before the future comes, we can use a "library of React Hooks that provides accessible UI primitives for your design system" from [react-aria](https://react-spectrum.adobe.com/react-aria/index.html). This is the simplest way you can take to make your components accessible while keeping your business satisfied with the speed of delivery.

Let's start from the `Button` component. First, we add simple `lib/Button.tsx`:

```tsx
import React from 'react';

import { styled } from '../stitches.config';

const ButtonRoot = styled('button', {
  py: '$2',
  px: '$3',
  bc: '$blue500',
  color: 'white',
  fontSize: '14px',
  br: '$sm',
});

const Button: React.FC = ({ children }) => {
  return <ButtonRoot>{children}</ButtonRoot>;
};

export default Button;
```

*One of the downsides of CSS-in-JS solutions is that you have to come up with even more variable names, like ugly ButtonRoot*

Let's now create a playground for our `Button` to see it in action. Create `pages/components/Button.mdx` and add simple playground code:

```mdx
---
name: Button
---

import { Playground } from 'dokz';
import Box from '../../lib/Box';
import Button from '../../lib/Button';

# Button

<Playground>
  <Box css={{ p: '$8' }}>
    <Button>Hello</Button>
  </Box>
</Playground>
```

*Box is just for offset for now*

So here's what we have:

![Button initial doc page](https://dev-to-uploads.s3.amazonaws.com/i/k794j0tdk0i21twang37.png)

Now let's add our first `react-aria` package:

```bash
yarn add @react-aria/button
```

And use it in our `lib/Button.tsx`:

```tsx
import React, { useRef } from 'react';
import { useButton } from '@react-aria/button';

import { styled } from '../stitches.config';

const ButtonRoot = styled('button', {
  py: '$2',
  px: '$3',
  bc: '$blue600',
  color: 'white',
  fontSize: '14px',
  br: '$sm',
});

const Button: React.FC = (props) => {
  const ref = useRef<HTMLButtonElement>(null);
  const { buttonProps } = useButton(props, ref);
  const { children } = props;

  return (
    <ButtonRoot {...buttonProps} ref={ref}>
      {children}
    </ButtonRoot>
  );
};

export default Button;
```

*Here, I'm just following the [official instructions](https://react-spectrum.adobe.com/react-aria/useButton.html) and I always encourage people to go to the docs directly and copy code from there than from the article. Just remember that code in the Internet isn't 100% valid unless taken from the official docs (then it's a least 90% valid)*

Okay, this looks simple. What we've achieved? Actually, [a lot](https://react-spectrum.adobe.com/react-aria/useButton.html#features). I'm pretty sure it's hard to buy the benefits when you don't know the context. So, if you're interested why you need all this code, why we need to handle "press management" on the button, I would suggest to read more in-depth articles by the author of the `react-aria`: [Building a Button](https://react-spectrum.adobe.com/blog/building-a-button-part-1.html).


Now, let's try this in the playground:

```mdx
<Button onPress={() => alert('Wow')}>Make Wow</Button>
```

Now let's make sense out of our CSS-in-JS solution and create few button variants. I will use [Tailwind CSS](https://tailwindcss.com/components/buttons) as a reference:

```tsx
const ButtonRoot = styled('button', {
  py: '$2',
  px: '$3',
  color: 'white',
  fontSize: '14px',
  fontWeight: 'bold',
  transition: '0.2s ease-in-out',

  variants: {
    variant: {
      default: {
        'bc': '$blue500',
        'color': 'white',
        'br': '$md',
        '&:hover': {
          bc: '$blue700',
        },
      },
      pill: {
        'bc': '$blue500',
        'color': 'white',
        'br': '$pill',
        '&:hover': {
          bc: '$blue700',
        },
      },
      outline: {
        'bc': 'transparent',
        'color': '$blue500',
        'border': '1px solid $blue500',
        'br': '$md',
        '&:hover': {
          bc: '$blue700',
          borderColor: 'transparent',
          color: 'white',
        },
      },
    },
  },
});
```

This will create a mapping between prop `variant` and set of class names to be assigned to the `button` component. You may notice that some styles are repeated between `variants`. This is where I would strongly suggest to block any thoughts about extracting common styles into separate variables to make code DRY. Allow `variant`s to be isolated unless you feel the need to extract something.

Now, when we have our variants defined, how do we use in our `Button` component? Well, with some tricks:

```tsx
const ButtonRoot = styled('button', {
  /* common styles */

  variants: {
    variant: {
      default: { /* styles */ },
      pill: { /* styles */ },
      outline: { /* styles */ },
    },
  },
});

type Props = React.ComponentProps<typeof ButtonRoot>;

const Button: React.FC<Props> = ({ as, variant = 'default', ...props }) => {
  const ref = useRef<HTMLButtonElement>(null);
  const { buttonProps } = useButton(props as any, ref);

  return (
    <ButtonRoot {...buttonProps} variant={variant} as={as} ref={ref}>
      {props.children}
    </ButtonRoot>
  );
};
```

First, we infer types generated by `stitches`:

```ts
type Props = React.ComponentProps<typeof ButtonRoot>;
```

This allows us to get access to defined `variant` prop and `as` prop that [`stitches` provides](https://stitches.dev/docs/overriding-styles#overriding-the-html-tag) to overwrite the HTML Element to render (BTW, you may argue whether you want this prop to be available for `Button` or it's better to create a new component to handle specific case, for example for `<a>` HTML element that looks like a button).

Second, we use this type for our `Button`, for the consumers of this component to see what props are available, specifically what `variant` one can apply:

```tsx
const Button: React.FC<Props> = ({ as, variant = 'default', ...props }) => {
```

We also extract props that aren't default to `<button>` element, just to make things clear. `variant` prop gets `default` variant (you can use `Button.defaultProps` also for that).

Then, we shamelessly use `any`:

```tsx
const { buttonProps } = useButton(props as any, ref);
```

It's not the first and not the last time we have to use it. But when you deal with types that aren't first-class citizens in the language, chances are that even describing same thing can be done in different way. In this case, `onFocus` prop expected by `useButton` doesn't match with `onFocus` prop that `stitches` has in its type definitions for `button`. But since we know it's `<button>` and we expect people to pass only `button` props – we can allow ourselves to use `any` this time.

Let's see these variants in `pages/components/Button.mdx`:

```mdx
---
name: Button
---

import { Playground } from 'dokz';
import { Box, Button } from '../../build';

# Button

<Playground>
  <Box css={{ p: '$8', display: 'flex', gap: '$3' }}>
    <Button onPress={() => alert('Wow')}>Make Wow</Button>
    <Button variant="pill" onPress={() => alert('Wow')}>
      Make Wow
    </Button>
    <Button variant="outline" onPress={() => alert('Wow')}>
      Make Wow
    </Button>
  </Box>
</Playground>
```

Saving, waiting a moment and...


![Button variants](https://dev-to-uploads.s3.amazonaws.com/i/dws62legsszfxmucf2vc.png)

Here we go!
If you want to test props autocomplete (unfortunately `mdx` is not supported yet), try to write simple component even inside `lib/Button.tsx` that uses this `Button` component. You'll see inferred possible `variant`s you can pass to the component:

![Button autocomplete shows variant prop options](https://dev-to-uploads.s3.amazonaws.com/i/r2nz16h5e92x048r6bz0.png)

So now we used some benefits of `stitches` and `react-aria` packages. I encourage you to check out more [react-aria](https://react-spectrum.adobe.com/react-aria/index.html) packages and see what else you can do with [stitches](https://stitches.dev/docs/introduction), for example, how you can easily change the layouts based on the `window` size using [responsive styles](https://stitches.dev/docs/responsive-styles).

Next, we are going to build and deploy the docs and the library, so that our foundation for component library is complete and we can start building more components.