Saltar al contenido

Avanzado

This section covers more advanced usage of @material-ui/core/styles.

Temática

Add a ThemeProvider to the top level of your app to pass a theme down the React component tree. Then, you can access the theme object in style functions.

Este ejemplo crea un objeto de tema para componentes construidos a medida. Si pretende utilizar algunos de los componentes de Material-UI, necesita proporcionar una estructura de tema más rica utilizando el método createTheme(). Dirígete a la sección temática para aprender cómo construir tu tema personalizado de Material-UI.

import { ThemeProvider } from '@material-ui/core/styles';
import DeepChild from './my_components/DeepChild';

const theme = {
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
};

function Theming() {
  return (
    <ThemeProvider theme={theme}>
      <DeepChild />
    </ThemeProvider>
  );
}
<ThemeProvider theme={themeInstance}>
  <DeepChild />
</ThemeProvider>

Accediendo al tema en un componente

You might need to access the theme variables inside your React components.

useTheme hook

For use in function components:

import { useTheme } from '@material-ui/core/styles';

function DeepChild() {
  const theme = useTheme();
  return <span>{`spacing ${theme.spacing}`}</span>;
}
spacing 8px
<ThemeProvider
  theme={{
    spacing: '8px',
  }}
>
  <DeepChild />
</ThemeProvider>

withTheme HOC

For use in class or function components:

import { withTheme } from '@material-ui/core/styles';

function DeepChildRaw(props) {
  return <span>{`spacing ${props.theme.spacing}`}</span>;
}

const DeepChild = withTheme(DeepChildRaw);
spacing 8px

Theme nesting

Usted puedes anidar multiples proveedores de tema. This can be really useful when dealing with different areas of your application that have distinct appearance from each other.

<ThemeProvider theme={outerTheme}>
  <Child1 />
  <ThemeProvider theme={innerTheme}>
    <Child2 />
  </ThemeProvider>
</ThemeProvider>


El tema interno sobrescribirá el tema exterior. Puede ampliar el tema externo proporcionando una función:

<ThemeProvider theme={} <ThemeProvider theme={} >
  <Child1 />
  <ThemeProvider theme={outerTheme => ({ darkMode: true, ...outerTheme })}>
    <Child2 />
  </ThemeProvider>
</ThemeProvider>

Overriding styles - classes prop

The makeStyles (hook generator) and withStyles (HOC) APIs allow the creation of multiple style rules per style sheet. Each style rule has its own class name. The class names are provided to the component with the classes variable. This is particularly useful when styling nested elements in a component.

// A style sheet
const useStyles = makeStyles({
  root: {}, // a style rule
  label: {}, // a nested style rule
});

function Nested(props) {
  const classes = useStyles();
  return (
    <button className={classes.root}> // 'jss1'
      <span className={classes.label}> // 'jss2'
        nested
      </span>
    </button>
  );
}

function Parent() {
  return <Nested />
}

However, the class names are often non-deterministic. How can a parent component override the style of a nested element?

withStyles

This is the simplest case. the wrapped component accepts a classes prop, it simply merges the class names provided with the style sheet.

const Nested = withStyles({
  root: {}, // a style rule
  label: {}, // a nested style rule
})(({ classes }) => (
  <button className={classes.root}>
    <span className={classes.label}> // 'jss2 my-label'
      Nested
    </span>
  </button>
));

function Parent() {
  return <Nested classes={{ label: 'my-label' }} />
}

makeStyles

The hook API requires a bit more work. You have to forward the parent props to the hook as a first argument.

const useStyles = makeStyles({
  root: {}, // a style rule
  label: {}, // a nested style rule
});

function Nested(props) {
  const classes = useStyles(props);
  return (
    <button className={classes.root}>
      <span className={classes.label}> // 'jss2 my-label'
        nested
      </span>
    </button>
  );
}

function Parent() {
  return <Nested classes={{ label: 'my-label' }} />
}

JSS plugins

JSS uses plugins to extend its core, allowing you to cherry-pick the features you need, and only pay the performance overhead for what you are using.

Not all the plugins are available in Material-UI by default. The following (which is a subset of jss-preset-default) are included:

Of course, you are free to use additional plugins. Here is an example with the jss-rtl plugin.

import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import rtl from 'jss-rtl'

const jss = create({
  plugins: [...jssPreset().plugins, rtl()],
});

export default function App() {
  return (
    <StylesProvider jss={jss}>
      ... </StylesProvider>
  );
}

String templates

If you prefer CSS syntax over JSS, you can use the jss-plugin-template plugin.

const useStyles = makeStyles({
  root: `
    background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%);
    border-radius: 3px;
    font-size: 16px;
    border: 0;
    color: white;
    height: 48px;
    padding: 0 30px;
    box-shadow: 0 3px 5px 2px rgba(255, 105, 135, 0.3);
  `,
});

Note that this doesn't support selectors, or nested rules.

CSS injection order

It's really important to understand how the CSS specificity is calculated by the browser, as it's one of the key elements to know when overriding styles. You are encouraged to read this MDN paragraph: How is specificity calculated?

By default, the style tags are injected last in the <head> element of the page. They gain more specificity than any other style tags on your page e.g. CSS modules, styled components.

injectFirst

The StylesProvider component has an injectFirst prop to inject the style tags first in the head (less priority):

import { StylesProvider } from '@material-ui/core/styles';

<StylesProvider injectFirst>
  {/* Your component tree.
      */}
</StylesProvider>
      import { StylesProvider } from '@material-ui/core/styles';

<StylesProvider injectFirst>
  {/* Your component tree. */}
</StylesProvider>
      Now, you can override Material-UI's styles.

makeStyles / withStyles / styled

The injection of style tags happens in the same order as the makeStyles / withStyles / styled invocations. For instance the color red wins in this case:

import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';

const useStylesBase = makeStyles({
  root: {
    color: 'blue', // 🔵
  },
});

const useStyles = makeStyles({
  root: {
    color: 'red', // 🔴
  },
});

export default function MyComponent() {
  // Order doesn't matter
  const classes = useStyles();
  const classesBase = useStylesBase();

  // Order doesn't matter
  const className = clsx(classes.root, classesBase.root)

  // color: red 🔴 wins.
  However, the class names are often non-deterministic.

The hook call order and the class name concatenation order don't matter.

insertionPoint

JSS provides a mechanism to control this situation. By adding an insertionPoint within the HTML you can control the order that the CSS rules are applied to your components.

HTML comment

The simplest approach is to add an HTML comment to the <head> that determines where JSS will inject the styles:

<head>
  <noscript id="jss-insertion-point" />
  <link href="..." />
</head>
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';

const jss = create({
  ...jssPreset(),
  // Define a custom insertion point that JSS will look for when injecting the styles into the DOM.
  insertionPoint: 'jss-insertion-point',
});

export default function App() {
  return <StylesProvider jss={jss}>...</StylesProvider>;
}

Other HTML elements

Create React App strips HTML comments when creating the production build. To get around this issue, you can provide a DOM element (other than a comment) as the JSS insertion point, for example, a <noscript> element:

<head>
  <!-- jss-insertion-point -->
  <link href="...">
</head> />
</head>
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';

const jss = create({
  ...jssPreset(),
  // Define a custom insertion point that JSS will look for when injecting the styles into the DOM.
  insertionPoint: 'jss-insertion-point',
});

export default function App() {
  return <StylesProvider jss={jss}>...</StylesProvider>;
}

JS createComment

codesandbox.io prevents access to the <head> element. To get around this issue, you can use the JavaScript document.createComment() API:

import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import rtl from 'jss-rtl'

const jss = create({
  plugins: [...jssPreset().plugins, rtl()],
});

export default function App() {
  return (
    <StylesProvider jss={jss}>
      ...
  insertionPoint: 'jss-insertion-point',
});

export default function App() {
  return <StylesProvider jss={jss}>...</StylesProvider>;
}

Server-side rendering

This example returns a string of HTML and inlines the critical CSS required, right before it's used:

import ReactDOMServer from 'react-dom/server';
import { ServerStyleSheets } from '@material-ui/core/styles';

function render() {
  const sheets = new ServerStyleSheets();

  const html = ReactDOMServer.renderToString(sheets.collect(<App />));
  const css = sheets.toString();

  return `
<!DOCTYPE html>
<html>
  <head>
    <style id="jss-server-side">${css}</style>
  </head>
  <body>
    <div id="root">${html}</div>
  </body>
</html>
  `;
}

You can follow the server side guide for a more detailed example, or read the ServerStyleSheets API documentation.

Gatsby

There is an official Gatsby plugin that enables server-side rendering for @material-ui/styles. Refer to the plugin's page for setup and usage instructions.

Refer to this example Gatsby project for an up-to-date usage example.

Next.js

You need to have a custom pages/_document.js, then copy this logic to inject the server-side rendered styles into the <head> element.

Refer to this example project for an up-to-date usage example.

Class names

The class names are generated by the class name generator.

Por defecto

By default, the class names generated by @material-ui/core/styles are non-deterministic; you can't rely on them to stay the same. Let's take the following style as an example:

const useStyles = makeStyles({
  root: {
    opacity: 1,
  },
});

This will generate a class name such as makeStyles-root-123.

You have to use the classes prop of a component to override the styles. The non-deterministic nature of the class names enables style isolation.

  • In development, the class name is: .makeStyles-root-123, following this logic:
const sheetName = 'makeStyles';
const ruleName = 'root';
const identifier = 123;

const className = `${sheetName}-${ruleName}-${identifier}`;
  • In production, the class name is: .jss123, following this logic:
const productionPrefix = 'jss';
const identifier = 123;

const className = `${productionPrefix}-${identifier}`;

With @material-ui/core

The generated class names of the @material-ui/core components behave differently. When the following conditions are met, the class names are deterministic:

  • Only one theme provider is used (No theme nesting)
  • The style sheet has a name that starts with Mui (all Material-UI components).
  • The disableGlobal option of the class name generator is false (the default).

These conditions are met with the most common use cases of @material-ui/core. For instance, this style sheet:

const useStyles = makeStyles({
  root: { /* … */ },
  label: { /* … */ },
  outlined: {
    /* … */
    '&$disabled': { /* … */ },
  },
  outlinedPrimary: {
    /* … */
    '&:hover': { /* … */ },
  },
  disabled: {},
}, { name: 'MuiButton' });

generates the following class names that you can override:

.MuiButton-root { /* … */ }
.MuiButton-label { /* … */ }
.MuiButton-outlined { /* … */ }
.MuiButton-outlined.Mui-disabled { /* … */ }
.MuiButton-outlinedPrimary: { /* … */ }
.MuiButton-outlinedPrimary:hover { /* … */
}

This is a simplification of the @material-ui/core/Button component's style sheet.

Customization of the TextField can be cumbersome with the classes API, where you have to define the the classes prop. It's easier to use the default values, as described above. For example:

import styled from 'styled-components';
import { TextField } from '@material-ui/core';

const StyledTextField = styled(TextField)`
  label.focused {
    color: green; 💚
  }
  .MuiOutlinedInput-root {
    fieldset {
      border-color: red; 💔
    }
    &:hover fieldset {
      border-color: yellow; 💛
    }
    &.Mui-focused fieldset {
      border-color: green; 💚
    }
  }
`;
<NoSsr>
  <StyledTextField
    label="Deterministic"
    variant="outlined"
    id="deterministic-outlined-input"
  />
</NoSsr>

Global CSS

jss-plugin-global

The jss-plugin-global plugin is installed in the default preset. You can use it to define global class names.

<div className="cssjss-advanced-global-child" />

Hybrid

You can also combine JSS generated class names with global ones.

<div className="child" />

CSS prefixes

JSS uses feature detection to apply the correct prefixes. Don't be surprised if you can't see a specific prefix in the latest version of Chrome. Your browser probably doesn't need it.