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:

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:

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:

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:

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

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:

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

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:

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:

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:

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:

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

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:

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.

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.
- https://bulma.io/
- https://ant.design/
- https://get.foundation/
- https://chakra-ui.com/
- https://tailwindcss.com/
- https://semantic-ui.com/
- https://mantine.dev/
- https://react.fluentui.dev/
- https://storybook.js.org
- https://www.primefaces.org/primereact/
- https://v2.grommet.io
- https://blueprintjs.com
- https://evergreen.segment.com
- https://www.radix-ui.com/
- https://react-spectrum.adobe.com/react-aria/index.html
- https://master.co/
- https://www.radix-ui.com/
- https://nextui.org/
- https://daisyui.com/
- https://ui.shadcn.com/
- https://www.tremor.so/
- https://headlessui.com/
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:

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.