Table of Contents
The problem
The solution
Making it “feel” like a code editor
More fixing!
Remove native spell checking
Handling new lines
Scrolling and resizing
Final newlines
Indenting lines
The final result
Updates
Home Web Front-end CSS Tutorial Creating an Editable Textarea That Supports Syntax-Highlighted Code

Creating an Editable Textarea That Supports Syntax-Highlighted Code

Mar 25, 2025 am 09:42 AM

Creating an Editable Textarea That Supports Syntax-Highlighted Code

When I was working on a project that needed an editor component for source code, I really wanted a way to have that editor highlight the syntax that is typed. There are projects like this, like CodeMirror, Ace, and Monaco, but they are all heavy-weight, full-featured editors, not just editable textareas with syntax highlighting like I wanted.

It took a little finagling, but I wound up making something that does the job and wanted to share how I did it, because it involves integrating a popular syntax highlighting library with HTML’s editing capabilities, as well as a few interesting edge cases to take into consideration.

Go ahead and give it a spin as we dig in!

After a suggestion, I have also released this as a custom element on GitHub, so you can quickly use the component in a webpage as a single element.</code-input></p> <h3 id="The-problem">The problem</h3> <p>First, I tried using the contenteditable attribute on a div. I typed some source code into the div and ran it through Prism.js, a popular syntax highlighter, on oninput via JavaScript. Seems like a decent idea, right? We have an element that can be edited on the front end, and Prism.js applies its syntax styling to what’s typed in the element.</p> <p>Chris covers how to use Prism.js in this video.</p> <p>But that was a no-go. Each time the content in the element changes, the DOM is manipulated and the <strong>user’s cursor is pushed back to the start of the code</strong>, meaning the source code appears backwards, with the last characters at the start, and the first characters at the end.</p> <p>Next, I tried about using a <textarea> but that also didn’t work, as <strong>textareas can only contain plain text</strong>. In other words, we’re unable to style the content that’s entered. A textarea seems to be the only way to edit the text without unwanted bugs — it just doesn’t let Prism.js do its thing.</textarea></p> <p>Prism.js works a lot better when the source code is wrapped in a typical </p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">&lt;code&gt; tag combo — it’s only missing the editable part of the equation. &lt;p&gt;So, neither seems to work by themselves. But, I thought, &lt;em&gt;why not both?&lt;/em&gt;&lt;/p&gt; &lt;h3 id=&quot;The-solution&quot;&gt;The solution&lt;/h3&gt; &lt;p&gt;I added both a syntax-highlighted &lt;/p&gt; &lt;pre class=&quot;brush:php;toolbar:false&quot;&gt;&lt;code&gt; &lt;em&gt;and&lt;/em&gt; a textarea to the page, and made the innerText content of &lt;pre class=&quot;brush:php;toolbar:false&quot;&gt;&lt;code&gt; change oninput, using a JavaScript function. I also added an aria-hidden attribute to the &lt;pre class=&quot;brush:php;toolbar:false&quot;&gt;&lt;code&gt; result so that screen readers would only read what is entered into the &lt;textarea&gt; instead of being read aloud twice. &lt;pre rel=&quot;HTML&quot; data-line=&quot;&quot;&gt;&lt;textarea oninput=&quot;update(this.value);&quot;&gt;&lt;/textarea&gt; &lt;pre aria-hidden=&quot;true&quot;&gt; &lt;code&gt;&lt;/code&gt; </pre><div class="contentsignin">Copy after login</div></div></pre> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="JavaScript" data-line="">function update(text) { let result_element = document.querySelector(&quot;#highlighting-content&quot;); // Update code result_element.innerText = text; // Syntax Highlight Prism.highlightElement(result_element); }</pre><div class="contentsignin">Copy after login</div></div> <p>Now, when the textarea is edited — as in, a pressed key on the keyboard comes back up — the syntax-highlighted code changes. There are a few bugs we’ll get to, but I want to focus first on making it look like you are directly editing the syntax-highlighted element, rather than a separate textarea.</p> <h3 id="Making-it-feel-like-a-code-editor">Making it “feel” like a code editor</h3> <p>The idea is to visibly merge the elements together so it looks like we’re interacting with one element when there are actually two elements at work. We can add some CSS that basically allows the <textarea> and the <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">&lt;code&gt; elements to be sized and spaced consistently.&lt;/code&gt;</pre><div class="contentsignin">Copy after login</div></div></textarea></p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="CSS" data-line="">#editing, #highlighting { /* Both elements need the same text and space styling so they are directly on top of each other */ margin: 10px; padding: 10px; border: 0; width: calc(100% - 32px); height: 150px; } #editing, #highlighting, #highlighting * { /* Also add text styles to highlighting tokens */ font-size: 15pt; font-family: monospace; line-height: 20pt; }</pre><div class="contentsignin">Copy after login</div></div> <p>Then we want to position them right on top of each other:</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="CSS" data-line="">#editing, #highlighting { position: absolute; top: 0; left: 0; }</pre><div class="contentsignin">Copy after login</div></div> <p>From there, z-index allows the textarea to stack in front the highlighted result:</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="CSS" data-line="">/* Move the textarea in front of the result */ #editing { z-index: 1; } #highlighting { z-index: 0; }</pre><div class="contentsignin">Copy after login</div></div> <p>If we stop here, we’ll see our first bug. It doesn’t look like Prism.js is highlighting the syntax, but that is only because the textarea is covering up the result.</p> <p>We can fix this with CSS! We’ll make the <textarea> completely transparent <em>except</em> the caret (cursor):</textarea></p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="CSS" data-line="">/* Make textarea almost completely transparent */ #editing { color: transparent; background: transparent; caret-color: white; /* Or choose your favorite color */ }</pre><div class="contentsignin">Copy after login</div></div> <p>Ah, much better!</p> <h3 id="More-fixing">More fixing!</h3> <p>Once I got this far, I played around with the editor a bit and was able to find a few more things that needed fixing. The good thing is that all of the issues are quite easy to fix using JavaScript, CSS, or even HTML.</p> <h4 id="Remove-native-spell-checking">Remove native spell checking</h4> <p>We’re making a code editor, and code has lots of words and attributes that a browser’s native spell checker will think are misspellings.</p> <p>Spell checking isn’t a bad thing; it’s just unhelpful in this situation. Is something marked incorrect because it is incorrectly spelled or because the code is invalid? It’s tough to tell. To fix this, all we need is to set the spellcheck attribute on the <textarea> to false:</textarea></p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="HTML" data-line="">&lt;textarea spellcheck=&quot;false&quot; ...&gt;&lt;/textarea&gt;</pre><div class="contentsignin">Copy after login</div></div> <h4 id="Handling-new-lines">Handling new lines</h4> <p>Turns out that innerText doesn’t support newlines (\n).</p> <p>The update function needs to be edited. Instead of using innerText, we can use innerHTML, replacing the open bracket character ( </p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="JavaScript" data-line="">result_element.innerHTML = text.replace(new RegExp(&quot;&amp;&quot;, &quot;g&quot;), &quot;&amp;&quot;).replace(new RegExp(&quot; &lt;h4 id=&quot;Scrolling-and-resizing&quot;&gt;Scrolling and resizing&lt;/h4&gt; &lt;p&gt;Here’s another thing: the highlighted code cannot scroll while the editing is taking place. And when the textarea is scrolled, the highlighted code does not scroll with it.&lt;/p&gt; &lt;p&gt;First, let’s make sure that both the textarea and result support scrolling:&lt;/p&gt; &lt;pre rel=&quot;CSS&quot; data-line=&quot;&quot;&gt;/* Can be scrolled */ #editing, #highlighting { overflow: auto; white-space: pre; /* Allows textarea to scroll horizontally */ }</pre><div class="contentsignin">Copy after login</div></div> <p>Then, to make sure that the result scrolls <em>with</em> the textarea, we’ll update the HTML and JavaScript like this:</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="HTML" data-line="">&lt;textarea oninput=&quot;update(this.value); sync_scroll(this);&quot; onscroll=&quot;sync_scroll(this);&quot;&gt;&lt;/textarea&gt;</pre><div class="contentsignin">Copy after login</div></div> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="JavaScript" data-line="">function sync_scroll(element) { /* Scroll result to scroll coords of event - sync with textarea */ let result_element = document.querySelector(&quot;#highlighting&quot;); // Get and set x and y result_element.scrollTop = element.scrollTop; result_element.scrollLeft = element.scrollLeft; }</pre><div class="contentsignin">Copy after login</div></div> <p>Some browsers also allow a textarea to be resized, but this means that the textarea and result could become different sizes. Can CSS fix this? Of course it can. We’ll simply disable resizing:</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="CSS" data-line="">/* No resize on textarea */ #editing { resize: none; }</pre><div class="contentsignin">Copy after login</div></div> <h4 id="Final-newlines">Final newlines</h4> <p>Thanks to this comment for pointing out this bug.</p> <p>Now the scrolling is almost always synchronized, but there is still <strong>one case where it still doesn’t work</strong>. When the user creates a new line, the cursor and the textarea’s text are <strong>temporarily in the wrong position</strong> before any text is entered on the new line. This is because <strong>the <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">&lt;code&gt; block ignores an empty final line for aesthetic reasons.&lt;/code&gt;</pre><div class="contentsignin">Copy after login</div></div></strong> Because this is for a functional code input, rather than a piece of displayed code, the empty final line needs to be shown. This is done by <strong>giving the final line content so it is no longer empty</strong>, with a few lines of JavaScript added to the update function. I have used a space character because it is <strong>invisible to the user</strong>.</p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="JavaScript" data-line="3-6">function update(text) { let result_element = document.querySelector(&quot;#highlighting-content&quot;); // Handle final newlines (see article) if(text[text.length-1] == &quot;\n&quot;) { // If the last character is a newline character text = &quot; &quot;; // Add a placeholder space character to the final line } // Update code result_element.innerHTML = text.replace(new RegExp(&quot;&amp;&quot;, &quot;g&quot;), &quot;&amp;&quot;).replace(new RegExp(&quot; &lt;h4 id=&quot;Indenting-lines&quot;&gt;Indenting lines&lt;/h4&gt; &lt;p&gt;One of the trickier things to adjust is how to handle line indentations in the result. The way the editor is currently set up, indenting lines with spaces works fine. But, if you’re more into tabs than spaces, you may have noticed that those aren’t working as expected.&lt;/p&gt; &lt;p&gt;JavaScript can be used to make the &lt;kbd&gt;Tab&lt;/kbd&gt; key properly work. I have added comments to make it clear what is happening in the function.&lt;/p&gt; &lt;pre rel=&quot;HTML&quot; data-line=&quot;&quot;&gt;&lt;textarea ... onkeydown=&quot;check_tab(this, event);&quot;&gt;&lt;/textarea&gt;</pre><div class="contentsignin">Copy after login</div></div> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="JavaScript" data-line="">function check_tab(element, event) { let code = element.value; if(event.key == &quot;Tab&quot;) { /* Tab key pressed */ event.preventDefault(); // stop normal let before_tab = code.slice(0, element.selectionStart); // text before tab let after_tab = code.slice(element.selectionEnd, element.value.length); // text after tab let cursor_pos = element.selectionEnd 1; // where cursor moves after tab - moving forward by 1 char to after tab element.value = before_tab &quot;\t&quot; after_tab; // add tab char // move cursor element.selectionStart = cursor_pos; element.selectionEnd = cursor_pos; update(element.value); // Update text to include indent } }</pre><div class="contentsignin">Copy after login</div></div> <p>To make sure the <strong>Tab characters are the same size</strong> in both the <textarea> and the syntax-highlighted code block, edit the CSS to include the tab-size property:</textarea></p> <div class="code" style="position:relative; padding:0px; margin:0px;"><pre rel="CSS" data-line="4">#editing, #highlighting, #highlighting * { /* Also add text styles to highlighing tokens */ /* etc. */ tab-size: 2; }</pre><div class="contentsignin">Copy after login</div></div> <h3 id="The-final-result">The final result</h3> <p>Not too crazy, right? All we have are <textarea>, <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false"> and &lt;code&gt; elements in the HTML, new lines of CSS that stack them together, and a syntax highlighting library to format what’s entered. And what I like best about this is that we’re working with normal, semantic HTML elements, leveraging native attributes to get the behavior we want, leaning on CSS to create the illusion that we’re only interacting with one element, and then reaching for JavaScript to solve some edge cases.&lt;/code&gt;</pre><div class="contentsignin">Copy after login</div></div></textarea></p> <p>While I used Prism.js for syntax highlighting, this technique will work with others. It would even work with a syntax highlighter you create yourself if you want it to. I hope this becomes useful and can be used in many places, whether it’s a WYSIWYG editor for a CMS or even forms where the ability to enter source code is a requirement like a front-end job application or perhaps a quiz. It is a<textarea>, after all, so it’s capable of being used in any form — you can even add a placeholder if you need to!</textarea></p> <p><strong>Update (Aug. 27, 2024):</strong> Reader Luis Lobo writes in with a React version of this in CodePen:</p> <h3 id="Updates">Updates</h3> </pre></pre> <ul> <li> <strong>January 13, 2025:</strong> The example was updated with a white-space: pre; declaration to treat whitespace equally in all browsers, allowing for better horizontal scrolling.</li> </ul></textarea>

The above is the detailed content of Creating an Editable Textarea That Supports Syntax-Highlighted Code. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Roblox: Bubble Gum Simulator Infinity - How To Get And Use Royal Keys
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
Nordhold: Fusion System, Explained
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
Mandragora: Whispers Of The Witch Tree - How To Unlock The Grappling Hook
3 weeks ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Hot Topics

Java Tutorial
1672
14
PHP Tutorial
1277
29
C# Tutorial
1257
24
A Comparison of Static Form Providers A Comparison of Static Form Providers Apr 16, 2025 am 11:20 AM

Let’s attempt to coin a term here: "Static Form Provider." You bring your HTML

A Proof of Concept for Making Sass Faster A Proof of Concept for Making Sass Faster Apr 16, 2025 am 10:38 AM

At the start of a new project, Sass compilation happens in the blink of an eye. This feels great, especially when it’s paired with Browsersync, which reloads

Weekly Platform News: HTML Loading Attribute, the Main ARIA Specifications, and Moving from iFrame to Shadow DOM Weekly Platform News: HTML Loading Attribute, the Main ARIA Specifications, and Moving from iFrame to Shadow DOM Apr 17, 2025 am 10:55 AM

In this week&#039;s roundup of platform news, Chrome introduces a new attribute for loading, accessibility specifications for web developers, and the BBC moves

Some Hands-On with the HTML Dialog Element Some Hands-On with the HTML Dialog Element Apr 16, 2025 am 11:33 AM

This is me looking at the HTML element for the first time. I&#039;ve been aware of it for a while, but haven&#039;t taken it for a spin yet. It has some pretty cool and

Paperform Paperform Apr 16, 2025 am 11:24 AM

Buy or build is a classic debate in technology. Building things yourself might feel less expensive because there is no line item on your credit card bill, but

Weekly Platform News: Text Spacing Bookmarklet, Top-Level Await, New AMP Loading Indicator Weekly Platform News: Text Spacing Bookmarklet, Top-Level Await, New AMP Loading Indicator Apr 17, 2025 am 11:26 AM

In this week&#039;s roundup, a handy bookmarklet for inspecting typography, using await to tinker with how JavaScript modules import one another, plus Facebook&#039;s

Where should 'Subscribe to Podcast' link to? Where should 'Subscribe to Podcast' link to? Apr 16, 2025 pm 12:04 PM

For a while, iTunes was the big dog in podcasting, so if you linked "Subscribe to Podcast" to like:

Options for Hosting Your Own Non-JavaScript-Based Analytics Options for Hosting Your Own Non-JavaScript-Based Analytics Apr 15, 2025 am 11:09 AM

There are loads of analytics platforms to help you track visitor and usage data on your sites. Perhaps most notably Google Analytics, which is widely used

See all articles