Creating Custom Variants with Fluent UI React v9
Published: 2022-07-25 15:55:33

There are some cases in which you'll want to build a UI component variant to promote coherent UX across a product or a team of engineers. Quite often, this happens by overriding the default behaviors of the UI components available in your respective UI library, so you don't have to reinvent the wheel.

This scenario is no different with Fluent UI React. We've designed the UI components to allow style overrides. The following examples will walk you through the various ways you can use CSS-in-JS to override the default styling of the UI Components and create custom variants for your application.

## Variant Scenario: Launch Button

So, for this example, we're creating a "Launch Button" that has a specific set of styling that overrides the default [Button]() provided by Fluent UI React. We want to keep all the built-in functionality of the `Button` and override the following:
1. Always change the shape to be circular
2. Always have a Rocket Icon
3. Always change the background color of the rest, hover, and pressed states
4. Always is a large size

Here's an example of what we want the "Launch Button" to look like:
![Screenshot of the Launch Button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v6odwxq4s8hvhp9ar3sk.png)

You'll notice that the overrides are always present—meaning our intent is that any consumer of this component isn't meant to be overridden themselves, thus promoting coherent UX across our theoretical product 😊. 
 
In the next sections, we'll cover various ways we can override the Fluent UI React Button component and ways to structure it for sharing across a project.
 
## Option 1: Create CSS style overrides and component props

The first option for creating a variant would be creating CSS-in-JS style overrides and configuring the props of the `Button` component. Let's look at defining the CSS-in-JS style overrides using [Griffle](https://griffle.js.org)—the default CSS-in-JS engine in Fluent UI React:
```
const useLaunchButtonStyles = makeStyles({
  launchButton: {
    backgroundColor: "#ce000a",
    ...shorthands.borderColor("transparent"),
    color: tokens.colorBrandBackgroundInverted,
    ":hover": {
      backgroundColor: "#b10007",
      ...shorthands.borderColor("transparent"),
      color: tokens.colorBrandBackgroundInverted
    },
    ":hover:active": {
      backgroundColor: "#5d0b00",
      ...shorthands.borderColor("transparent"),
      color: tokens.colorNeutralForegroundOnBrand
    }
  }
});
```
We've created CSS-in-JS style definitions that overrides the `backgound-color`, `border-color`, and `color` attributes of the `Button` style. We also did this for the rest, hover, and pressed states o the button.

Next, we'll use the styles hook we created and apply the class to the `Button` component via the `className` prop:
```
const l = useLaunchButtonStyles();

<Button className={l.launchButton}>Launch</Button>
```

Now, this only overrides the color of the `Button` component, which means we need to add the Rocket Icon and change the `Button` shape and size:
```
const l = useLaunchButtonStyles();

<Button className={l.launchButton}
  icon={<RocketRegular />}
  shape="circular"
  size="large">
  Launch
</Button>
```

So, now we have a "Launch Button." The downside is that we need to tell any consumer that they need to configure the `Button` component with a CSS class, an Icon, and a Shape. This means that potential consumers could either change the design or misconfigure the component to promote a consistent UX.

Let's see how we can solve this with our next option!

## Option 2: Wrapper Component with style overrides and prop configuration
For this approach, we'll do exactly what we did in Option 1 above, but we'll wrap that up in a React Component.

Note: This technique does create a virtual node, so it's not recommended to have many layers of component wrapping as that can create performance issues. For cases of simple overrides not wrapped many times over, this approach meets our needs.

Once again, we'll take the same steps we did in Option 1, but wrap that up in a React Functional Component:
```
const useLaunchButtonStyles = makeStyles({
  launchButton: {
    backgroundColor: "#ce000a",
    ...shorthands.borderColor("transparent"),
    color: tokens.colorBrandBackgroundInverted,
    ":hover": {
      backgroundColor: "#b10007",
      ...shorthands.borderColor("transparent"),
      color: tokens.colorBrandBackgroundInverted
    },
    ":hover:active": {
      backgroundColor: "#5d0b00",
      ...shorthands.borderColor("transparent"),
      color: tokens.colorNeutralForegroundOnBrand
    }
  }
});

const LaunchButton: React.FC<LaunchButtonProps> = () => {
  const l = useLaunchButtonStyles();
  return (
    <Button
      icon={<RocketRegular />}
      className={classes}
      shape="circular"
      size="large"
    >
      Launch
    </Button>
  );
};

export default LaunchButton;
```

Now, when a consumer imports the `LaunchButton` they get all the consistency wrapped up into a functional component:

```
<LaunchButton />
```

This works great in guaranteeing coherent UX across a product, but we've essentially taken away any further downstream customization to consumers. If this was your intent—you're done. Yay! 🎉 

But, let's pretend someone on your team says, "The Design team wants this feature to have a green "Launch Button" in this one specific feature. How can we do that?"

Well, you could create another wrapper component and call it `LaunchButtonGreen`. However, as you imagine more and more color requests in the future, you know this isn't scalable. So, let's leverage the existing styling capabilities in the Fluent UI React components and bring that to your variant.

In the next option, we'll look at allowing CSS-in-JS style overrides to let consumers control the color of the `LaunchButton` but keep everything else fixed.

## Option 3: Wrapper component with overrides and allowing consumer style overrides

Now, we'll build on everything we've done in Option 2, but we'll allow consumers of the `LaunchButton` to pass style overrides via the `className` prop (just like we did with the Fluent UI React `Button`).

The interesting requirement we need to think about here is the order of the style overrides being applied. We'll leverage the [mergeClasses](https://griffel.js.org/react/api/merge-classes) API in Griffel which merges and dedupes the atomic classes generated by [makeStyles](https://griffel.js.org/react/api/make-styles) API.

And, because order is important for `mergeClasses` we'll make sure consumer styles passed in via the `className` prop always win over the default `LaunchButton` styles:
```
const useLaunchButtonStyles = makeStyles({
  launchButton: {
    backgroundColor: "#ce000a",
    ...shorthands.borderColor("transparent"),
    color: tokens.colorBrandBackgroundInverted,
    ":hover": {
      backgroundColor: "#b10007",
      ...shorthands.borderColor("transparent"),
      color: tokens.colorBrandBackgroundInverted
    },
    ":hover:active": {
      backgroundColor: "#5d0b00",
      ...shorthands.borderColor("transparent"),
      color: tokens.colorNeutralForegroundOnBrand
    }
  }
});

export type LaunchButtonProps = Pick<
  React.HTMLAttributes<HTMLElement>,
  "className"
>;

const LaunchButton: React.FC<LaunchButtonProps> = ({ className }) => {
  const l = useLaunchButtonStyles();
  const classes = mergeClasses(l.launchButton, className);

  return (
    <Button
      icon={<RocketRegular />}
      className={classes}
      shape="circular"
      size="large"
    >
      Launch
    </Button>
  );
};

export default LaunchButton;
```

Here's an usage example from a consumer overriding the `LaunchButton` styles:
```
const useLaunchButtonOverrides = makeStyles({
  overrides: {
    backgroundColor: "green"
  }
});

const o = useLaunchButtonOverrides();

<LaunchButton className={o.overrides} />

```

Now, your variant has a similar developer experience as any other Fluent UI React component—a beautiful thing! It also gives the consumer a nice balance of customization and control over UX qualities that need to be coherent and consistent.

Want to know more? Checkout the CodesandBox for the full sample:

{% embed https://codesandbox.io/embed/component-variants-3cxw6k?fontsize=14&hidenavigation=1&theme=dark %}

Let us know what kind of content you'd like to see from Fluent UI React by visiting us on:

GitHub: https://github.com/microsoft/fluentui
Docs: https://react.fluentui.dev
Twitter: https://twitter.com/fluentui

Thanks!