Simple WYSIWYG Text Editor Component
A lightweight, browser-native WYSIWYG text editor component. It leverages the contenteditable attribute and document.execCommand for basic formatting like bold and italic, along with text alignment. The component includes a clean toolbar, word count functionality.
LTR
RTL
<div class="max-w-4xl mx-auto">
<h1 class="text-3xl font-bold text-gray-800 dark:text-white mb-2">Custom Text Editor</h1>
<p class="text-gray-600 dark:text-gray-300 mb-8">A simple text editor with dark mode and RTL support</p>
<!-- Text Editor Component -->
<div
x-data="{
darkMode: false,
rtl: false,
wordCount: 0,
init() {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
this.darkMode = true;
document.documentElement.classList.add('dark');
}
this.updateWordCount();
this.$refs.editor.addEventListener('input', () => { this.updateWordCount(); });
},
formatText(command) {
document.execCommand(command, false, null);
this.$refs.editor.focus();
},
setAlignment(align) {
document.execCommand('justifyLeft', false, null);
document.execCommand('justifyCenter', false, null);
document.execCommand('justifyRight', false, null);
document.execCommand('justifyFull', false, null);
if (align === 'center') {
document.execCommand('justifyCenter', false, null);
} else if (align === 'right') {
document.execCommand('justifyRight', false, null);
} else if (align === 'justify') {
document.execCommand('justifyFull', false, null);
} else {
document.execCommand('justifyLeft', false, null);
}
this.$refs.editor.focus();
},
updateWordCount() {
const text = this.$refs.editor.innerText || '';
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
this.wordCount = words;
}
}"
x-init="init()"
class="bg-white dark:bg-gray-700 rounded-lg shadow-md overflow-hidden transition-colors duration-200">
<!-- Toolbar -->
<div class="flex flex-wrap items-center gap-2 p-3 border-b border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800">
<!-- Formatting buttons -->
<button @click="formatText('bold')" type="button"
class="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 transition-colors"
title="Bold">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M6 12H12.5C14.9853 12 17 9.98528 17 7.5C17 5.01472 14.9853 3 12.5 3H6V12ZM6 12H13.5C15.9853 12 18 14.0147 18 16.5C18 18.9853 15.9853 21 13.5 21H6V12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button @click="formatText('italic')" type="button"
class="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 transition-colors"
title="Italic">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path d="M10 3H20M4 21H14M15 3L9 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="h-6 border-l border-gray-300 dark:border-gray-600 mx-1"></div>
<!-- Text alignment -->
<button @click="setAlignment('left')" type="button"
class="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 transition-colors"
title="Align Left">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 14h18M3 18h10M3 6h18"></path>
</svg>
</button>
<button @click="setAlignment('center')" type="button"
class="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 transition-colors"
title="Align Center">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 14h10M9 18h6M5 6h14"></path>
</svg>
</button>
<button @click="setAlignment('right')" type="button"
class="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 transition-colors"
title="Align Right">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M9 14h10M5 18h14M3 6h18"></path>
</svg>
</button>
<button @click="setAlignment('justify')" type="button"
class="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 transition-colors"
title="Justify">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 14h18M3 18h18M3 6h18"></path>
</svg>
</button>
<div class="flex-1"></div>
</div>
<!-- Editor content -->
<div
x-ref="editor"
:dir="rtl ? 'rtl' : 'ltr'"
:class="rtl ? 'rtl:text-right' : 'text-left'"
class="p-4 min-h-[200px] max-h-[400px] overflow-y-auto text-gray-800 dark:text-gray-200 bg-white dark:bg-gray-700 transition-colors duration-200"
contenteditable="true">
<p>Start typing your content here...</p>
</div>
<!-- Word count -->
<div class="px-4 py-2 text-xs text-gray-500 dark:text-gray-400 border-t border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 transition-colors duration-200">
<span x-text="wordCount"></span> words
</div>
</div>
<!-- Instructions -->
<div class="mt-8 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<h3 class="font-semibold text-blue-800 dark:text-blue-300 mb-2">How to use this editor:</h3>
<ul class="text-blue-700 dark:text-blue-400 text-sm list-disc pl-5 space-y-1">
<li>Select text and use the toolbar buttons to format it</li>
<li>Use the alignment buttons to change text alignment</li>
</ul>
</div>
</div>