Cover Image for React UI components Code Style Guide

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:

  1. Explicit over implicit - Named exports are explicit, forcing the consumer to import with the names the original author intended and removing any ambiguity.
  2. 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.
  3. 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.
  4. 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}
/>
  1. But what if I need an icon in the Menu’s title
  2. What if I want different style for menu items
  3. And different style search
  4. Is the search is on the server side
  5. 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