Custom Bullets in React Using Emotion
Stock bullets shoot a hole through my soul. Time for a technical post on how to add a custom bullet component in Docusaurus and React.
I once read that a great way to think of new post topics is to consider the last thing you searched for trying to find an answer and write about that. So here I am. I was recently looking for a particular solution to a problem and was running into challenges. Things weren’t clearly laid out anywhere and I had to piece it together from various sources. Now that I’m here - at the end of the yellow brick learning path - I can share my small bits of knowledge.
The problem
I work heavily with Docusaurus to rapidly build internal documentation/simple web sites based on Markdown files and React. Basic Markdown formatting is great and carries 80% of the load, but we find ourselves building custom React components to mix with the Markdown (technically, MDX in Docusarus) when we need additional formatting/presentation options.
I love the look of custom bullet styling in lists - primarily switching away from the traditional bulleted circle to something more expressive - check marks, red x’s, lightning bolts, etc. Google Material icons make utilizing any number of custom icons relatively easy.
See the Pen Custom Bullets by Brian (@breyman) on CodePen.
After creating custom styling twice, I wanted to convert things into a custom React component to make things easier, faster, and more usable by other members of the team. Ideally, someone should set the desired Material Icon character symbol and a color value as component properties.
<CustomBulletSet>
<CustomBullet bulletcharacter="ea0b" bulletcolor="#FFCE00"></CustomBullet>
</CustomBulletSet>
I prefer to create custom bullets using the :before
CSS pseudo-element, which relies on the content:
property to set the desired replacement bullet character.
li.my-bullet-formatting-class {
list-style: none;
}
li.my-bullet-formatting-class:before {
font-family: "Material Icons";
position: relative;
content: "\ea0b";
color: #ffce00;
}
Enter the problem. I was hoping the following would work to allow dynamic passing and setting of a different character for each bullet. It didn’t. React - HTML really - doesn’t allow content:
to be set inline, which would be necessary to allow handling the property.
return (
<li
style={ { content: '\\bulletCharacter' } }
className="my-bullet-formatting-class"
>
My bullet content
</li>
)
The solution summary
There are many ways to solve this. I wanted something relatively straightforward, compatible with React and Docusaurus, and that would allow flexibility with other sorts of styling cusotmizations in the future.
I chose Emotion - a library for creating CSS with Javascript. It allows a variety of ways to write style definitions in Javascript - at build time it moves the styles to a CSS class. This provides powerful customization, including assigning variables to the content:
property, while moving things to basic CSS styles.
I also really like that it only requires installing the NPM package and importing the Emotion library into the page(s) needing it. No centralized plugin configuration or other hassles. This allows for easily removal/switching to something else in the future if I find something I’d prefer instead.
The solution details
These are the step-by-step for incorporating Emotion into a React component used in Docusaurus.
1. Install the Emotion library in the current project directory.
Emotion provides several ways to style in React - I chose the styled component approach as others caused unexpected errors in my setup I didn’t want to troubleshoot.
npm -i @emotion/styled
2. Import Emotion Into the Component
import styled from "@emotion/styled";
3. Add the styling to the component
Next, we move the styling from CSS into Emotion by creating a sub-component that Emotion handles for styling the bullet JSX tags within the overall CustomBullet component.
const Li = styled.li`
list-style: none;
padding-left: 1rem;
&:before{
font-family: "Material Icons"
color: ${bulletcolor};
position: relative;
margin-left: -1.3rem;
left: -1rem;
top: .2rem;
content: "\\${bulletid});
}
`;
return <Li>{props.children}</Li>;
That’s it! The solution came together pretty easily with Emotion in the mix.
4. All pieced together
import React from "react";
import styled from "@emotion/styled";
const CustomBulletSet = ({ ...props }) => {
return <ul>{props.children}</ul>;
};
const CustomBullet = ({
bulletid = "e5cc",
bulletcolor = "var(--ifm-color-primary)",
...props
}) => {
const Li = styled.li`
list-style: none;
padding-left: 1rem;
&:before {
font-family: "Material Icons";
color: ${bulletcolor};
position: relative;
margin-left: -1.3rem;
left: -1rem;
top: 0.22rem;
content: "\\${bulletid}";
}
`;
return <Li>{props.children}</Li>;
};
export { CustomBullet, CustomBulletSet };