Skip to main contentCarbon Design System

4. Creating components

With two pages comprised entirely of Carbon components, let’s revisit the landing page and build a couple components of our own by using Carbon icons and tokens.

Preview

Carbon provides a solid foundation for building web applications through its color palette, layout, spacing, type, as well as common building blocks in the form of components. So far, we’ve only used Carbon components to build out two pages.

Next, we’re going to use Carbon assets to build application-specific components. We’ll do so by including accessibility and responsive considerations all throughout.

A preview of what you’ll build (see bottom of page):

Fork, clone and branch

This tutorial has an accompanying GitHub repository called carbon-tutorial that we’ll use as a starting point for each step. If you haven’t forked and cloned that repository yet, and haven’t added the upstream remote, go ahead and do so by following the step 1 instructions.

Branch

With your repository all set up, let’s check out the branch for this tutorial step’s starting point.

git fetch upstream
git checkout -b v11-react-step-4 upstream/v11-react-step-4

Note: This builds on top of step 3, but be sure to check out the upstream step 4 branch because it includes the static assets required to get through this step.

Build and start app

Install the app’s dependencies (in case you’re starting fresh in your current directory and not continuing from the previous step):

yarn

Then, start the app:

yarn start

You should see something similar to where the previous step left off.

Review design

Here’s what we’re building – an informational section that has a heading and three subheadings. Each subheading has accompanying copy and a pictogram. We’ll assume that this informational section is used elsewhere on the site, meaning it’s a great opportunity to build it as a reusable component. As for naming, we’ll call it an InfoSection with three InfoCards as children.

Info section layout

Info section layout

Create components

First we need files for the components, so create an Info folder in src/components. Even though we’re building multiple components, their names all start with Info, so it makes sense to have them share one folder in components. Create these files:

Add files

src/components/Info
├──_info.scss
├──index.js
└──Info.js

Import _info.scss in app.scss after all of the tutorial-header.scss import.

src/app.scss
@use './components/Info/info';

Like our other components, index.js will serve as an entrypoint. Since Info.js will export multiple components, we’ll use the * wildcard in the entrypoint export.

src/components/Info/index.js
export * from './Info';

InfoSection component

Let’s create the parent component that includes the “The Principles” heading. That markup currently looks like this in LandingPage.js:

src/content/LandingPage/LandingPage.js
<Column lg={16} md={8} sm={4} className="landing-page__r3">
<Grid>
<Column md={4} lg={4}>
<h3 className="landing-page__label">The Principles</h3>
</Column>
<Column md={4} lg={4}>
Carbon is Open
</Column>
<Column md={4} lg={4}>

We want to do a few things when abstracting it to a component. First, we only want this component’s class names; we don’t want to include landing-page__r3 as that’s specific to the landing page. For that we’ll use React props so we can pass in and use props.className.

We’ll also:

  • Add component class names like info-section, info-card, and info-section__heading
  • We will be using the Grid and Column components
  • Replace The Principles with {props.heading}
  • Replace columns 2 - 4 with {props.children}

Using props we can render any heading and any number of children components (InfoCard that we’ll build soon.)

src/components/Info/Info.js
import React from 'react';
import { Grid, Column } from '@carbon/react';
const InfoSection = (props) => (
<Grid className={`${props.className} info-section`}>
<Column md={8} lg={4} xlg={3}>
<h3 className="info-section__heading">{props.heading}</h3>
</Column>
{props.children}

At this point let’s import our needed styles and add styling for the new class names that we just added.

src/components/Info/_info.scss
@use '@carbon/react/scss/type' as *;
.info-section__heading {
@include type-style('heading-01');
}

InfoCard component

Next up we’re going to build a component for columns 2 - 4, which currently looks like <Column md={4} lg={4}>Carbon is Open</Column>. At the bottom of Info.js, add:

src/components/Info/Info.js
const InfoCard = (props) => {
return (
<Column sm={4} md={8} lg={4} className="info-card">
<h4 className="info-card__heading">{props.heading}</h4>
<p className="info-card__body">{props.body}</p>
{props.icon()}
</Column>
);
};

Note: Make sure to export the two components!

In doing so, we:

  • Added info-card classes
  • Used props to render the heading, body copy, and icon
  • Set columns to match the grid

Use components

Nothing is styled yet, but with our components built let’s put them to use. In LandingPage.js, import the components towards the top of the file.

src/content/LandingPage/LandingPage.js
import { InfoSection, InfoCard } from '../../components/Info';

While we’re at the top of LandingPage.js, import the icons that we’ll need as well.

src/content/LandingPage/LandingPage.js
import { Globe, Application, PersonFavorite } from '@carbon/react/icons';

With everything imported, replace the current:

src/content/LandingPage/LandingPage.js
<Grid>
<Column md={4} lg={4}>
<h3 className="landing-page__label">The Principles</h3>
</Column>
<Column md={4} lg={4}>
Carbon is Open
</Column>
<Column md={4} lg={4}>
Carbon is Modular

With the new components:

src/content/LandingPage/LandingPage.js
<InfoSection heading="The Principles" className="landing-page__r3">
<InfoCard
heading="Carbon is Open"
body="It's a distributed effort, guided by the principles of the open-source movement. Carbon's users are also it's makers, and everyone is encouraged to contribute."
icon={() => <PersonFavorite size={32} />}
/>
<InfoCard
heading="Carbon is Modular"
body="Carbon's modularity ensures maximum flexibility in execution. It's components are designed to work seamlessly with each other, in whichever combination suits the needs of the user."

Note: Now is a good time to resize your browser from phone to large viewport widths to see how the responsive grid is working before we add further styling.

Add styling

Here’s our design showing the spacing tokens that we need to add. We also need to set type style and borders.

Info section spacing

Layout

Starting with layout, import the following carbon styles to src/components/Info/_info.scss. Make sure to import the needed carbon styles.

src/components/Info/_info.scss
@use '@carbon/react/scss/spacing' as *;
@use '@carbon/react/scss/breakpoint' as *;
@use '@carbon/react/scss/theme' as *;

We will then add the following styles to the same file to style our info cards.

src/components/Info/_info.scss
.info-card {
margin-top: $spacing-09;
display: flex;
flex-direction: column;
padding-left: 1rem;
svg {
margin-top: $spacing-09;
}

Once you save, go ahead and resize your browser to see the responsive layout at the different breakpoints. Make sure to review these color and spacing tokens. There are also a few breakpoint mixins that may be new to you.

Type

Our InfoCard headings look to be too small. We need to increase their font sizes according to the design spec. Lets update the heading with the following type style:

src/components/Info/_info.scss
.info-card__heading {
@include type-style('productive-heading-03');
}

Also, the design has the last word in each subheading as bold. To accomplish that, add this helper function after the import in Info.js.

src/components/Info/Info.js
// Take in a phrase and separate the third word in an array
function createArrayFromPhrase(phrase) {
const splitPhrase = phrase.split(' ');
const thirdWord = splitPhrase.pop();
return [splitPhrase.join(' '), thirdWord];
}

Then, update InfoCard to use createArrayFromPhrase.

src/components/Info/Info.js
const InfoCard = (props) => {
const splitHeading = createArrayFromPhrase(props.heading);
return (
<Column sm={4} md={8} lg={4} className="info-card">
<h4 className="info-card__heading">
{`${splitHeading[0]} `}
<strong>{splitHeading[1]}</strong>
</h4>

Finally, add the declaration block in _info.scss to set InfoCard body copy styles and to bottom-align the icons.

src/components/Info/_info.scss
.info-card__body {
margin-top: $spacing-06;
flex-grow: 1; // fill space so icons are bottom aligned
@include type-style('body-long-01');
// prevent large line lengths between small and medium viewports
@include breakpoint-between(321px, md) {
max-width: 75%;
}

Check accessibility

We’ve added new markup and styles, so it’s a good practice to check Equal Access Checker and make sure our rendered markup is on the right track for accessibility.

With the browser extension installed, Chrome in this example, open Dev Tools and run Accessibility Assessment.

Submit pull request

We’re going to submit a pull request to verify completion of this tutorial step.

Continuous integration (CI) check

Run the CI check to make sure we’re all set to submit a pull request.

yarn ci-check

Note: Having issues running the CI check? Step 1 has troubleshooting notes that may help.

Git commit and push

Before we can create a pull request, format your code, then stage and commit all of your changes:

yarn format
git add --all && git commit -m "feat(tutorial): complete step 4"

Then, push to your repository:

git push origin v11-react-step-4

Note: Having issues pushing your changes? Step 1 has troubleshooting notes that may help.

Pull request (PR)

Finally, visit carbon-tutorial to “Compare & pull request”. In doing so, make sure that you are comparing to v11-react-step-4 into base: v11-react-step-4.

Note: Expect your tutorial step PRs to be reviewed by the Carbon team but not merged. We’ll close your PR so we can keep the repository’s remote branches pristine and ready for the next person!

Note: If your PR fails the CircleCI test with the error Can't make a request in offline mode, try running the following command: rm -rf .yarn-offline-mirror node_modules && yarn cache clean && yarn install. Add and commit the changes once this completes, and try pushing again.