withStyles

It's like the material-ui v4 higher-order component API but type safe by design.

Using as const can often helps when you get red squiggly lines.

MyComponent.tsx
import { withStyles } from "tss-react/mui";
// When using withStyles, if you need to combine classes 
// you can use the clsx package. (But use const { cx } = useStyles()
// with makeStyles!)
import clsx from "clsx";

type Props = {
    className?: string;
    classes?: Partial<Record<"root" | "text", string>>;
    colorSmall: string;
};

function MyComponent(props: Props) {

    const classes = withStyles.getClasses(props);

    return (
      // props.classeName and props.classes.root are merged, props.className get higher specificity
      <div className={classes.root}>
        <span className={classes.text}>The background color should be different when the screen is small.</span>
      </div>
    );
}

const MyComponentStyled = withStyles(
    MyComponent, 
    (theme, props) => ({
        root: {
            backgroundColor: theme.palette.primary.main,
            height: 100
        },
        text: {
            border: "1px solid red"
        },
        "@media (max-width: 960px)": {
            root: {
                backgroundColor: props.colorSmall
            }
        }
    })
);

export default MyComponentStyled;
import MyComponent from "./MyComponent";

render(
    <MyComponent 
       className="foo bar"
       classes={{ text: "baz baz" }} 
       colorSmall="cyan" 
    />
);

If you have your styles defined as a separate function:

MyComponent.tsx
import { withStyles } from "tss-react/mui";
import type { Theme } from '@mui/material';
import clsx from "clsx";

type Props = {
    className?: string;
    classes?: Partial<Record<keyof ReturnType<typeof styles>, string>>;
    colorSmall: string;
};

function MyComponent(props: Props) {

    const classes = withStyles.getClasses(props);

    return (
      // props.classeName and props.classes.root are merged, props.className get higher specificity
      <div className={classes.root}>
        <span className={classes.text}>The background color should be different when the screen is small.</span>
      </div>
    );
}

const styles = (theme: Theme, props: Props) => ({
    root: {
        backgroundColor: theme.palette.primary.main,
        height: 100
    },
    text: {
        border: "1px solid red"
    },
    "@media (max-width: 960px)": {
        root: {
            backgroundColor: props.colorSmall
        }
    }
});

const MyComponentStyled = withStyles(MyComponent, styles);

export default MyComponentStyled;

With no classes props

Your component can also only have a className prop (and no classes).

MyComponent.tsx
import * as React from "react";
import { withStyles }ย from "tss-react/mui";

export type Props ={
  className?: string;
  isBig: boolean;
};

class MyComponent extends React.Component<Props> {
  render() {
  
    const classes = withStyles.getClasses(this.props);

    return (
      <div className={classes.root}>
        The background color should be different when the screen is small.
      </div>
    );
  }
}

const MyComponentStyled = withStyles(
  MyComponent, 
  (theme, props) => ({
      "root": {
          "backgroundColor": theme.palette.primary.main,
          "height": props.isBig ? 100 : 50
      },
      "@media (max-width: 960px)": {
          "root": {
              "backgroundColor": "red"
          }
      }
  })
);

export default MyComponentStyled;
import MyComponent from "./MyComponent";

render(
    <MyComponent 
       className="foo bar"
       colorSmall="cyan" 
    />
);

With a MUI component

You can also pass a mui component like for example <Button /> and you'll be able to overwrite every rule name of the component (it uses the classes prop).

import Button from "@mui/material/Button";
import { withStyles }ย from "tss-react/mui";

const MyStyledButton = withStyles(Button, {
    root: {
        backgroundColor: "grey"
    }
    text: {
        color: "red"
    },
    "@media (max-width: 960px)": {
        text: {
            color: "blue"
        }
    }
});

What's very powerfull about the withStyles API it it's capable to infer the type of the nested overwritable classes, example:

import Breadcrumbs from "@mui/material/Breadcrumbs";
import { withStyles } from "tss-react/mui";

const MyBreadcrumbs = withStyles(
    Breadcrumbs,
    (theme, props, classes) => {
        ol: {
            [`& .${classes.separator}`]: {
                color: theme.palette.primary.main
            }
        }
    }
);

With an base HTML component

import { withStyles } from "tss-react/mui";

const MyAnchorStyled = withStyles("a", (theme, { href }) => ({
    root: {
        border: "1px solid black",
        backgroundColor: href?.startsWith("https")
            ? theme.palette.primary.main
            : "red"
    }
}));

You can experiment with those examples here live here, you can also run it locally with yarn start_spa.

Naming the stylesheets (useful for debugging and theme style overrides)

To ease debugging you can specify a name that will appear in every class names. It is like the option.name in material-ui v4's makeStyles.

It's also required to for theme style overrides.

import { withStyles }ย from "tss-react/mui";

const MyDiv = withStyles("div", {
  root: {
    /* ... */
  }
}, { name: "MyDiv" });

//The class apllied to the div will be like: "css-xxxxxx-MyDiv-root"

Use in place of styled

If you want to use withStyles instead of styled for the extra type safety it provides:

Before:

import { styled } from '@mui/material/styles';
import Popper from '@mui/material/Popper';

const StyledPopper = styled(Popper)({
  border: '1px solid red',
  '& .Mui-autoComplete-listBox': {
    boxSizing: 'border-box',
    '& ul': {
      padding: 0,
      margin: 0
    }
  },
  "@media (max-width: 960px)": {
    color: "blue"
  }
});

After (just wrap everything into root):

import { withStyles } from 'tss-react/mui';
import Popper from '@mui/material/Popper';

const StyledPopper = withStyles(Popper, {
  root: {
    border: '1px solid red',
    '& .Mui-autoComplete-listBox': {
      boxSizing: 'border-box',
      '& ul': {
        padding: 0,
        margin: 0
      }
    },
    "@media (max-width: 960px)": {
      color: "blue"
    }
  }
});

Last updated