Styling Storyblok Richtext with Tailwind
How to implement custom richtext schema
The whole code can be found in this GitHub repository: https://github.com/marekrozmus/blog-styling-storyblok-richtext-with-tailwind
Rendering the Richtext in Storyblok is easy — put the content of Richtext into the provided renderer:
import { renderRichText } from "@storyblok/astro";
const { blok } = Astro.props
const renderedRichText = renderRichText(blok.richtext);
and then put that in some div
, Fragment
or whatever you need:
<div class="w-full flex flex-col gap-4">
<Fragment set:html={renderedRichText} />
</div>
The Fragment
content will be filled with a HTML representation of the Richtext.
I am using Astro here but the API is same for React and other frameworks.
The styling
When it comes to styling we can easily style the elements with pure CSS and selectors. But what if we want to style the Richtext with Tailwind classes and not by CSS selectors?
The schema
The answer is to use custom Richtext schema and use it in the render method:
const renderedRichText = renderRichText(
blok.richtext,
{ schema: newRichTextSchema }
);
OK, but how to prepare custom schema? The Storyblok’s documentation says:
// you can make a copy of the default RichTextSchema
// ... and edit the nodes and marks, or add your own.
// Check the base RichTextSchema source here:
//https://github.com/storyblok/storyblok-js-client/blob/main/src/schema.ts
But it actually does not show any example how to do it.
The idea
Here is one way of how it can be done. If you know some other please write in comments below the article.
First (as written in docs) we need to make a deep copy of the schema:
import cloneDeep from 'clone-deep';
const newRichTextSchema = cloneDeep(RichTextSchema);
And now we can tune our new shiny schema to instruct the render method how the elements should look like.
The headings
The default schema for the heading looks like this and can be found in the mentioned Storyblok repository:
const heading: NodeSchema = (node: ISbNode) => {
return {
tag: `h${node.attrs.level}`,
}
}
This is what you will see in the Storyblok UI richtext field:
To style it we need to update the returned object to have also attrs
property. The cx
used in the example code below is the classnames
module I use to join/add conditionally the class names.
In this case different classes are applied to different heading level. The attrs.level
hold the level of the heading.
newRichTextSchema.nodes.heading = (node: ISbNode) => ({
tag: [
{
tag: `h${node.attrs.level}`,
attrs: {
class: cx('font-bold', {
'text-lime-500 text-2xl': node.attrs.level === 1,
'text-teal-700 text-xl': node.attrs.level === 2,
'text-blue-800 text-lg': node.attrs.level === 3,
'text-fuchsia-600 text-base': node.attrs.level === 4,
'text-rose-600 text-sm': node.attrs.level === 5,
'text-yellow-500 text-xs': node.attrs.level === 6,
}),
},
},
],
});
We can use whatever tailwind classes here we want. This is the outcome:
And just a note — the gaps between heading elements are coming from the parent but you can also add paddings on the element themselves if needed.
The paragraph
The default schema looks like this:
const paragraph: NodeSchema = () => {
return {
tag: 'p',
}
}
Actually same structure as in case of heading. We can use same approach to style it:
newRichTextSchema.nodes.paragraph = () => {
return {
tag: [
{
tag: 'p',
attrs: {
class: 'text-blue-800 lg:font-normal',
},
},
],
};
};
The link
The link has much more code and you can check it here. We can’t overwrite whole implementation like in previous examples as we will loose some functionality (unless you do not need it).
What is actually crucial in the default implementation is the structure that is returned:
return {
tag: [
{
tag: 'a',
attrs: attrs,
},
],
}
So it looks like all the previous examples we have styled here. The only difference here is the way how we will “push” those classes into that element.
newRichTextSchema.marks.link = (node: ISbNode) => {
node.attrs.class = 'text-rose-600 hover:text-yellow-500 underline';
return RichTextSchema.marks.link(node);
};
So we get the object, apply there some changes (adding the classes) and then use it as argument to default schema which will do all other work for us.
The summary
I showed here just few of element but this idea can be applied to style also other elements from the schema.
You can prepare one schema for your application or several schemas for every Storyblok’s blok.
Do you like my content? You can support me and buy me a coffee. Thank you so much!