👋 Next.js Conf 2024: Come build, party, run, and connect with us! See all events
Published October 02, 2024

Align Text with Block Content

By Brian Nelson

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
  ],
})
Align your block however you want in the block editor

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.