BEMed Components

Tutorial#

BEMed Components or react-bemed is a component primitive building tool for React.js inspired by Styled Components and the Block Element Modifier convention.

Feature highlights

    Human readable class names
    Optional CSS-in-JS API
      Server-side rendering of critical CSS
      Source maps and zero(ish)-runtime when using the Babel plugin
    Perfect for hybrid approaches: Write essential component structure with CSS-in-JS but add theming via external stylesheet
    Always up to date TypeScript types as it is written in TypeScript

Install#

npm install react-bemed

Basics#

Simplest possible component (or a "Block" in BEM terms) with BEMed is

import { bemed } from "react-bemed";
const Button = bemed({ name: "MyButton" });

This just creates a div with a MyButton class

<Button />
// =>
<div class="MyButton"></div>

We could just start styling this right away with vanilla CSS into a external stylesheet file but we can also use the CSS-in-JS api and write the styles within the component definition

import { css } from "react-bemed/css";
const Button = bemed({
name: "MyButton",
css: css`
color: white;
background-color: hotpink;
padding: 10px;
`,
});

and now with

<Button>My button</Button>

we get

My button

Note that even though we use the CSS-in-JS API in this tutorial it's completely optional. If you are not interested in it you can just ignore it and provide the css for the class names how ever you want.

Target DOM Elements#

but this is not very accessible button because it's a div and not a real button. Lets make it into a real button

const Button = bemed({
name: "MyButton",
as: "button",
css: css`
color: white;
background-color: hotpink;
padding: 10px;
`,
});
// Result
<button class="MyButton">My button</button>

It's better but now the default browser styles have ruined our cool button styles

Custom Class Names#

Luckily we have already defined an utility class that can reset button styles. Let's apply it to our button

const Button = bemed({
name: "MyButton",
as: "button",
className: "reset-button",
css: css`
color: white;
background-color: hotpink;
padding: 10px;
`,
});
// Result
<button class="MyButton reset-button">My button</button>

Default Props#

Our button is starting to look pretty good but for a generic React button this has one annoying feature: When it is inside a <form> element it will by default submit the form when clicked.

We can prevent this by changing the button type from the default submit to the button type which has no default behaviour when clicked.

This can be done by adding a default prop for it

const Button = bemed({
name: "MyButton",
as: "button",
className: "reset-button",
defaultProps: {
type: "button",
}
css: css`
color: white;
background-color: hotpink;
padding: 10px;
`,
});
// Result
<button type="button" class="MyButton reset-button">
My button
</button>

Modifiers#

Some buttons are more important than others. So lets add a modifier that can make the button look more important

const Button = bemed({
name: "MyButton",
as: "button",
className: "reset-button",
defaultProps: {
type: "button",
}
css: css`
color: white;
background-color: hotpink;
padding: 10px;
`,
mods: {
important: css`
box-shadow: 0px 0px 30px 10px purple;
`
}
});

We can apply this mod just by adding the important boolean prop

<Button important>My button</Button>
// Result
<button type="button" class="MyButton MyButton--important reset-button">
My button
</button>

This will add a class MyButton--important to the button and apply the styles to it when it's true

Elements#

We already have pretty good button primitive with basic styles but real world React components are composed from multiple components. For example button might have an icon element inside it.

One could start by just creating a another standalone component with the bemed() function but in BEM there is a concept of "Elements" that have no standalone meaning but are part of a larger "Block" component.

The bemed() function supports this convention with the elements property

import { FaReact } from "react-icons/fa";
const Button = bemed({
name: "MyButton",
as: "button",
className: "reset-button",
defaultProps: {
type: "button",
}
css: css`
color: white;
background-color: hotpink;
padding: 10px;
`,
mods: {
important: css`
box-shadow: 0px 0px 30px 10px purple;
`
},
elements: {
Icon: bemed({
as: FaReact,
}),
},
});

These element components are not techincally any different from the standalone components. The idea is to just tell the reader that these components are inherently part of the parent component. They get class names automatically from the parent component (MyButton__Icon) and are exposed as properties of the parent:

<MyButton important>
<MyButton.Icon size={20} />
React is Awesome
</MyButton>
// Result
<button type="button" class="MyButton MyButton--important reset-button">
<svg className="MyButton__Icon"><path ... /></svg>
React is Awesome
</button>

Custom Target Components#

Note how we used a custom component FaReact for the as property. In fact bemed() can use any components as the target; even other components created by itself.

Props are passed to the underlying component so the size prop accepted by the FaReact component works as expected.

If you make your own commponents you want to pass to the bemed() function all you need to do is to make sure it handles the className prop properly by passing it to the underlying component:

function MyLink(props: { className?: string }) {
return <a className={props.className}>MyLink</a>;
}

Scoped CSS Selectors#

The styling of the icon is bit off though. Lets fix that

const Button = bemed({
name: "MyButton",
as: "button",
className: "reset-button",
defaultProps: {
type: "button",
}
css: css`
color: white;
background-color: hotpink;
padding: 10px;
`,
mods: {
important: css`
box-shadow: 0px 0px 30px 10px purple;
`
},
elements: {
Icon: bemed({
as: FaReact,
css: css`
background-color: black;
border-radius: 20px;
padding: 1px;
path {
fill: #61dafb;
}
`,
}),
},
});

We can target the path element inside the svg icon. This selector is scoped automatically under the .MyButton__Icon class selector.

And here's the result

Targeting child elements like this using CSS is really powerful but it should be avoided as much as possible because it makes fixed dependencies between the components that can make it harder to refactor and in worst case can leak into unwanted components. For example if the FaIcon would accept children and you would pass a path component to it the style we created earlier would apply to it.

TypeScript#

All the code shown in this tutorial has been actually valid and 100% typed TypeScript. BEMed leverages TypeScript type inference very heavily so you as the developer don't have to worry about writing types manually.

For example when we used "button" for the as property the resulting BEMed component will typed as a button.

So this invalid

<MyButton type="bad" /> // Type error!

Because the type of the type prop is "button" | "submit" | "reset" | undefined

The mod we created extend the button type and is type of boolean | undefined which means the following are valid values for it

<MyButton /> // Implicit undefined
<MyButton important /> // implicit true
<MyButton important={true} />
<MyButton important={false} />
<MyButton important={undefined} />
// but this is a type error!
<MyButton important={1} />
Edit on Github