RGB logo

RGB Studios.org

A web development company

April 26, 2024

How to Create a Rich Text Editor in JavaScript

Justin Golden

webdev
js
Photo credit @brett_jordan on Unsplash

Intro

Rich Text editing can be quite helpful for many users and use cases, but they also may be intimidating to create. There are many packages to help with this, but let’s say you want to create one yourself, either to learn, to be in control of your code, to keep the file size down, or just for fun.

The Secret: contenteditable

You may or may not know about contenteditable, but that’s our not so secret sauce here.

Simply adding contenteditable to a div:

<div contenteditable="true"></div>

Will already get you pretty far (try using ctrl+b to bold selected text above for example).

Secret 2: execCommand and Obligatory Warning

We will also be using execCommand to perform formatting on our contenteditable, which as you may know, is depreciated with no alternative, however, it’s got 98.3% browser support on caniuse and doesn’t seemt o be going away any time soon.

With that out of the way, let’s begin:

Shortcut Buttons: The Basics

Feel free to follow along in your own project, or make a new code pen at pen.new

I’ll start us off with some buttons, a wrapper for them, and some basic CSS:

<div id="editor" contenteditable="true"></div>

<div id="button-wrapper">
	<button id="bold"><strong>B</strong></button>
	<button id="italic"><em>I</em></button>
	<button id="underline"><u>U</u></button>
</div>
body {
	margin: 16px;
	padding: 0;
}

#editor {
	border: 1px solid #e6e6e6;
	margin: 16px 0;
	padding: 16px;
	font-size: 16px;

	border-radius: 4px;
}

#button-wrapper {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

button {
	padding: 8px 16px;
	font-size: 16px;
	border: 1px solid #e6e6e6;
	border-radius: 4px;
	background-color: #f0f0f0;
	color: #333;
	cursor: pointer;
}

button:hover {
	background-color: #e6e6e6;
}

Now, let’s make this work. Here’s the JS:

document.addEventListener('DOMContentLoaded', () => {
	function format(command, value) {
		document.execCommand(command, false, value);
	}

	document.getElementById('bold').addEventListener('click', () => document.execCommand('bold'));

	document.getElementById('italic').addEventListener('click', () => document.execCommand('italic'));

	document
		.getElementById('underline')
		.addEventListener('click', () => document.execCommand('underline'));
});

This is quite repetitive, and we’ll be reusing that same execCommand over and over, so let’s clean this up before it gets out of hand:

document.addEventListener('DOMContentLoaded', () => {
	function format(command) {
		document.execCommand(command);
	}

	document.getElementById('bold').addEventListener('click', () => format('bold'));

	document.getElementById('italic').addEventListener('click', () => format('italic'));

	document.getElementById('underline').addEventListener('click', () => format('underline'));
});

You’ll see why we create this wrapper function later.

Final Result

Now, let’s add some more buttons. I’ll be adding alignment, color, and font settings.

We can make use of the other parameters in execCommand in our wrapper function now. We will use prompt to ask the user to type in color and font settings, which of course can and should be changed when moving being this proof of concept.

<div id="editor" contenteditable="true"></div>

<div id="button-wrapper">
	<button id="bold"><strong>B</strong></button>
	<button id="italic"><em>I</em></button>
	<button id="underline"><u>U</u></button>
	<button id="alignLeft">Align Left</button>
	<button id="alignCenter">Align Center</button>
	<button id="alignRight">Align Right</button>
	<button id="textColor">Text Color</button>
	<button id="bgColor">Background Color</button>
	<button id="fontSize">Font Size</button>
	<button id="fontFamily">Font Family</button>
	<button id="log">Console Log</button>
</div>
body {
	margin: 16px;
	padding: 0;
}

#editor {
	border: 1px solid #e6e6e6;
	margin: 16px 0;
	padding: 16px;
	font-size: 16px;

	border-radius: 4px;
}

#button-wrapper {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
}

button {
	padding: 8px 16px;
	font-size: 16px;
	border: 1px solid #e6e6e6;
	border-radius: 4px;
	background-color: #f0f0f0;
	color: #333;
	cursor: pointer;
}

button:hover {
	background-color: #e6e6e6;
}
document.addEventListener('DOMContentLoaded', () => {
	const editor = document.getElementById('editor');

	function format(command, value) {
		document.execCommand(command, false, value);
	}

	document.getElementById('bold').addEventListener('click', () => format('bold'));

	document.getElementById('italic').addEventListener('click', () => format('italic'));

	document.getElementById('underline').addEventListener('click', () => format('underline'));

	document.getElementById('alignLeft').addEventListener('click', () => format('justifyLeft'));

	document.getElementById('alignCenter').addEventListener('click', () => format('justifyCenter'));

	document.getElementById('alignRight').addEventListener('click', () => format('justifyRight'));

	document.getElementById('log').addEventListener('click', () => console.log(editor.innerHTML));

	document.getElementById('textColor').addEventListener('click', () => {
		const color = prompt('Enter text color (e.g., red, #f00):');
		if (color !== null) {
			format('foreColor', color);
		}
	});

	document.getElementById('bgColor').addEventListener('click', () => {
		const color = prompt('Enter background color (e.g., red, #f00):');
		if (color !== null) {
			format('backColor', color);
		}
	});

	document.getElementById('fontSize').addEventListener('click', () => {
		const size = prompt('Enter font size (e.g., 16px):');
		if (size !== null) {
			format('fontSize', size);
		}
	});

	document.getElementById('fontFamily').addEventListener('click', () => {
		const fontFamily = prompt('Enter font family (e.g., Arial, Times New Roman):');
		if (fontFamily !== null) {
			document.execCommand('fontName', false, fontFamily);
		}
	});
});

More Blog Articles
  Share   Tweet   Pin   Share   Post   Post   Share   Email