RTE Text Alignment findings & assistance

default discord avatar
10 months ago

Hey folks, I've been trying to build text alignment into my RTE (like I'm sure a lot of you have tried to do as well) and I'm wanting to share some of the stuff I've done so far. Just a disclaimer, this is currently working in a very limited capacity, the UI is glitchy, and the only thing is achieves so far is mutating the target node to have alignment as an option. I have trawled through every CMS project on the Payload Github to identify key areas for developing a custom text alignment element. Here are my findings so far;

- Text Alignment should be done using an Element rather than a Leaf, this is because leafs alter their child elements directly, whereas Elements affect a block. So if you have a h4 tag with Bold and Underline leaves, you want to add alignment to the entire text block rather than the normal text, bold and underline leaves.

- Text Alignment should not be treated as a unique element, it is an extension of the current node. According to this post -


- the author of Slate states that you should add custom data to the nodes, rather than setting the 'type' property of the node (like what happens currently when applying h1, h2, h3...)

- When attempting to use Slatejs or Slate-react directly inside Element or Button code, YOU DO NOT NEED TO NPM INSTALL THESE PACKAGES. Instead, using the version used by payload. These packages can be accessed from




And now, my code;

import React, { useCallback } from 'react';
import { ElementButton } from 'payload/components/rich-text';
import { useSlate, ReactEditor } from 'payload/node_modules/slate-react'
import { Transforms, Editor, Element as SlateElement } from 'payload/node_modules/slate'
import { IconAlignCenter, IconAlignLeft, IconAlignRight } from '../Icons';

const TEXT_ALIGN_TYPES = ['left', 'center', 'right']

type AlignmentNode = Partial<SlateElement &
    align?: typeof TEXT_ALIGN_TYPES,
    type: string

const addAlignment = (editor, format) => {

    let targetNode: AlignmentNode = undefined;

    Transforms.unwrapNodes(editor, {
        match: n => {
            var match = !Editor.isEditor(n) &&
                SlateElement.isElement(n) &&

            if (match) {
                targetNode = n as AlignmentNode;
                return true;
            return false;
        split: true,

    let newProperties: AlignmentNode = targetNode

    newProperties = {
        align: format,

    Transforms.setNodes<SlateElement>(editor, newProperties)


const Button: React.FC<{ path: string }> = () => {

    const editor = useSlate();

    return (<>
        <ElementButton onClick={useCallback(() => addAlignment(editor, 'left'), [editor])}
            <IconAlignLeft />

        <ElementButton onClick={useCallback(() => addAlignment(editor, 'center'), [editor])}
            <IconAlignCenter />

        <ElementButton onClick={useCallback(() => addAlignment(editor, 'right'), [editor])}
            <IconAlignRight />

export default Button

- Does this work?

Yeeeeaahhmmhmm... It works in such a way that it adds a custom node to the top level element. It adds an alignment field when it's applied; either left, right, or center. It doesn't actually change anything in the Editor UI as for some reason the element re-render doesn't seem to get triggered. Because it doesn't re-trigger, I am unable to actually make changes to way the text is displayed on the editor. HOWEVER, rest assured that the change has actually happened, so json object passed to your website front-end WILL show this change (big win). Obviously this isn't workable from a general user perspective as the UI appears to be unresponsive in Payload. But it's a start!

So guys, I have a baseline. Who wants to help improve it so we can actually show this in the Payload UI?

  • discord user avatar
    Payload Team
    10 months ago

    Hey Mark - this is certainly something that should be on our roadmap if it isn't already. I'll get some more answers on this one for you shortly. Stay tuned!

  • default discord avatar
    10 months ago

    No worries @seanzubrickas 🙂 I saw that 1.6 released with a new toggleElement function, that might be the thing that solves the button toggle issue. If that's the case I'll share the updated code

  • default discord avatar
    6 months ago

    hi @markatomniux , did you managed to work on the updated code with the toggleElement function?

  • default discord avatar
    6 months ago

    Hi @shooreh.pippi When I checked toggleElement, it was fixed with 2 values and didn't allow me to pass custom node elements (align). That was a while ago though so it's maybe changed since then!

    I've been looking further into the toggle Element function and I believe I can intercept it and create a new element that wraps around your other elements. I can then attempt to flatten this down so that the element's format is removed, replaced with a new "alignment" field. This might or might not still satisfy the post-condition for the toggleElement function, however if it doesn't then I will PR a change to the toggleElement function to allow for custom fields

  • default discord avatar
    6 months ago

    i wrote a really simple implementation like this:

    import { ElementButton } from "payload/components/rich-text";
    import React from "react";
    const Button = () => {
      return <ElementButton format="center">C</ElementButton>;
    const Element: React.FC<{
      children: React.ReactNode;
      attributes: any;
      element: any;
    }> = ({ attributes, children }) => {
      return (
        <div {...attributes} style={{ textAlign: "center" }}>
    export default {
      name: "center",

    and on the front-end side, the data is received with

    type: center

    , which allows me to style the text alignment accordingly. i use the


    package and pass this to



    center: ({ children }) => {
      return new Element("p", { style: "text-align: center;" }, children);

    repeating the same code for right and left alignment. probably won't work for all use cases, but works well enough for mine.

  • default discord avatar
    6 months ago

    Does this work with text that have other element types associated? For instance a H1 tag?

    I've opened a PR with a change that should allow me to implement a much more sophisticated Text Alignment solution with little additional slate code. If the PR gets approved, I'll push to have it included in Payload's default Slate editor

  • default discord avatar
    4 months ago

    Nice, when will this be available in payload default editor?

Open the post
Continue the discussion in Discord
Like what we're doing?
Star us on GitHub!


Connect with the Payload Community on Discord



Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.