DEV Community

Konnor Rogers
Konnor Rogers

Posted on • Edited on

Adding text alignment to Trix

A user had asked in Discord "Is there a way to add text alignment to Trix"

I thought this was a pretty basic ask for a Rich Text Editor, but to my surprise, as far as I can tell, Trix has no nice way of handling this. textAttributes support "style" attributes, but those are for inline elements. blockAttributes are intended for wrappers, but dont support "style" attributes. As far as I can tell, only tagName is supported.

With a few hours of exploration and googling down the drain, I saw someone mention using WebComponents to fill the gap in Trix. So, here's the result of me trying that approach!

The first step is to override the default Trix text editor's toolbar, which is covered in a previous part of the series.

https://dev.to/paramagicdev/modifying-the-default-toolbar-in-trix-411b

We will import Trix's default styles for simplicity here rather than introducing our own. To start, our javascript file should look something like this:

// application.js
import Trix from 'trix';

// Import trix's css for simplicity.
import 'trix/dist/trix.css';

Trix.config.toolbar.getDefaultHTML = toolbarDefaultHTML;

document.addEventListener('trix-initialize', updateToolbars, { once: true });

function updateToolbars(event) {
  const toolbars = document.querySelectorAll('trix-toolbar');
  const html = Trix.config.toolbar.getDefaultHTML();
  toolbars.forEach((toolbar) => (toolbar.innerHTML = html));
}

function toolbarDefaultHTML() {
  const { lang } = Trix.config;
  // omitted for brevity.
  return ``
Enter fullscreen mode Exit fullscreen mode

Alright, now that we have the boilerplate out of the way, the first step is to add icons to our default html for our toolbar. To do so, we can add HTML to our toolbarDefaultHTML like so:

function toolbarDefaultHTML() {
  const { lang } = Trix.config;
  return `
  <div class="trix-button-row">
    <span class="trix-button-group trix-button-group--alignment-tools">
      <button type="button" class="trix-button trix-button--icon trix-button--icon-align-left" data-trix-attribute="alignLeft" title="Align Left" tabindex="-1">Align Left</button>

      <button type="button" class="trix-button trix-button--icon trix-button--icon-align-center" data-trix-attribute="alignCenter" title="Align Left" tabindex="-1">Align Center</button>

      <button type="button" class="trix-button trix-button--icon trix-button--icon-align-right" data-trix-attribute="alignRight" title="Align Right" tabindex="-1">Align Right</button>
    </span>
    <!-- Other default HTML below -->
  </div>`
Enter fullscreen mode Exit fullscreen mode

Then, in your CSS, you can add the following (icons pulled from Bootstrap Icons):

/* styles.css */
.trix-button--icon-align-left::before {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-left" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M2 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>');
}

.trix-button--icon-align-center::before {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-center" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>');
}
.trix-button--icon-align-right::before {
  background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-right" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-4-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm4-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-4-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>');
}
Enter fullscreen mode Exit fullscreen mode

Dont forget to import your styles!!

Finally, we get into how the magic will happen. Let's start by creating some basic custom elements. We'll be creating the following:

  • <align-left>
  • <align-center>
  • <align-right>

Here is the JS I used to create the 3 elements:

class BaseElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }
}

function innerHTML(alignment) {
  return `
    <style>
      :host {
        text-align: ${alignment};
        width: 100%;
        display: block;
      }
    </style>

    <slot></slot>
  `;
}

export class AlignLeftElement extends BaseElement {
  constructor() {
    super();

    this.shadowRoot.innerHTML = innerHTML('left');
  }
}

export class AlignCenterElement extends BaseElement {
  constructor() {
    super();

    this.shadowRoot.innerHTML = innerHTML('center');
  }
}

export class AlignRightElement extends BaseElement {
  constructor() {
    super();

    this.shadowRoot.innerHTML = innerHTML('right');
  }
}

window.customElements.define('align-left', AlignLeftElement);
window.customElements.define('align-center', AlignCenterElement);
window.customElements.define('align-right', AlignRightElement);
Enter fullscreen mode Exit fullscreen mode

With our newly minted custom elements, we can now add them to our Trix config for when our alignment buttons are triggered.

Here is how to add our alignment tags to Trix's config:

Trix.config.toolbar.getDefaultHTML = toolbarDefaultHTML;

Trix.config.blockAttributes.alignLeft = {
  tagName: 'align-left',
  parse: false,
  nestable: false,
  exclusive: true,
};

Trix.config.blockAttributes.alignCenter = {
  tagName: 'align-center',
  parse: false,
  nestable: false,
  exclusive: true,
};

Trix.config.blockAttributes.alignRight = {
  tagName: 'align-right',
  parse: false,
  nestable: false,
  exclusive: true,
};

// Below omitted for brevity.
Enter fullscreen mode Exit fullscreen mode

And thats it! You now have alignment baked into your Trix editor!!

A stackblitz repo can be found here:

https://stackblitz.com/edit/js-cb4oo7?file=index.js

And here is a preview video of how it looks / works!

https://twitter.com/RogersKonnor/status/1493387234455785478

Good luck and enjoy your Trix adventure with newly added alignment!

EDIT: You may also want to add these to the ActionText allowed tags.

https://github.com/rails/rails/blob/4328d0e16028a46bba79ab775e509a743ceaf18c/actiontext/app/helpers/action_text/content_helper.rb#L7-L10

Top comments (2)

Collapse
 
martin_meier_d0c7521703a3 profile image
Martin Meier • Edited

Setting the blockAttributes to "parse: false" lead in my case to attributes that were saved in the ActionText but "removed" by Trix on next display for edit.

The "value" of the hidden input was OK but in the editor, the tags were converted to "div".

Removing the "parse: false" fixed the issue.

Collapse
 
justintanner profile image
Justin Tanner

Fantastic article, this works perfectly, with one exception.

A centered heading, try this:

  1. Create an H1 by highlighting text and clicking "Tt"
  2. Highlight the same text and click center
  3. Text is not centered

But, changing the order like so works:

  1. Highlight text click center
  2. Highlight the same text click H1 (or Tt)
  3. Heading is centered

Any ideas on how to fix that?