UNPKG

materialuiupgraded

Version:

Material-UI's workspace package

434 lines (329 loc) 17 kB
# CSS in JS <p class="description">You can leverage our styling solution, even if you are not using our components.</p> 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](/guides/interoperability/) 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](https://github.com/oliviertassinari/a-journey-toward-better-style) 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](https://medium.com/seek-blog/a-unified-styling-language-d0c208de2660) - [The future of component-based styling](https://medium.freecodecamp.org/css-in-javascript-the-future-of-component-based-styling-70b161a79a32) - [Convert SCSS (Sass) to CSS-in-JS](https://egghead.io/courses/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`](#api) to inject an array of styles into the DOM as CSS, using JSS. Here's an example: {{"demo": "pages/customization/css-in-js/CssInJs.js"}} ## JSS Material-UI's styling solution uses [JSS](https://github.com/cssinjs/jss) at its core. It's a [high performance](https://github.com/cssinjs/jss/blob/master/docs/performance.md) 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](https://github.com/cssinjs/jss/blob/master/docs/plugins.md) 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](http://cssinjs.org/plugins/) provides. Material-UI uses [few of them](#plugins). You can always add new plugins if needed with the [`JssProvider`](https://github.com/cssinjs/react-jss#custom-setup) helper. If you wish to build your own instance of `jss` **and** support *rtl* make sure you also include the [jss-rtl](https://github.com/alitaheri/jss-rtl) plugin. Check the jss-rtl [readme](https://github.com/alitaheri/jss-rtl#simple-usage) 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](/guides/server-rendering/). {{"demo": "pages/customization/css-in-js/JssRegistry.js", "hideEditButton": true}} ## Sheets manager The sheets manager uses a [reference counting](https://en.wikipedia.org/wiki/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](/guides/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: ```css .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](http://cssinjs.org/js-api/#generate-your-own-class-names). ### Global CSS We provide a custom implementation of the class name generator for Material-UI needs: [`createGenerateClassName()`](#creategenerateclassname-options-class-name-generator). 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](http://getbem.com/introduction/) 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: ```jsx 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 `<link>` is injected at the bottom of the `<head>` 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](https://github.com/cssinjs/jss/blob/master/docs/setup.md#specify-dom-insertion-point) to handle this situation. By adjusting the placement of the `insertionPoint` within your HTML head you can [control the order](http://cssinjs.org/js-api/#attach-style-sheets-in-a-specific-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: ```jsx <head> <!-- jss-insertion-point --> <link href="..." /> </head> ``` ```jsx 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](https://github.com/facebook/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 `<noscript>` element: ```jsx <head> <noscript id="jss-insertion-point"></noscript> <link href="..." /> </head> ``` ```jsx 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 `<head>` element. To get around the issue, you can use the JavaScript `document.createComment()` API: ```jsx 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.](/customization/css-in-js/#sheets-registry) - Providing a JSS instance. You might want to support [Right-to-left](/guides/right-to-left/) or changing the [CSS injection order](/customization/css-in-js/#css-injection-order). Read [the JSS documentation](http://cssinjs.org/js-api/) to learn more about the options available. Here is an example: ```jsx 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](http://cssinjs.org/jss-global/) - [jss-nested](http://cssinjs.org/jss-nested/) - [jss-camel-case](http://cssinjs.org/jss-camel-case/) - [jss-default-unit](http://cssinjs.org/jss-default-unit/) - [jss-vendor-prefixer](http://cssinjs.org/jss-vendor-prefixer/) - [jss-props-sort](http://cssinjs.org/jss-props-sort/) It's a subset of [jss-preset-default](http://cssinjs.org/jss-preset-default/). Of course, you are free to add a new plugin. We have one example for the [`jss-rtl` plugin](/guides/right-to-left/#3-jss-rtl). ## 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])](http://cssinjs.org/js-api/#create-style-sheet). #### Returns `higher-order component`: Should be used to wrap a component. #### Examples ```jsx 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](https://babeljs.io/docs/en/babel-plugin-transform-decorators/) like so: ```jsx 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](http://cssinjs.org/js-api/#generate-your-own-class-names). #### 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 ```jsx 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](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce)? 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”](https://reactjs.org/docs/render-props.html) refers to a simple technique for sharing code between React components using a prop whose value is a function. ```jsx // 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> ); } ``` {{"demo": "pages/customization/css-in-js/RenderProps.js"}} You can access the theme the same way you would do it with `withStyles`: ```js const Styled = createStyled(theme => ({ root: { backgroundColor: theme.palette.background.paper, }, })); ``` [@jedwards1211](https://github.com/jedwards1211) Has taken the time to move this module into a package: [material-ui-render-props-styles](https://github.com/jcoreio/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. ```jsx // 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>; } ``` {{"demo": "pages/customization/css-in-js/StyledComponents.js"}} You can access the theme the same way you would do it with `withStyles`: ```js const MyButton = styled(Button)(theme => ({ backgroundColor: theme.palette.background.paper, })); ```