Hướng dẫn css-in js material ui

You can leverage our styling solution, even if you are not using our components.

⚠️ We are working on extracting the styling solution into it's own package: @material-ui/styles. It's an unstable project (alpha version). Hopefully, we will make it the default style implementation for the core components in Material-UI v4.

Material-UI aims to provide strong foundations for building dynamic UIs. For the sake of simplicity, we expose our styling solution to users. You can use it, but you don't have to. This styling solution is interoperable with all the other major solutions.

Material-UI's styling solution

In previous versions, Material-UI has used LESS, then a custom inline-style solution to write the style of the components, but these approaches have proven to be limited. Most recently, we have moved toward a CSS-in-JS solution. It unlocks many great features (theme nesting, dynamic styles, self-support, etc.). We think that it's the future:

  • A Unified Styling Language
  • The future of component-based styling
  • Convert SCSS (Sass) to CSS-in-JS

So, you may have noticed in the demos what CSS-in-JS looks like. We use the higher-order component created by withStyles to inject an array of styles into the DOM as CSS, using JSS. Here's an example:

JSS

Material-UI's styling solution uses JSS at its core. It's a high performance JS to CSS compiler which works at runtime and server-side. It is about 8 kB (minified and gzipped) and is extensible via a plugins API.

If you end up using this styling solution in your codebase, you're going to need to learn the API. The best place to start is by looking at the features that each plugin provides. Material-UI uses few of them. You can always add new plugins if needed with the JssProvider helper.

If you wish to build your own instance of jss and support rtl make sure you also include the jss-rtl plugin. Check the jss-rtl readme to learn how.

Sheets registry

When rendering on the server, you will need to get all rendered styles as a CSS string. The SheetsRegistry class allows you to manually aggregate and stringify them. Read more about Server Rendering.

Sheets manager

The sheets manager uses a reference counting algorithm in order to attach and detach the style sheets only once per (styles, theme) couple. This technique provides an important performance boost when re-rendering instances of a component.

When only rendering on the client, that's not something you need to be aware of. However, when rendering on the server you do. You can read more about Server Rendering.

Class names

You may have noticed that the class names generated by our styling solution are non-deterministic, so you can't rely on them to stay the same. The following CSS won't work:

.MuiAppBar-root-12 {
  opacity: 0.6
}

Instead, you have to use the classes property of a component to override them. On the other hand, thanks to the non-deterministic nature of our class names, we can implement optimizations for development and production. They are easy to debug in development and as short as possible in production:

  • development: .MuiAppBar-root-12
  • production: .jss12

If you don't like this default behavior, you can change it. JSS relies on the concept of class name generator.

Global CSS

We provide a custom implementation of the class name generator for Material-UI needs: createGenerateClassName(). As well as the option to make the class names deterministic with the dangerouslyUseGlobalCSS option. When turned on, the class names will look like this:

  • development: .MuiAppBar-root
  • production: .MuiAppBar-root

⚠️ Be cautious when using dangerouslyUseGlobalCSS. We provide this option as an escape hatch for quick prototyping. Relying on it for code running in production has the following implications:

  • Global CSS is inherently fragile. People use strict methodologies like BEM to workaround the issue.
  • It's harder to keep track of classes API changes.

⚠️ When using dangerouslyUseGlobalCSS standalone (without Material-UI), you should name your style sheets. withStyles has a name option for that:

const Button = withStyles(styles, { name: 'button' })(ButtonBase)

CSS injection order

The CSS injected by Material-UI to style a component has the highest specificity possible as the is injected at the bottom of the to ensure the components always render correctly.

You might, however, also want to override these styles, for example with styled-components. If you are experiencing a CSS injection order issue, JSS provides a mechanism to handle this situation. By adjusting the placement of the insertionPoint within your HTML head 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 that determines where JSS will inject the styles:

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

const generateClassName = createGenerateClassName();
const jss = create({
  ...jssPreset(),
  // We define a custom insertion point that JSS will look for injecting the styles in the DOM.
  insertionPoint: 'jss-insertion-point',
});

function App() {
  return (
    <JssProvider jss={jss} generateClassName={generateClassName}>
      ...
    JssProvider>
  );
}

export default App;

Other HTML element

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

For example, a

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

const generateClassName = createGenerateClassName();
const jss = create({
  ...jssPreset(),
  // We define a custom insertion point that JSS will look for injecting the styles in the DOM.
  insertionPoint: document.getElementById('jss-insertion-point'),
});

function App() {
  return (
    <JssProvider jss={jss} generateClassName={generateClassName}>
      ...
    JssProvider>
  );
}

export default App;

JS createComment

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

import JssProvider from 'react-jss/lib/JssProvider';
import { create } from 'jss';
import { createGenerateClassName, jssPreset } from '@material-ui/core/styles';

const styleNode = document.createComment("jss-insertion-point");
document.head.insertBefore(styleNode, document.head.firstChild);

const generateClassName = createGenerateClassName();
const jss = create({
  ...jssPreset(),
  // We define a custom insertion point that JSS will look for injecting the styles in the DOM.
  insertionPoint: 'jss-insertion-point',
});

function App() {
  return (
    <JssProvider jss={jss} generateClassName={generateClassName}>
      ...
    JssProvider>
  );
}

export default App;

JssProvider

react-jss exposes a JssProvider component to configure JSS for the underlying child components. There are different use cases:

  • Providing a class name generator.
  • Providing a Sheets registry.
  • Providing a JSS instance. You might want to support Right-to-left or changing the CSS injection order. Read the JSS documentation to learn more about the options available. Here is an example:
import JssProvider from 'react-jss/lib/JssProvider';
import { create } from 'jss';
import { createGenerateClassName, jssPreset } from '@material-ui/core/styles';

const generateClassName = createGenerateClassName();
const jss = create(jssPreset());

function App() {
  return (
    <JssProvider jss={jss} generateClassName={generateClassName}>
      ...
    JssProvider>
  );
}

export default App;

Plugins

JSS uses the concept of plugins to extend its core, allowing people to cherry-pick the features they need. You pay the performance overhead for only what's you are using. Given withStyles is our internal styling solution, all the plugins aren't available by default. We have added the following list:

  • jss-global
  • jss-nested
  • jss-camel-case
  • jss-default-unit
  • jss-vendor-prefixer
  • jss-props-sort

It's a subset of jss-preset-default. Of course, you are free to add a new plugin. We have one example for the jss-rtl plugin.

API

withStyles(styles, [options]) => higher-order component

Link a style sheet with a component. It does not modify the component passed to it; instead, it returns a new component with a classes property. This classes object contains the name of the class names injected in the DOM.

Some implementation details that might be interesting to being aware of:

  • It adds a classes property so you can override the injected class names from the outside.
  • It adds an innerRef property so you can get a reference to the wrapped component. The usage of innerRef is identical to ref.
  • It forwards non React static properties so this HOC is more "transparent". For instance, it can be used to defined a getInitialProps() static method (next.js).

Arguments

  1. styles (Function | Object): A function generating the styles or a styles object. It will be linked to the component. Use the function signature if you need to have access to the theme. It's provided as the first argument.
  2. options (Object [optional]):
    • options.withTheme (Boolean [optional]): Defaults to false. Provide the theme object to the component as a property.
    • options.name (String [optional]): The name of the style sheet. Useful for debugging. If the value isn't provided, it will try to fallback to the name of the component.
    • options.flip (Boolean [optional]): When set to false, this sheet will opt-out the rtl transformation. When set to true, the styles are inversed. When set to null, it follows theme.direction.
    • The other keys are forwarded to the options argument of jss.createStyleSheet([styles], [options]).

Returns

higher-order component: Should be used to wrap a component.

Examples

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

const styles = {
  root: {
    backgroundColor: 'red',
  },
};

class MyComponent extends React.Component {
  render () {
    return <div className={this.props.classes.root} />;
  }
}

export default withStyles(styles)(MyComponent);

Also, you can use as decorators like so:

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

const styles = {
  root: {
    backgroundColor: 'red',
  },
};

@withStyles(styles)
class MyComponent extends React.Component {
  render () {
    return <div className={this.props.classes.root} />;
  }
}

export default MyComponent

createGenerateClassName([options]) => class name generator

A function which returns a class name generator function.

Arguments

  1. options (Object [optional]):
    • options.dangerouslyUseGlobalCSS (Boolean [optional]): Defaults to false. Makes the Material-UI class names deterministic.
    • options.productionPrefix (String [optional]): Defaults to 'jss'. The string used to prefix the class names in production.
    • options.seed (String [optional]): Defaults to ''. The string used to uniquely identify the generator. It can be used to avoid class name collisions when using multiple generators.

Returns

class name generator: The generator should be provided to JSS.

Examples

import JssProvider from 'react-jss/lib/JssProvider';
import { createGenerateClassName } from '@material-ui/core/styles';

const generateClassName = createGenerateClassName({
  dangerouslyUseGlobalCSS: true,
  productionPrefix: 'c',
});

function App() {
  return (
    <JssProvider generateClassName={generateClassName}>
      ...
    JssProvider>
  );
}

export default App;

Alternative APIs

Do you think that higher-order components are the new mixins? Rest assured we don't, however because withStyles() is a higher-order component, it can be extended with just a few lines of code to match different patterns that are all idiomatic React. Here are a couple of examples.

Render props API (+11 lines)

The term “render prop” refers to a simple technique for sharing code between React components using a prop whose value is a function.

// You will find the `createStyled` implementation in the source of the demo.
const Styled = createStyled({
  root: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    borderRadius: 3,
    border: 0,
    color: 'white',
    height: 48,
    padding: '0 30px',
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  },
});

function RenderProps() {
  return (
    <Styled>
      {({ classes }) => (
        <Button className={classes.root}>
          {'Render props'}
        Button>
      )}
    Styled>
  );
}

You can access the theme the same way you would do it with withStyles:

const Styled = createStyled(theme => ({
  root: {
    backgroundColor: theme.palette.background.paper,
  },
}));

@jedwards1211 Has taken the time to move this module into a package: material-ui-render-props-styles. Feel free to use it.

styled-components API (+15 lines)

styled-components's API removes the mapping between components and styles. Using components as a low-level styling construct can be simpler.

// You will find the `styled` implementation in the source of the demo.
// You can even write CSS with https://github.com/cssinjs/jss-template.
const MyButton = styled(Button)({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  borderRadius: 3,
  border: 0,
  color: 'white',
  height: 48,
  padding: '0 30px',
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
});

function StyledComponents() {
  return <MyButton>{'Styled Components'}MyButton>;
}

You can access the theme the same way you would do it with withStyles:

const MyButton = styled(Button)(theme => ({
  backgroundColor: theme.palette.background.paper,
}));