materialuiupgraded
Version:
Material-UI's workspace package
434 lines (329 loc) • 17 kB
Markdown
# 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 '-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 '-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 '-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 '-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 '-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 '-ui/core/styles';
const styles = {
root: {
backgroundColor: 'red',
},
};
(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 '-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,
},
}));
```
[](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,
}));
```