Align Text with Block Content
After doing some searching, I couldn't find a solution to allowing users to align text themselves in the Sanity Block Content type. I spent some time looking over different documentation on how to customize the editor as well as being able to serialize it correctly and I'm happy with the solution I came up with.
I tried to achieve aligning text a couple different ways, the first being to add it as its own object, similar to how we add images to block content. I didn't love this solution because it opened an additional popup for a user to select an option from a list of strings.
I wanted this to be a simple and intuitive solution for user to use without more popups. So I figured the best option was to add it inside of the marks > decorators, along with strong, em, underline, etc. So lets do that first:
// blockContent.ts SCHEMA
marks: {
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
{title: 'Left', value: 'left',},
{title: 'Center', value: 'center',},
{title: 'Right', value: 'right',},
],
annotations: [
// ... //
],
},
Gotcha
The only downside of using decorators is users can select more than 1 decorator, but it does work as long as only one of the alignments is selected.
So now we are able to select the alignment we want and receive a value from it, one problem is theres no icon to go with it. Luckily the decorator objects allow for an icon
value so it's as simple as adding the icon after the value field.
// blockContent.ts SCHEMA
import {ICON_NAME} from 'ICON_LIBRARY'';
// rest of the code
marks: {
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
{title: 'Left', value: 'left', icon: LeftAlignIcon,},
{title: 'Center', value: 'center', icon: CenterAlignIcon,},
{title: 'Right', value: 'right', icon: RightAlignIcon,},
],
},
Now we're getting somewhere. I like to use the Heroicons library and store the jsx svg inside an icons folder to easily reuse as I need without the need to import entire libraries. Now even though we are getting the correct value when we select them (looking at our inspector), there's no visual indicator to show the user what alignment their block has on it.
To fix this, we will make use of a custom component that decorators allow us to set. Thankfully the customizing the portable text editor docs has an example of passing a jsx/tsx component into the decorator to give the visual cue we are looking for.
// TextAlignComponent.tsx
import React from 'react'
export const TextAlign = (props: any) => {
return (
<div style={{textAlign: props.value ? props.value : 'left', width: '100%'}}>
{props.children}
</div>
)
}
This component takes in props from the decorator and passes in the value
as where to align the text and the children
as whatever the content of that block is. I tried using <span>...</span>
originally but spans don't extend the full width of the available space so using a <div>
is what we got. I set the textAlign
style to default to left if there is no props.value
And we now pass in the component into the blockContent type
// blockContent.ts SCHEMA
import CenterAlignIcon from '@/components/icons/CenterAlignIcon'
import LeftAlignIcon from '@/components/icons/LeftAlignIcon'
import RightAlignIcon from '@/components/icons/RightAlignIcon'
import { TextAlign } from '@/sanity/lib/components/TextAlign'
import {defineType, defineArrayMember, defineField} from 'sanity'
export default defineType({
title: 'Block Content',
name: 'blockContent',
type: 'array',
of: [
defineArrayMember({
title: 'Block',
type: 'block',
// the rest of the styles and list types
marks: {
decorators: [
{title: 'Strong', value: 'strong'},
{title: 'Emphasis', value: 'em'},
{title: 'Left', value: 'left', icon: LeftAlignIcon, component: (props) => TextAlign(props)},
{title: 'Center', value: 'center', icon: CenterAlignIcon, component: (props) => TextAlign(props)},
{title: 'Right', value: 'right', icon: RightAlignIcon, component: (props) => TextAlign(props)},
],
},
}),
// any other objects to add to your blockContent
],
})
Now we have 3 new text "decorators" that assign a value to the the block as well as visually showing the writer/editor where their text will be aligned. Finally we need to add the rule to our RichText serializer so we get the same outcome on the frontend.
// RichTextComponent.tsx
export const RichTextComponent: any = {
// ... all of your other types, lists, and blocks
marks: {
left: ({children}: any) => <div className="text-left">{children}</div>,
center: ({children}: any) => <div className="text-center w-full">{children}</div>,
right: ({children}: any) => <div className="text-right">{children}</div>,
}
}
Here we add the 3 new value options we expect inside of the marks
field. If left, use a div with the style or class text-align: left. If center, use a div with the style or class text-align: center. If right, use a div with the style or class text-align: right. Again we have to use a div here because span doesn't extend the full available width.
Sanity – build remarkable experiences at scale
Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.