React UI components Code Style Guide
UI components Code Style Guide
UI components best practices with React and styled-components
This is meant to be a guide to help new developers understand the best practices to implement components.
Let's go over the main issues:
1. Files Folders naming
- Components folder - PascalCase
- Component file - PascalCase
- Utils files/folders - kebab-case
Example:
MyCard/
├── index.is
├── MyCard.tsx
├── BaseCard
├────── index.is
├────── BaseCard.tsx
└── my-card-utils.ts
2. Component definition
Structure
All components (presentation, containers or pages) should always be
defined as a directory, named with pascal casing. The main component file
should be index.ts
MyCard/
├── index.ts
├── MyCard.tsx
Named export
Use named exports. Benefits of the Named export:
- Explicit over implicit - Named exports are explicit, forcing the consumer to import with the names the original author intended and removing any ambiguity.
- Refactoring actually works - Because named exports require you to use the name of the object, function or variable that was exported from a module, refactoring works across the board.
- Codebase look-up - Because default exports can have any name applied to them, it's almost impossible to perform a look-up in your codebase, especially if a naming convention isn't put in place.
- Better Tree Shaking - Instead of exporting a single bloated object with properties you may or may not need, named exports permit you to import individual pieces from a module, excluding unused code from the bundle during the build process.
import styled from 'styled-components';
const StyledButton = styled.button`
`;
const Button = ({ children, className }) =>
<StyledButton className={className}>
{children}
</StyledButton>
export default Button;
Good
import styled from 'styled-components';
const StyledButton = styled.button`
`;
export const Button = ({ children, className }) =>
<StyledButton className={className}>
{children}
</StyledButton>
Forward Ref
Building reusable components we want to enable users to attach ref to those components. The way to do it is to wrap components with React.forwardRef https://reactjs.org/docs/forwarding-refs.html
Example
import { forwardRef } from 'react';
import styled from 'styled-components';
const StyledButton = styled.button`
`;
export const Button = forwardRef(({ children, className }), ref) =>
<StyledButton className={className} ref={ref}>
{children}
</StyledButton>
//Usage
const buttonRef = useRef(null);
render <Button ref={buttonRef}/>
3. Styled Components
ClassName
Add className to the container of the component. in order to be able to override styles from the parent
Bad
import styled from 'styled-components';
const StyledButton = styled.button`
`;
export const Button = ({ children}: { children: ReactNode}) =>
<StyledButton>
{children}
</StyledButton>
Good
import styled from 'styled-components';
const StyledButton = styled.button`
`;
export const Button = ({ children, className}: { children: ReactNode; className?: string }) =>
<StyledButton className={className}>
{children}
</StyledButton>
Usage
const MyButton = styled(Button)`
margin-right: 16px;
`;
<StyledButton/>
Do not use string class names with styled components
Bad
import styled from 'styled-components';
const StyledButton = styled.button`
.contained {
background: blue;
}
.outlined {
background: white;
border: 1px solid red;
}
`;
export const Button = ({ children, className, variant = "contained" }) =>
<StyledButton className={variant + className}>
{children}
</StyledButton>
Good
import styled, { css } from 'styled-components';
const StyledButton = styled.button<{variant: string}>`
${props => {
switch (props.variant) {
case "outlined": {
return css`
background: white;
border: 1px solid red;
`
}
case "contained": {
return css`
background: blue;
border: 1px solid red;
`
}
default: {
return css`
background: blue;
`
}
}
}}
`;
export const Button = ({ children, className, variant }) =>
<StyledButton variant={variant} className={className}>
{children}
</StyledButton>
Naming
Styled components should start with prefix Styled
Bad
import styled from 'styled-components';
const MyButton = styled.button`
`;
export const Button = ({ children}) =>
<MyButton>
{children}
</MyButton>
Good
import styled from 'styled-components';
const StyledButton = styled.button`
`;
export const Button = ({ children, className}) =>
<StyledButton className={className}>
{children}
</StyledButton>
Props theme
Use getTheme to get props in styled components. getTheme uses default theme in case you are using components without ThemeProvider
Bad
import styled from 'styled-components';
const StyledButton = styled.button`
color: ${(props) => props.palette.colors.typography.tint1};
`;
export const Button = ({ children, className }) =>
<StyledButton className={className}>
{children}
</StyledButton>
Good
import styled from 'styled-components';
const StyledButton = styled.button`
color: ${(props) => getTheme(props).palette.colors.typography.tint1};
`;
export const Button = ({ children, className }) =>
<StyledButton className={className}>
{children}
</StyledButton>
Theme tokens
Use all the available theme tokens in components like: colors, fonts, spaces, radius, and etc.
Bad
import styled from 'styled-components';
const StyledButton = styled.button`
color: white;
font-family: Proxima Nova, sans-serif;
background-color: #6c89f0;
padding: 2px;
border-radius: 4px;
`;
export const Button = ({ children, className }) =>
<StyledButton className={className}>
{children}
</StyledButton>
Good
import styled from 'styled-components';
const StyledButton = styled.button`
color: ${(props) => props.palette.colors.white.main};
font-family: ${(props) => getTheme(props).typography.F10};
background-color: ${(props) => props.theme.palette.colors.primary.tint1};
padding: ${(props) => props.theme.spaces.SP0};
border-radius: ${(props) => getTheme(props).radius.R20};
`;
export const Button = ({ children, className }) =>
<StyledButton className={className}>
{children}
</StyledButton>
Export styled components
If you have a simple style component you can export it without creating component.</br/> In this can it will be just like creating react component that receive children.
Bad
import styled from 'styled-components';
const StyledHeader = styled.div`
height: 30px;
background: gray;
`;
export const Header = ({ children, className }) =>
<StyledHeader className={className}>
{children}
</StyledHeader>
Good
import styled from 'styled-components';
export const Header = styled.div`
height: 30px;
background: gray;
`;
Styled components properties
Property that is only used by styled components and shouldn't be in the dom should be with $.
Bad
import styled from 'styled-components';
const StyledHeader = styled.div`
height: 30px;
background: ${(props) => props.someProp ? "red" : "blue"};
`;
export const Header = ({ children, className, someProp }) =>
<StyledHeader someProp={someProp} className={className}>
{children}
</StyledHeader>
Good
import styled from 'styled-components';
const StyledHeader = styled.div`
height: 30px;
background: ${(props) => props.$someProp ? "red" : "blue"};
`;
export const Header = ({ children, className, someProp }) =>
<StyledHeader $someProp={someProp} className={className}>
{children}
</StyledHeader>
Styled components hover child
When you need to add style on one of the children after hover on the parent
Bad
import styled from 'styled-components';
const StyledChild = styled.div`
background-color: red;
`;
const StyledParent = styled.div`
&:hover {
.child {
background-color: blue;
}
}
`;
export const Header = ({ className }) =>
<StyledParent className={className}>
<StyledChild className={"child"}>hello</StyledChild>
</StyledParent>
Good
import styled from 'styled-components';
const StyledChild = styled.div`
background-color: red;
`;
const StyledParent = styled.div`
&:hover ${StyledChild} {
background-color: blue;
}
`;
export const Header = ({ className }) =>
<StyledParent className={className}>
<StyledChild>hello</StyledChild>
</StyledParent>
4. Code standards
Destruct your prop. More than 2 props from an object been used in the same place should be destructed
Code style
- Line length should not exceed 80 characters. We should be able to read the code without scrolling horizontally.
Use explanatory variables
Bad
const onlyNumbersRegex = /^\d+$/
const validateNumber = message => value => !onlyNumbersRegex.test(value) && message
validateNumber('error message')(1)
Good
const onlyNumbersRegex = /^\d+$/
const getNumberValidation = message => value => !onlyNumbersRegex.test(value) && message
const isNumber = getNumberValidation('error message')
isNumber(1)
Use intention revealing names
Use intention revealing names instead of number or string
Bad
setTimeout(doSomething, 86400000)
Good
const DAY_IN_MILLISECONDS = 1 * 24 * 60 * 60 * 1000;
setTimeout(doSomething, DAY_IN_MILLISECONDS)
One component per file
Bad
import styled from 'styled-components';
export const Tab = ({ children, className }) =>
<StyledTab className={className}>
{children}
</StyledTab>
export const TabsHeader = ({ children, className }) =>
<StyledTabsHeader className={className}>
{children}
</StyledTabsHeader>
Good
File: Tab.tsx
import styled from 'styled-components';
export const Tab = ({ children, className }) =>
<StyledTab className={className}>
{children}
</StyledTab>
File: TabsHeader.tsx
import styled from 'styled-components';
export const TabsHeader = ({ children, className }) =>
<StyledTabsHeader className={className}>
{children}
</StyledTabsHeader>
5. CSS Design Patterns
The parent constrains the child
Leaf components shouldn't constrain width or height (unless it makes sense).
Bad
import styled from 'styled-components';
const StyledInput = styled.input`
box-sizing: border-box;
padding: 10px;
width: 140px;
`;
export const Input = ({ children, className }) =>
<StyledInput className={className}>
{children}
</StyledInput>
Good
import styled from 'styled-components';
const StyledInput = styled.input`
box-sizing: border-box;
padding: 10px;
width: 100%;
`;
export const Input = ({ children, className }) =>
<StyledInput className={className}>
{children}
</StyledInput>
Components never leak margin
All components are self-contained and their final size should never suffer margin leakage! This allows the components to be much more reusable!
|--|-content size-|--| margin
____________________
| ______________ | | margin
| | | |
| | | |
| | | |
| |______________| |
|____________________| | margin
|---container size---|
|-content size-|
______________
| |
| |
| |
|______________|
Bad
import styled from 'styled-components';
const StyledLoader = styled.div`
box-sizing: border-box;
width: 50px;
height: 50px;
margin-top: 100px;
`;
export const Loader = ({ children, className }) =>
<StyledLoader className={className}/>
Good
import styled from 'styled-components';
const StyledLoader = styled.div`
box-sizing: border-box;
width: 50px;
height: 50px;
`;
export const Loader = ({ children, className }) =>
<StyledLoader className={className}/>
//Usage
const MyLoader = styled(Loader)`
margin-top: 100px;
`;
return <MyLoader/>
The parent spaces the children
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-child
to reset margins
Example
import styled from 'styled-components';
const StyledImage = styled.img`
margin-left: 10px;
:first-chld {
margin-left: unset;
}
`;
export const Reviews = ({ items, className }) =>
<div className={className}>
{items.map(item =>
<StyledImage src={item.image} alt={item.title} />
)}
</div>
6. Components Design Patterns
Use Controlled components
Controlled components takes its current value through props and makes changes through callbacks like onClick, onChange, etc... When possible use Controlled components instead of Uncontrolled ones. Since most of the time we have initial state and everytime the input changes there are actions triggered.
Uncontrolled
import { useState } from 'react';
import styled from 'styled-components';
const StyledInput = styled.div`
height: 30px;
`;
export const Search = forwardRef(({ children, className }), ref) =>
<StyledInput className={className} ref={ref}/>
//Usage
const inputRef = useRef(null);
const value = inputRef.current.value
render <Search ref={inputRef}/>
Controlled
import { useState } from 'react';
import styled from 'styled-components';
const StyledInput = styled.div`
height: 30px;
`;
export const Search = ({ value, onChange, className }) => {
return <StyledInput value={value} className={className} onChange={(e) => onChange(e.target.value)} />
}
Do not use redundant state
Do not create state in the component in case value and onchange is passed.
Bad
import { useState } from 'react';
import styled from 'styled-components';
const StyledInput = styled.div`
height: 30px;
`;
export const Search = ({ value, onChange, className }) => {
const [text, setText] = useState<string>(value);
const handleChange = (e) => {
setText(e.target.value);
onChange(e.target.value)
}
return <StyledInput value={value} className={className} onChange={onChange}/>
}
Good
import { useState } from 'react';
import styled from 'styled-components';
const StyledInput = styled.div`
height: 30px;
`;
export const Search = ({ value, onChange, className }) => {
return <StyledInput value={value} className={className} onChange={(e) => onChange(e.target.value)}/>
}
7. API design
Children instead of properties
Bad
<Header title="Hi" />
Good
<StyledHeader>
<Title>Hi</Title>
</StyledHeader>
Composability
Components should be built in a composable way. Larger, more complex components should be built using smaller, simple components. And those larger components should export those sub-components. This will allow users to drop a level below the higher abstraction and compose the sub-components in a way to meet their use case. Example: Mui Design API
Bad
<Menu
label="Animals"
options={options}
noSearchResults={<div>Custom No Results</div>}
placeholder={"Select"}
onChange={onChange}
optionsHeight={400}
/>
- But what if I need an icon in the Menu’s title
- What if I want different style for menu items
- And different style search
- Is the search is on the server side
- Menu items with checkboxes
Good We create Menu and MenuItem and we can compose any menu from it. We can style MenuItem, put icons before and after and to add checkmark.
export const Menu = () => ...
export const MenuItem = () => ...
<Menu>
<MyMenuTitle>
{items.map((item) => {
const isSelected = selected === item.value;
return (
<MenuItem key={item.value} onClick={(e) => handleClick(e, item.value)} selected={isSelected} checkMark>
<MenuItemStartIcon selected={isSelected}>
<item.startIcon />
</MenuItemStartIcon>
<MenuItemText selected={isSelected}>{item.label}</MenuItemText>
</MenuItem>
);
})}
</Menu>
8. Typography
Always use Typography component when adding a text
Bad
import styled from 'styled-components';
const StyledText = styled.p`
font-family: ${(props) => getTheme(props).typography.F10};
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
`;
export const Title = ({ children, className }) =>
<StyledText className={className}>
{children}
</StyledText>
Good
import styled from 'styled-components';
import { Typography } from './Typography';
export const Title = ({ children, className }) =>
<Typography variant="T30" className={className}>
{children}
</Typography>
//or if you need div element
export const Title = ({ children, className }) =>
<Typography as="div" variant="T30" className={className}>
{children}
</Typography>
You can also check it on github