question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[RFC] Support custom variants in the theme

See original GitHub issue

Summary

This RFC is proposing a solution for adding custom variants for the core components inside the theme. We already have an option for adding custom overrides inside the theme, with this RFC we want to extend it to support custom variants as well.

Basic example

The API could look something like this:

const theme = outerTheme => createMuiTheme({
  variants: {
    MuiTypography: [
      {
        props: { variant: 'headline1' }, // combination of props for which the styles will be applied
        styles: {
          padding: '5px 15px',
          border: `5px dashed ${ ${outerTheme.palette.primary.main}}`,
        },
      },
      {
        props: { variant: 'headline1', color: 'secondary' },
        styles: {
          padding: '5px 15px',
          border: `5px dashed ${outerTheme.palette.secondary.main}`,
        },
      },
    ],
  },
});

declare module '@material-ui/core/Typography/Typography' {
  interface TypographyPropsVariantOverrides {
    headline1: true;
    h1: false; // variant="h1" is no longer available
  }
}

<Typography variant="headline1" color="secondary" />

Motivation

From the developer’s survey, the 3rd most popular use case for Material-UI is to build a custom design system on top of it. This proposal is meant to make it easier. Currently developers can add new props combination by creating wrapper components:

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import { deepmerge } from "@material-ui/utils";
import MuiButton, {
  ButtonProps as MuiButtonProps
} from "@material-ui/core/Button";

type ButtonProps = Omit<MuiButtonProps, "variant"> & {
  variant: "text" | "outlined" | "contained" | "dashed";
};

const useStyles = makeStyles(theme => ({
  root: ({ variant }: ButtonProps) => ({
    ...(variant === "dashed" && {
      border: "2px dashed grey"
    })
  })
}));

const Button: React.FC<ButtonProps> = props => {
  const variantClassses = useStyles(props);
  const { classes: propsClasses, variant: propsVariant, ...rest } = props;

  const classes = deepmerge(variantClassses, propsClasses);
  const variant = propsVariant !== "dashed" ? propsVariant : undefined;

  return <MuiButton classes={classes} variant={variant} {...rest} />;
};

export default function App() {
  return (
    <Button variant="dashed" color="secondary">
      Custom variant
    </Button>
  );
}

Adding and removing variants from Material-UI components creates a challenge. You have to document these variants as well as making sure they will be used correctly. Solving the issue at the documentation level will likely require making progress on #21111.

While this option is already available, we have heard pushbacks from the community around it.

  • #15573: 38 upvotes, proposed solution with wrapper: 0 reactions.
  • #15573: “ability to use theme to a greater extent”
  • #15454: “prevents you from having to repeat class implementations (or create yet another file in /components/)”
  • #8498: “New Typography variants”

The issues with the wrapper path are:

  • It’s more effort. You have to 1. write the component, 2. update all the codebase to migrate the usage to the new component, 3. put constraints in place to make sure the other developers on the project will only use the wrapper component. Some teams decide to pay the cost upfront, start by wrapping all the Material-UI components.
  • The wrapper component approach doesn’t play nicely with our goal to provide different themes. If we look at how it’s working with jQuery and static templates, the class names are constraints your put in your codebase, token that then can be targeted to apply customization, like a custom Bootstrap theme. You don’t add new class names before reusing the existing ones. Shouldn’t it be the same in the React era? The components could be the new class names. You add them once, you are set.

In the long run, it could be ideal if we can implement the Material Design light and dark themes with this approach alone.

Detailed design

PR https://github.com/mui-org/material-ui/pull/21648 is implementing this feature for the Button component. This is how it can be used:

import React from 'react';
import {
  createMuiTheme,
  makeStyles,
  ThemeProvider,
} from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const theme = outerTheme => createMuiTheme({
  variants: {
    MuiButton: [
      {
        props: { variant: 'dashed' },
        styles: {
          padding: '5px 15px',
          border: `5px dashed ${ ${outerTheme.palette.primary.main}}`,
        },
      },
      {
        props: { variant: 'dashed', color: 'secondary' },
        styles: {
          padding: '5px 15px',
          border: `5px dashed ${outerTheme.palette.secondary.main}`,
        },
      },
    ],
  },
});

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button variant="dashed" color="secondary">
        Custom variant
      </Button>
    <ThemeProvider>
  );
}

The typescript users, can use module augmentation for defining their new variants types:

declare module '@material-ui/core/Button/Button' {
  interface ButtonPropsVariantOverrides {
    dashed: true;
  }
}

Drawbacks

Always with new API we have to consider also the drawbacks of adding it. Here are some points:

  • the proposed feature can already be implemented in userspace (with wrapper components)
  • clients will need to learn new API, while longer-term, this API could be also used by design tools to customize Material-UI components.
  • there may be some necessary changes per component, regarding the classKey definition
  • there has to be per component validation and implementation of the feature

Alternatives

This can be implemented with wrapper components. Another idea that we tried was, relaxing the typings of the overrides key, and allowing users to specify the new classKeys directly there - this will mean that clients need to know how the props are converted to classes keys inside each component.

Adoption strategy

As this is a new API, the adoption can be straight forward for the users.

Unresolved questions

One thing that we need to decide on whether to support slots styles inside the defining, for example, defining the styles of the root and label slots in the Button.

const theme = outerTheme => createMuiTheme({
  variants: {
    MuiButton: [
      {
        props: { variant: 'dashed' },
        styles: {
          root: {
            padding: '5px 15px',
            border: `5px dashed ${ ${outerTheme.palette.primary.main}}`,
          },
          label: {
            color: outerTheme.palette.primary.main;
          }
        },
      },
      {
        props: { variant: 'dashed', color: 'secondary' },
        styles: {
          root: {
            padding: '5px 15px',
            border: `5px dashed ${ ${outerTheme.palette.secondary.main}}`,
          },
          label: {
            color: outerTheme.palette.secondary.main;
          },
        },
      },
    ],
  },
});

This may require changes in the components implementation and adding some new classKeys that will support this API.

Progress

Here is a list of all components that would benefit from this API. This list will help us track the progress of where the API is implemented.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:36
  • Comments:38 (30 by maintainers)

github_iconTop GitHub Comments

2reactions
mnajdovacommented, Nov 16, 2021

@EliasJorgensen we are discussing this currently with the development of the new design system cc @siriwatknp In some components the variant is used for more than styling purposes, so we cannot reuse it. I would propose in the meantime using some data-* attribute, for example, something like - https://codesandbox.io/s/jolly-waterfall-6piyj?file=/src/App.tsx or use a wrapper component.

2reactions
andrey1andreycommented, Oct 19, 2020

Hi. Is it possible to get support for ‘Card’ component too ? At the moment I don’t see this component in the list of WIP

Read more comments on GitHub >

github_iconTop Results From Across the Web

[RFC] Support custom variants in the theme #21749 - GitHub
This RFC is proposing a solution for adding custom variants for the core components inside the theme. We already have an option for...
Read more >
Olivier Tassinari on Twitter: "We have a new RFC on extending the ...
We have a new RFC on extending the theme to support custom variants. We would love your feedback on it. https://github.com/mui-org/material-ui/issues/21749…
Read more >
Creating custom variants with Material-UI - Stack Overflow
In Material-UI v5, you can easily create a new variants for your components (See the list of supported components in this RFC) using ......
Read more >
Themes in UI Builder - Product Documentation | ServiceNow
Themes enable you to change the visual style of your app's experiences so that they express the look and feel of your brand....
Read more >
Support product variants - Shopify.dev
To support variants in your theme, you need to implement the following components: Variant deep link handling: A variant can be linked to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found