Skip to content

c

More about styles

Back in part 2, we examined two different ways of adding styles to our application: the old-school single CSS file and inline styles. In this part, we will take a look at a few other ways.

Ready-made UI libraries

One approach to defining styles for an application is to use a ready-made "UI framework".

One of the first widely popular UI frameworks was the Bootstrap toolkit created by Twitter which may still be the most popular framework. Recently, there has been an explosion in the number of new UI frameworks that have entered the arena. The selection is so vast that there is little hope of creating an exhaustive list of options.

Many UI frameworks provide developers of web applications with ready-made themes and "components" like buttons, menus, and tables. We write components in quotes because, in this context, we are not talking about React components. Usually, UI frameworks are used by including the CSS stylesheets and JavaScript code of the framework in the application.

Many UI frameworks have React-friendly versions where the framework's "components" have been transformed into React components. There are a few different React versions of Bootstrap like reactstrap and react-bootstrap.

To start off, we will examine two UI frameworks, Bootstrap and MaterialUI. We will use both frameworks to add similar styles to the application we made in the React Router section of the course material.

React Bootstrap

Let's start by taking a look at Bootstrap with the help of the react-bootstrap package.

Let's install the package with the command:

npm i react-bootstrap

Then let's add a link for loading a Bootstrap CSS stylesheet inside of the head tag in the public/index.html file of the application:

<head>
  <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
    integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
    crossorigin="anonymous"
  />
  // ...
</head>

When we reload the application, we notice that it already looks a bit more stylish:

browser tasks app with bootstrap

In Bootstrap, all of the contents of the application are typically rendered inside a container. In practice this is accomplished by giving the root div element of the application the container class attribute:

const App = () => {
  // ...

  return (
    <div className="container">      // ...
    </div>
  )
}

We notice that this already affected the appearance of the application. The content is no longer as close to the edges of the browser as it was earlier:

browser tasks app with margin spacing

Bootstrap Table

Next, let's make some changes to the Tasks component so that it renders the list of tasks as a table. React Bootstrap provides a built-in Table component for this purpose, so there is no need to define CSS classes separately.

const Tasks = ({ tasks }) => (
  <div>
    <h2>Tasks</h2>
    <Table striped>      <tbody>
        {tasks.map(task =>
          <tr key={task.id}>
            <td>
              <Link to={`/tasks/${task.id}`}>
                {task.content}
              </Link>
            </td>
            <td>
              {task.user}
            </td>
          </tr>
        )}
      </tbody>
    </Table>
  </div>
)

Notice that the React Bootstrap components have to be imported separately from the library as shown below:

import { Table } from 'react-bootstrap'

The appearance of the application is a tad bit more stylish:

browser tasks tab with built-in table

Form in Bootstrap

Let's improve the form in the Login view with the help of Bootstrap forms.

React Bootstrap provides built-in components for creating forms (although the documentation for them is slightly lacking):

let Login = (props) => {
  // ...
  return (
    <div>
      <h2>login</h2>
      <Form onSubmit={onSubmit}>
        <Form.Group>
          <Form.Label>username:</Form.Label>
          <Form.Control
            type="text"
            name="username"
          />
        </Form.Group>
        <Form.Group>
          <Form.Label>password:</Form.Label>
          <Form.Control
            type="password"
          />
        </Form.Group>
        <Button variant="primary" type="submit">
          login
        </Button>
      </Form>
    </div>
  )
}

For this to work, you'll need to import the following (or simply follow WebStorm's lead and have it import these for you via its context actions):

import { Table, Form, Button } from 'react-bootstrap'

After switching over to the Bootstrap form, our improved application looks like this:

browser tasks app with bootstrap login

Notification in Bootstrap

Now that the login form is in better shape, let's take a look at improving our application's notifications:

browser tasks app with bootstrap notification

Let's add a message for the notification when a user logs into the application. We will start by storing a message variable in the App component's state:

const App = () => {
  const [tasks, setTasks] = useState([
    // ...
  ])

  const [user, setUser] = useState(null)
  const [message, setMessage] = useState(null)
  const login = (user) => {
    setUser(user)
    setMessage(`welcome ${user}`)    setTimeout(() => {      setMessage(null)    }, 10000)  }
  // ...
}

We will then render the message as a Bootstrap Alert component. Once again, the React Bootstrap library provides us with a matching React component:

<div className="container">
  {(message &&    <Alert variant="success">      {message}    </Alert>  )}  // ...
</div>

You'll need to import the Alert library from react-bootstrap as well here.

Navigation structure in Bootstrap

Lastly, let's alter the application's navigation menu to use Bootstrap's Navbar component. The React Bootstrap library provides us with matching built-in components. Through trial and error, we end up with a working solution despite the cryptic documentation, replacing our existing navigation links.

<Navbar collapseOnSelect expand="lg" bg="dark" variant="dark">
  <Navbar.Toggle aria-controls="responsive-navbar-nav" />
  <Navbar.Collapse id="responsive-navbar-nav">
    <Nav className="me-auto">
      <Nav.Link href="#" as="span">
        <Link style={padding} to="/">home</Link>
      </Nav.Link>
      <Nav.Link href="#" as="span">
        <Link style={padding} to="/tasks">tasks</Link>
      </Nav.Link>
      <Nav.Link href="#" as="span">
        <Link style={padding} to="/users">users</Link>
      </Nav.Link>
      <Nav.Link href="#" as="span">
        {user
          ? <em style={padding}>{user} logged in</em>
          : <Link style={padding} to="/login">login</Link>
        }
      </Nav.Link>
    </Nav>
  </Navbar.Collapse>
</Navbar>

The resulting layout has a new appearance:

browser tasks app bootstrap black navigation bar

If the browser window is narrowed, notice how the menu "collapses" into the "hamburger" icon:

browser tasks app with hamburger menu

Bootstrap and a large majority of existing UI frameworks produce responsive designs, meaning that the resulting applications render well on a variety of different screen sizes.

Chrome's developer tools make it possible to simulate using our application in the browser of different mobile clients:

chrome devtools with mobile browser preview of tasks app

You can find the complete code for the application here.

Material UI

As our second example, we will look into the MaterialUI React library, which implements the Material Design visual language developed by Google.

For this part, we will reset our javascript file and reset it with the contents that we previously had before we added the bootstrap libraries, which you can retrieve from GitHub I have also removed the bootstrap library, which is nice to do some overall CSS niceties, but we will remove for simplicity sake.

Once you have reset your index.js in your repo, install the MaterialUI library with the command:

npm i @mui/material @emotion/react @emotion/styled

Then add the following line to the head tag in the public/index.html file.

Make sure you removed the bootstrap link from before

The line loads Google's font Roboto.

<head>
  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
  // ...
</head>

Now let's use MaterialUI to do the same modifications to the code we did earlier with bootstrap.

Let's start by placing the contents of the App in a MaterialUI Container. You can do this by replacing the div className="container" tag.

import { Container } from '@mui/material'

const App = () => {
  // ...
  const roboto = {
        fontFamily: "Roboto"
  }

  return (
    <Container style={roboto}>
      // ...
    </Container>
  )
}

MaterialUI Table

Then let's move to the Tasks component. We'll change the current table of tasks to a MaterialUI Table

const Tasks = ({ tasks }) => (
  <div>
    <h2>Tasks</h2>

    <TableContainer component={Paper}>
      <Table>
        <TableBody>
          {tasks.map(task => (
            <TableRow key={task.id}>
              <TableCell>
                <Link to={`/tasks/${task.id}`}>{task.content}</Link>
              </TableCell>
              <TableCell>
                {task.user}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  </div>
)

The table looks like so:

browser tasks materialUI table

One less pleasant feature of Material UI is that each component has to be imported separately. The import list for the tasks page is quite long:

import {
  Container,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableRow,
  Paper,
} from '@mui/material'

Form in Material UI

Next, let's make the login form in the Login view better using the TextField and Button components:

const Login = (props) => {
  const navigate = useNavigate()

  const onSubmit = (event) => {
    event.preventDefault()
    props.onLogin('powercat')
    navigate('/')
  }

  return (
    <div>
      <h2>login</h2>
      <form onSubmit={onSubmit}>
        <div>
          <TextField label="username" />
        </div>
        <div>
          <TextField label="password" type='password' />
        </div>
        <div>
          <Button variant="contained" color="primary" type="submit">
            login
          </Button>
        </div>
      </form>
    </div>
  )
}

The result is:

browser tasks app materialUI login form

MaterialUI, unlike Bootstrap, does not provide a component for the form itself. The form here is an ordinary HTML form element.

Remember to import all the components used in the form.

Notification in Material UI

The notification displayed on login can be done using the Alert component, which is quite similar to Bootstrap's equivalent component:

<div>
  {(message &&    <Alert severity="success">      {message}    </Alert>  )}</div>

You'll also need to add the setMessage state and timeouts as we did above The alert message looks professional:

browser tasks app materialUI notifications

Navigation structure in Material UI

We can implement navigation using the AppBar component.

If we use the example code from the documentation

<AppBar position="static">
  <Toolbar>
    <IconButton edge="start" color="inherit" aria-label="menu">
    </IconButton>
    <Button color="inherit">
      <Link to="/">home</Link>
    </Button>
    <Button color="inherit">
      <Link to="/tasks">tasks</Link>
    </Button>
    <Button color="inherit">
      <Link to="/users">users</Link>
    </Button>  
    <Button color="inherit">
      {user
        ? <em>{user} logged in</em>
        : <Link to="/login">login</Link>
      }
    </Button>                
  </Toolbar>
</AppBar>

our navigation works, but it could look better

browser tasks app materialUI blue navbar

The documentation provides a better way for us to work through this. We can use component props to define how the root element of a MaterialUI component is rendered.

By defining

<Button color="inherit" component={Link} to="/">
  home
</Button>

the Button component is rendered so that its root component is react-router-dom's Link which receives its path as the prop field to.

The code for the navigation bar is the following:

<AppBar position="static">
  <Toolbar>
    <Button color="inherit" component={Link} to="/">
      home
    </Button>
    <Button color="inherit" component={Link} to="/tasks">
      tasks
    </Button>
    <Button color="inherit" component={Link} to="/users">
      users
    </Button>   
    {user
      ? <em>{user} logged in</em>
      : <Button color="inherit" component={Link} to="/login">
          login
        </Button>
    }                              
  </Toolbar>
</AppBar>

and it looks like we want it to:

browser tasks app MaterialUI blue nav bar white text

The code of the application can be found here.

Closing thoughts

The difference between react-bootstrap and MaterialUI is not big. It's up to you which one you find better looking. Honestly, I haven't used either extensively, but I see both favorably. The MaterialUI documentation seems a bit better than react-bootstrap's. According to https://www.npmtrends.com/ which tracks the popularity of different npm-libraries, MaterialUI and react-bootstrap have had different eras in which they've been popular.

npmtrends of materialUI vs bootstrap

There are other packages that MaterialUI is also split into, which could explain some of the more recent dropoff.

In the two previous examples, we used the UI frameworks with the help of React-integration libraries.

Instead of using the React Bootstrap library, we could have just as well used Bootstrap directly by defining CSS classes for our application's HTML elements. Instead of defining the table with the Table component:

<Table striped>
  // ...
</Table>

We could have used a regular HTML table and added the required CSS class:

<table className="table striped">
  // ...
</table>

The benefit of using the React Bootstrap library is not that evident from this example.

In addition to making the frontend code more compact and readable, another advantage of using React UI framework libraries is that they include the JavaScript that is needed to make specific components work. Some Bootstrap components require a few unpleasant JavaScript dependencies that we would prefer not to include in our React applications.

One drawback to using UI frameworks through integration libraries instead of using them "directly" are that integration libraries may have unstable APIs and poor documentation. The situation with Semantic UI React is a lot better than with many other UI frameworks, as it is an official React integration library.

So why haven't I used either framework extensively? Mostly because I've done things using CSS. I was very hesitant to work with CSS originally, but after spending some time understanding it, I've found it suits my needs. However, for people lacking knowledge in CSS and web design, using these frameworks/tools can be very useful.

Other UI frameworks

There are plenty of other UI frameworks out there. I would not spend a lot of time with any of them unless you are extremely curious or have heard a lot about one or the other. If you do not see your favorite UI framework in the list, please make a pull request to the course material.

Styled components

There are also other ways of styling React applications that we have not yet taken a look at.

The styled components library offers an interesting approach for defining styles through tagged template literals that were introduced in ES6.

Let's make a few changes to the styles of our application with the help of styled components. I'm going to reset again the page using our default starter. This time I will make sure that we do not use any stylesheet or font, so make sure you remove any links to the bootstrap or roboto fonts. While either of these two links can combine with styled components, we will leave them out for now so you can be sure that everything we have is directly from the styled components.

First, install the package with the command:

npm i styled-components

Then let's define two components with styles:

import styled from 'styled-components'

const Button = styled.button`
  background: lightgray;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 3px solid darkgray;
  border-radius: 8px;
`

const Input = styled.input`
  margin: 0.25em;
`

The code above creates styled versions of the button and input HTML elements and then assigns them to the Button and Input variables.

The syntax for defining the styles is quite interesting, as the CSS rules are defined inside of backticks.

The styled components that we defined work exactly like regular button and input elements, and they can be used in the same way:

const Login = (props) => {
  // ...
  return (
    <div>
      <h2>login</h2>
      <form onSubmit={onSubmit}>
        <div>
          username:
          <Input />        </div>
        <div>
          password:
          <Input type='password' />        </div>
        <Button type="submit" primary=''>login</Button>      </form>
    </div>
  )
}

Let's create a few more components for styling this application which will be styled versions of div elements:

const Page = styled.div`
  background: #EDE7DC;
  font-family: sans-serif;
`

const Navigation = styled.div`
  background: #DCD2CC;
  padding: 1em;
  border-radius: 5px;
  margin-bottom: 1em;
`

const Footer = styled.div`
  background: #CCAFA5;
  padding: 1em;
  border-radius: 30px;
  margin-top: 1em;
`

Let's use the components in our application:

const App = () => {
  // ...

  return (
     <Page>      <Navigation>        <Link style={padding} to="/">home</Link>
        <Link style={padding} to="/tasks">tasks</Link>
        <Link style={padding} to="/users">users</Link>
        {user
          ? <em>{user} logged in</em>
          : <Link style={padding} to="/login">login</Link>
        }
      </Navigation>      
      <Routes>
        <Route path="/tasks/:id" element={<Task task={task} />} />  
        <Route path="/tasks" element={<Tasks tasks={tasks} />} />   
        <Route path="/users" element={user ? <Users /> : <Navigate replace to="/login" />} />
        <Route path="/login" element={<Login onLogin={login} />} />
        <Route path="/" element={<Home />} />      
      </Routes>

      <Footer>        <em>Task app, Department of Computer Science 2023</em>
      </Footer>    </Page>  )
}

The appearance of the resulting application is shown below:

browser tasks app styled components

Styled components have seen consistent growth in popularity in recent times, and quite a lot of people consider it to be the best way of defining styles in React applications.