- Basic Rules
- Component Definition
- Project Organization
- Class vs
React.createClassvs Stateless - Mixins
- Ordering
isMounted- Use ES6 Classes
- Component Method and Property Ordering
- Name Handlers
handleEventName - Name Handlers in Props
onEventName - Open Elements on the Same Line
- Align and Sort HTML Properties
- Only Export a Single React Class
- Make Presentation Components Pure
- Prefer Props to State
- Never Store State in the DOM
- Use Flow Instead of PropTypes
- Annotate
children - Props Must Be Plain JSON
- Pure Functions of Props and State
- 80 Columns, Soft Tabs of 2 Spaces
- Camel Case Instead of Dash-Case for Class Names
- Never Use ID and Tag Name as Root Selectors
- When Using Multiple Selectors, Give Each Selector Its Own Line
- Break Lines in CSS Function Arguments
- When writing rules, be sure to
- The Parent Constrains the Child
- The Parent Doesn’t Assume Child Structure
- Components Never Leak Margin
- The Parent Spaces the Children
- Only include one React component per file.
- However, multiple Stateless, or Pure, Components are allowed per file. eslint:
react/no-multi-comp.
- However, multiple Stateless, or Pure, Components are allowed per file. eslint:
- Always use JSX syntax.
- Do not use
React.createElementunless you’re initializing the app from a file that is not JSX. react/forbid-prop-typeswill allowarraysandobjectsonly if it is explicitly noted whatarrayandobjectcontains, usingarrayOf,objectOf, orshape.
All components (presentation, containers or pages) should always be
defined as a directory, named with pascal casing. The main component file
should be index.js, main stylesheet style.css. CSS custom properties
can be kept in properties.css:
AwesomeCard/
├── index.js
├── properties.css
└── style.css
- Styles should always be defined in a separate CSS file
- Avoid prefixing or suffixing component names
- E.g.:
lib/pages/UserPageorlib/container/UserContainer
- E.g.:
- On conflict rename on import time
import UserContainer from '...'-import { User as UserContainer } from '...'
Your project components should be separated in at least three directories:
awesome-react-project/
└── src/
├── components/
├── containers/
└── pages/
Each of these directories have special types of components:
Stateless components. Shouldn't store state. Most components in this directory will be function-based components. Stuff like buttons, inputs, labels and all presentational components goes here. This components can also accept functions as props and dispatch events, but no state should be held inside.
Container components can store state. Containers are built mostly from the composition of presentational components with some styles to layout them together. Containers can also store internal state and access refs to provide additional logic, but all actions should be accepted as component callbacks.
Page components can store state, receive route parameters and dispatch Redux actions when applicable. Pages are the highest level of application's components. They represent the application routes and most times are displayed by a router. Pages are also responsible for handling container components callbacks and flowing data into children containers.
-
If you have internal state and/or refs, prefer
class extends React.ComponentoverReact.createClass. eslint:react/prefer-es6-classreact/prefer-stateless-function// bad const Listing = React.createClass({ // ... render() { return <div>{this.state.hello}</div>; }, }); // good class Listing extends React.Component { // ... render() { return <div>{this.state.hello}</div>; } }
And if you don’t have state or refs, prefer normal functions (not arrow functions) over classes:
// bad class Listing extends React.Component { render() { return <div>{this.props.hello}</div>; } } // bad (relying on function name inference is discouraged) const Listing = ({ hello }) => <div>{hello}</div>; // good function Listing({ hello }) { return <div>{hello}</div>; }
Why? Mixins introduce implicit dependencies, cause name clashes, and cause snowballing complexity. Most use cases for mixins can be accomplished in better ways via components, higher-order components, or utility modules.
-
Extensions: Use
.jsxextension for React components. eslint:react/jsx-filename-extension -
Filename: Use PascalCase for filenames. E.g.,
ReservationCard.jsx. -
Reference Naming: Use PascalCase for React components and camelCase for their instances. eslint:
react/jsx-pascal-case// bad import reservationCard from "./ReservationCard"; // good import ReservationCard from "./ReservationCard"; // bad const ReservationItem = <ReservationCard />; // good const reservationItem = <ReservationCard />;
-
Component Naming: Use the filename as the component name. For example,
ReservationCard.jsxshould have a reference name ofReservationCard. However, for root components of a directory, useindex.jsxas the filename and use the directory name as the component name:// bad import Footer from "./Footer/Footer"; // bad import Footer from "./Footer/index"; // good import Footer from "./Footer";
-
Higher-order Component Naming: Use a composite of the higher-order component’s name and the passed-in component’s name as the
displayNameon the generated component. For example, the higher-order componentwithFoo(), when passed a componentBarshould produce a component with adisplayNameofwithFoo(Bar).Why? A component’s
displayNamemay be used by developer tools or in error messages, and having a value that clearly expresses this relationship helps people understand what is happening.// bad export default function withFoo(WrappedComponent) { return function WithFoo(props) { return <WrappedComponent {...props} foo />; } } // good export default function withFoo(WrappedComponent) { function WithFoo(props) { return <WrappedComponent {...props} foo />; } const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; WithFoo.displayName = `withFoo(${wrappedComponentName})`; return WithFoo; }
-
Props Naming: Avoid using DOM component prop names for different purposes.
Why? People expect props like
styleandclassNameto mean one specific thing. Varying this API for a subset of your app makes the code less readable and less maintainable, and may cause bugs.// bad <MyComponent style="fancy" /> // bad <MyComponent className="fancy" /> // good <MyComponent variant="fancy" />
-
Do not use
displayNamefor naming components. Instead, name the component by reference.// bad export default React.createClass({ displayName: 'ReservationCard', // stuff goes here }); // good export default class ReservationCard extends React.Component { }
-
Follow these alignment styles for JSX syntax. eslint:
react/jsx-closing-bracket-locationreact/jsx-closing-tag-location// bad <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // good <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // if props fit in one line then keep it on the same line <Foo bar="bar" /> // children get indented normally <Foo superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> </Foo> // bad {showButton && <Button /> } // bad { showButton && <Button /> } // good {showButton && ( <Button /> )} // good {showButton && <Button />} // good {someReallyLongConditional && anotherLongConditional && ( <Foo superLongParam="bar" anotherSuperLongParam="baz" /> ) } // good {someConditional ? ( <Foo /> ) : ( <Foo superLongParam="bar" anotherSuperLongParam="baz" /> )}
-
Always use double quotes (
") for JSX attributes, but single quotes (') for all other JS. eslint:jsx-quotesWhy? Regular HTML attributes also typically use double quotes instead of single, so JSX attributes mirror this convention.
// bad <Foo bar='bar' /> // good <Foo bar="bar" /> // bad <Foo style={{ left: "20px" }} /> // good <Foo style={{ left: '20px' }} />
-
Always include a single space in your self-closing tag. eslint:
no-multi-spaces,react/jsx-tag-spacing// bad <Foo/> // very bad <Foo /> // bad <Foo /> // good <Foo />
-
Do not pad JSX curly braces with spaces. eslint:
react/jsx-curly-spacing// bad <Foo bar={ baz } /> // good <Foo bar={baz} />
-
Always use camelCase for prop names, or PascalCase if the prop value is a React component.
// bad <Foo UserName="hello" phone_number={12345678} /> // good <Foo userName="hello" phoneNumber={12345678} Component={SomeComponent} />
-
Omit the value of the prop when it is explicitly
true. eslint:react/jsx-boolean-value// bad <Foo hidden={true} /> // good <Foo hidden /> // good <Foo hidden />
-
Always include an
altprop on<img>tags. If the image is presentational,altcan be an empty string or the<img>must haverole="presentation". eslint:jsx-a11y/alt-text// bad <img src="hello.jpg" /> // good <img src="hello.jpg" alt="Me waving hello" /> // good <img src="hello.jpg" alt="" /> // good <img src="hello.jpg" role="presentation" />
-
Do not use words like "image", "photo", or "picture" in
<img>altprops. eslint:jsx-a11y/img-redundant-altWhy? Screenreaders already announce
imgelements as images, so there is no need to include this information in the alt text.// bad <img src="hello.jpg" alt="Picture of me waving hello" /> // good <img src="hello.jpg" alt="Me waving hello" />
-
Use only valid, non-abstract ARIA roles. eslint:
jsx-a11y/aria-role// bad - not an ARIA role <div role="datepicker" /> // bad - abstract ARIA role <div role="range" /> // good <div role="button" />
-
Do not use
accessKeyon elements. eslint:jsx-a11y/no-access-key
Why? Inconsistencies between keyboard shortcuts and keyboard commands used by people using screenreaders and keyboards complicate accessibility.
// bad
<div accessKey="h" />
// good
<div />- Avoid using an array index as
keyprop, prefer a stable ID. eslint:react/no-array-index-key
Why? Not using a stable ID is an anti-pattern because it can negatively impact performance and cause issues with component state.
We don’t recommend using indexes for keys if the order of items may change.
// bad
{
todos.map((todo, index) => <Todo {...todo} key={index} />);
}
// good
{
todos.map((todo) => <Todo {...todo} key={todo.id} />);
}- Always define explicit defaultProps for all non-required props.
Why? propTypes are a form of documentation, and providing defaultProps means the reader of your code doesn’t have to assume as much. In addition, it can mean that your code can omit certain type checks.
// bad
function SFC({ foo, bar, children }) {
return (
<div>
{foo}
{bar}
{children}
</div>
);
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
// good
function SFC({ foo, bar, children }) {
return (
<div>
{foo}
{bar}
{children}
</div>
);
}
SFC.propTypes = {
foo: PropTypes.number.isRequired,
bar: PropTypes.string,
children: PropTypes.node,
};
SFC.defaultProps = {
bar: "",
children: null,
};- Use spread props sparingly.
Why? Otherwise you’re more likely to pass unnecessary props down to components. And for React v15.6.1 and older, you could pass invalid HTML attributes to the DOM.
Exceptions:
- HOCs that proxy down props and hoist propTypes
function HOC(WrappedComponent) {
return class Proxy extends React.Component {
Proxy.propTypes = {
text: PropTypes.string,
isLoading: PropTypes.bool
};
render() {
return <WrappedComponent {...this.props} />
}
}
}- Spreading objects with known, explicit props. This can be particularly useful when testing React components with Mocha’s beforeEach construct.
export default function Foo {
const props = {
text: '',
isPublished: false
}
return (<div {...props} />);
}Notes for use: Filter out unnecessary props when possible. Also, use prop-types-exact to help prevent bugs.
// bad
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...this.props} />
}
// good
render() {
const { irrelevantProp, ...relevantProps } = this.props;
return <WrappedComponent {...relevantProps} />
}-
Always use ref callbacks. eslint:
react/no-string-refs// bad <Foo ref="myRef" /> // good <Foo ref={(ref) => { this.myRef = ref; }} />
-
Wrap JSX tags in parentheses when they span more than one line. eslint:
react/jsx-wrap-multilines// bad render() { return <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent>; } // good render() { return ( <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent> ); } // good, when single line render() { const body = <div>hello</div>; return <MyComponent>{body}</MyComponent>; }
-
Always self-close tags that have no children. eslint:
react/self-closing-comp// bad <Foo variant="stuff"></Foo> // good <Foo variant="stuff" />
-
If your component has multiline properties, close its tag on a new line. eslint:
react/jsx-closing-bracket-location// bad <Foo bar="bar" baz="baz" /> // good <Foo bar="bar" baz="baz" />
-
Use arrow functions to close over local variables. It is handy when you need to pass additional data to an event handler. Although, make sure they do not massively hurt performance, in particular when passed to custom components that might be PureComponents, because they will trigger a possibly needless rerender every time.
function ItemList(props) { return ( <ul> {props.items.map((item, index) => ( <Item key={item.key} onClick={(event) => { doSomethingWith(event, item.name, index); }} /> ))} </ul> ); }
-
Bind event handlers for the render method in the constructor. eslint:
react/jsx-no-bindWhy? A bind call in the render path creates a brand new function on every single render. Do not use arrow functions in class fields, because it makes them challenging to test and debug, and can negatively impact performance, and because conceptually, class fields are for data, not logic.
// bad class extends React.Component { onClickDiv() { // do stuff } render() { return <div onClick={this.onClickDiv.bind(this)} />; } } // very bad class extends React.Component { onClickDiv = () => { // do stuff } render() { return <div onClick={this.onClickDiv} /> } } // good class extends React.Component { constructor(props) { super(props); this.onClickDiv = this.onClickDiv.bind(this); } onClickDiv() { // do stuff } render() { return <div onClick={this.onClickDiv} />; } }
-
Do not use underscore prefix for internal methods of a React component.
Why? Underscore prefixes are sometimes used as a convention in other languages to denote privacy. But, unlike those languages, there is no native support for privacy in JavaScript, everything is public. Regardless of your intentions, adding underscore prefixes to your properties does not actually make them private, and any property (underscore-prefixed or not) should be treated as being public. See issues #1024, and #490 for a more in-depth discussion.
// bad React.createClass({ _onClickSubmit() { // do stuff }, // other stuff }); // good class extends React.Component { onClickSubmit() { // do stuff } // other stuff }
-
Be sure to return a value in your
rendermethods. eslint:react/require-render-return// bad render() { (<div />); } // good render() { return (<div />); }
- Ordering for
class extends React.Component:
- optional
staticmethods constructorgetChildContextcomponentWillMountcomponentDidMountcomponentWillReceivePropsshouldComponentUpdatecomponentWillUpdatecomponentDidUpdatecomponentWillUnmount- event handlers starting with 'handle' like
handleSubmit()orhandleChangeDescription() - event handlers starting with 'on' like
onClickSubmit()oronChangeDescription() - getter methods for
renderlikegetSelectReason()orgetFooterContent() - optional render methods like
renderNavigation()orrenderProfilePicture() render
-
How to define
propTypes,defaultProps,contextTypes, etc...import React from "react"; import PropTypes from "prop-types"; const propTypes = { id: PropTypes.number.isRequired, url: PropTypes.string.isRequired, text: PropTypes.string, }; const defaultProps = { text: "Hello World", }; class Link extends React.Component { static methodsAreOk() { return true; } render() { return ( <a href={this.props.url} data-id={this.props.id}> {this.props.text} </a> ); } } Link.propTypes = propTypes; Link.defaultProps = defaultProps; export default Link;
-
Ordering for
React.createClass: eslint:react/sort-comp
displayNamepropTypescontextTypeschildContextTypesmixinsstaticsdefaultPropsgetDefaultPropsgetInitialStategetChildContextcomponentWillMountcomponentDidMountcomponentWillReceivePropsshouldComponentUpdatecomponentWillUpdatecomponentDidUpdatecomponentWillUnmount- clickHandlers or eventHandlers like
onClickSubmit()oronChangeDescription() - getter methods for
renderlikegetSelectReason()orgetFooterContent() - optional render methods like
renderNavigation()orrenderProfilePicture() render
- Do not use
isMounted. eslint:react/no-is-mounted
Why?
isMountedis an anti-pattern, is not available when using ES6 classes, and is on its way to being officially deprecated.
-
Use static properties for
defaultProps. -
Use an instance property for
state. -
Use flow for
propsandstate. -
Autobind event handlers and callbacks.
Example:
import React, { Component } from "react"; class Foo extends Component { static defaultProps = {}; state = {}; state: {}; props: {}; handleClick = (e) => { // handle the click }; }
-
If
statedepends onprops, define it in the constructor.Example:
class Bar extends Component { constructor(props) { super(props); // must be called first this.state = { value: props.value, }; } }
-
Use higher order components instead of mixins. ES6 style classes do not support mixins. See the mixins considered harmful blog post for details of how to convert mixins to higher order components.
Codemods
Converting legacy components to use ES2015 classes? There's a codemod for that.
Convert one or more files with the following command:
$ tools/react-codemod.js class path/to/file path/to/other/fileOrdering within a React component is strict. The following example illustrates the precise ordering of various component methods and properties:
class Foo extends Component {
// Static properties
static defaultProps = {}
// The `constructor` method
constructor() {
super();
}
// Instance properties
state = { hi: 5}
// Flow `state` annotation
// NOTE: `state` is special. All other Flow annotations live below.
state: { hi: number }
// React lifecycle hooks.
// They should follow their chronological ordering:
// 1. componentWillMount
// 2. componentDidMount
// 3. componentWillReceiveProps
// 4. shouldComponentUpdate
// 5. componentWillUpdate
// 6. componentDidUpdate
// 7. componentWillUnmount
componentDidMount() { ... }
// All other Flow annotations
props: { ... }
someRandomData: string,
// All other instance methods
handleClick = (e) => { ... }
// Finally, the render method
render() { ... }
}Example:
<Component
onClick={this.handleClick}
onLaunchMissiles={this.handleLaunchMissiles}
/>This is consistent with React's event naming: onClick, onDrag,
onChange, etc.
Example:
<Component onLaunchMissiles={this.handleLaunchMissiles} />The 80-character line limit is a bit tight, so we opt to conserve the extra 4.
Yes:
return <div>...</div>;No:
return (
// "div" is not on the same line as "return"
<div>...</div>
);Fit them all on the same line if you can. If you can't, put each property on a line of its own, indented four spaces, in sorted order. The closing angle brace should be on a line of its own, indented the same as the opening angle brace. This makes it easy to see the props at a glance.
Yes:
<div className="highlight" key="highlight-div">
<div
className="highlight"
key="highlight-div"
>
<Image
className="highlight"
key="highlight-div"
/>No:
<div className="highlight" // property not on its own line
key="highlight-div"
>
<div // closing brace not on its own line
className="highlight"
key="highlight-div">
<div // property is not sorted
key="highlight-div"
className="highlight"
>Every .jsx file should export a single React class, and nothing else. This is for testability; the fixture framework requires it to function.
Note that the file can still define multiple classes, it just can't export more than one.
It's useful to think of the React world as divided into "logic" components and "presentation" components.
"Logic" components have application logic, but do not emit HTML themselves.
"Presentation" components are typically reusable, and do emit HTML.
Logic components can have internal state, but presentation components never should.
Prefer props to state.
You almost always want to use props. By avoiding state when possible, you minimize redundancy, making it easier to reason about your application.
A common pattern — which matches the "logic" vs. "presentation" component distinction — is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via props. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
Copying data from props to state can cause the UI to get out of sync and is especially bad.
Do not use data- attributes or classes. All information
should be stored in JavaScript, either in the React component itself,
or in a React store if using a framework such as Redux.
Type-checking with Flow
Flow is a type-checker that runs at compile-time to catch issues and prevent bugs. It should be used on all new components.
For more information on how we use Flow, check the Javascript style guide
Props can now be validated with Flow instead of React's PropTypes. Flow provides a much more expressive way to set types for props, with the additional benefits of being able to annotate state (and any additional methods or data).
Types can be defined on the class itself:
class Foo extends Component {
props: {
name: string,
uniqueId: number,
complexData: {
setOfThings: Array<string>,
},
};
}If you're writing a Stateless Functional Component, or if you use
lifecycle methods that reference props (eg. componentDidUpdate),
you may find it helpful to define a Props type, and reference it:
type Props = {
numbers: Array<number>,
};
class Foo extends Component {
props: Props
shouldComponentUpdate(nextProps: Props) {
...
}
}
const StatelessFoo = ({numbers}: Props) => {
...
};children is something of a special prop in React. Most often,
you'll want to pass a React element (or an array of React elements).
This can be annotated like so:
children: React$Element<any> | Array<React$Element<any>>Note that this is only the most common use-case for children. Children can also be other types (like a string, or a function).
To make components safe to render server-side, they must adhere to a few more restrictions than regular components.
In order to render server-side, the props are serialized and
deserialized from JSON. This means that e.g. dates must be
passed as timestamps as opposed to JS Date objects.
Note that this only applies to the root component being rendered with server-side rendering. If the root component constructs more complex data structures from props, and passes those constructs down to child components, that won't cause problems.
Components must be pure functions of their props and state.
This means the output of their render() function must not
depend on either KA-specific globals, on browser-specific
state, or browser-specific APIs.
Examples of KA-specific globals include anything attached to
the KA global, e.g. KA.getUserId(), or data extracted from
the DOM such as data- properties attached to other DOM nodes.
Examples of browser-specific state include the user agent, the screen resolution, the device pixel density etc.
An example of a browser-specific API is canvas.getContext().
The output must be deterministic. One way to get
non-deterministic output is to generate random
IDs in getInitialState(), and have the output
of render depend on that ID. Don't do this.
The parts of the React component lifecycle that are run to render on the server must be free from side effects.
Examples of side effects that must be avoided:
- Sending an AJAX request
- Mutating global JS state
- Injecting elements into the DOM
- Changing the page title
alert()
The lifecycle methods that run on the server are currently:
getInitialState()getDefaultProps()componentWillMount()render()
If you need to execute any of the above listed side effects,
you must do so in componentDidMount or later in the component
lifecycle. These functions are not executed server-side.
Use redux actions, or fetch directly, instead.
We are trying to remove Backbone from our codebase entirely.
Never use jQuery for DOM manipulation.
Try to avoid using jQuery plugins. When necessary, wrap the jQuery plugin with a React component so you only have to touch the jQuery once.
Use the fetch API (accessible via khanFetch) instead of $.ajax.
For more information on khanFetch, see the javascript style guide.
If possible, re-use existing components, especially low-level, pure components that emit HTML directly. If you write a new such one, and it finds a use in a different project, put it in a shared location such as the react.js package.
The standard shared location for useful components that have been
open sourced is the react-components.js package in
javascript-packages.json. This includes components such as these:
SetIntervalMixin- provides a setInterval method so something can be done every x milliseconds$_- the i18n wrapper to allow for translating text in React.TimeAgo- “five minutes ago”, etc - this replaces $.timeago
Reusable components that have not (yet) been open sourced are in the
(poorly named) react.js package. This include components such as
these:
KUIButton- render a Khan Academy styled button.Modal- create a modal dialog.
We use CSS modules everywhere. CSS modules are great because they provide scope to CSS and allow us to create compartmentalized styles that don't leak to global scope. Here are our good practices of doing CSS modules:
Keep your code lines under 80 columns wide. This helps when opening multiple splits. Use soft tabs of 2 spaces to save some space! 😛
With CSS modules, camel case makes much more sense:
| GOOD | |
|---|---|
lib/components/Input/index.js |
lib/components/Input/style.css |
import style from "./style.css";
const Item = ({ children }) => (
<li className={style.circleBullet}>{children}</li>
);
export default Item; |
.circleBullet {
list-style-type: disc;
} |
Using ID and tag name at the selector's root makes the rule to be applied globally.
| GOOD | |
|---|---|
lib/components/Item/index.js |
lib/components/Item/style.css |
import style from "./style.css";
const Item = ({ title, thumbnail }) => (
<div className={style.container}>
<img src={thumbnail} alt={title} />
</div>
);
export default Item; |
.container > img {
background-color: #cccccc;
} |
| BAD | |
lib/components/Item/index.js |
lib/components/Item/style.css |
import style from "./style.css";
const Item = ({ title, thumbnail }) => (
<div className={style.container}>
<img src={thumbnail} alt={title} />
</div>
);
export default Item; |
img {
background-color: #cccccc;
} |
Organize one selector per line, even when placing all of them at the same line doesn't exceed 80 columns.
| GOOD | BAD |
|---|---|
.container > img,
.container > div,
.container > section {
background-color: #cccccc;
} |
.container > img,
.container > div,
.container > section {
background-color: #cccccc;
} |
Sometimes, not to exceed the 80 columns limit, you need to break lines. While at it, be sure to do it right after the colon, and keep at one argument per line.
| GOOD | BAD |
|---|---|
.container {
background-color: linear-gradient(
0deg,
var(--color-light-yellow-12),
var(--color-light-yellow-10)
);
} |
.container {
background-color: linear-gradient(0deg, --color-light...
}
.container {
background-color: linear-gradient(
0deg, var(--color-light-yellow-12), var(--color-lig...
} |
- Put a space before the opening brace
{ - In properties put a space after (but not before) the
:character - Put closing braces
}of rule declarations on a new line - Leave ONE blank line in between rule declarations
| GOOD | BAD |
|---|---|
.container {
font-size: 12pt;
}
.thumbnail {
width: 160px;
height: 90px;
} |
.container {
font-size: 12pt;
}
.thumbnail {
width: 160px;
height: 90px;
} |
Leaf components shouldn't constrain width or height (unless it makes sense). That said, most components should default to fill the parent:
| GOOD | |
|---|---|
lib/components/Input/index.js |
lib/components/Input/style.css |
import style from "./style.css";
const Input = ({ children }) => (
<input className={style.input}>{children}</input>
);
export default Input; |
.input {
box-sizing: border-box;
padding: 10px;
width: 100%;
} |
Sometimes we don't want to fill the whole width by default. An example is the button component, which we want to resize itself based on title width.
In this cases, we should allow the parent component to inject styles into the child component's container. The child is responsible for choosing where parent styles are injected.
For merging styles, always use [classnames][classnames] package. The
rightmost arguments overrides the leftmost ones.
| GOOD | |
|---|---|
lib/components/Button/index.js |
lib/components/Button/style.css |
import classNames from "classnames";
import style from "./style.css";
const Button = ({ children, className }) => (
<button className={classNames(style.button, className)}>{children}</button>
);
export default Button; |
.button {
box-sizing: border-box;
padding: 10px;
width: 100%;
} |
All components are self-contained and their final size should never suffer margin leakage! This allows the components to be much more reusable!
| BAD | GOOD |
|---|---|
|
|
When building lists or grids:
- Build list/grid items as separate components
- Use the the list/grid container to space children
- To space them horizontally, use
margin-left - To space them vertically, use
margin-top - Select the
first-childto reset margins
| GOOD | |
|---|---|
lib/containers/Reviews/index.js |
lib/containers/Reviews/style.css |
import style from "./style.css";
const Reviews = ({ items }) => (
<div className={style.container}>
{items.map((item) => (
<img src={item.image} alt={item.title} />
))}
</div>
);
export default Reviews; |
.container > img {
margin-left: 10px;
}
.container > img:first-child {
margin-left: unset;
} |
CSS modules already provides us scope. We don't need to use nested classes for providing scope isolation. Use nested class selectors for modifying children based on parent class. A use case is when a component is in error or success state:
| BAD | |
|---|---|
lib/components/Button/index.js |
lib/components/Button/style.css |
import style from "./style.css";
const Button = ({ children }) => (
<button className={style.button}>
<img className={style.icon} />
{children}
</button>
);
export default Button; |
.button {
box-sizing: border-box;
padding: 10px;
width: 100%;
}
.button .icon {
width: 22px;
height: 22px;
} |
| GOOD | |
lib/components/Input/index.js |
lib/components/Input/style.css |
import style from "./style.css";
const Input = ({ value, onChange, error }) => (
<div className={classNames({ [style.error]: error })}>
<input onChange={onChange} />
<p>{error}</p>
</div>
);
export default Input; |
.error p {
color: red;
display: unset;
} |
We encourage the "variabilification". Always define variables to increase reuse and make styles more consistent. The CSS4 specification defines a way to declare native variables. We adopted them as the standard.
To define a variable accessible globally:
| GOOD | |
|---|---|
app/App/variables.css |
app/components/Button/styles.css |
:root {
--color-green-1: #6ccfae;
--color-green-2: #6b66b5;
--color-green-3: #aac257;
--color-green-4: #68b5c1;
} |
.container {
background-color: linear-gradient(
0deg,
var(--color-green-1),
var(--color-green-2)
);
} |