search
HomeWeb Front-endCSS TutorialCreating an Editable Textarea That Supports Syntax-Highlighted Code

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> <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></pre> <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> <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 <pre class="brush:php;toolbar:false">&lt;code&gt; elements to be sized and spaced consistently.&lt;/code&gt;</pre></textarea></p> <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> <p>Then we want to position them right on top of each other:</p> <pre rel="CSS" data-line="">#editing, #highlighting { position: absolute; top: 0; left: 0; }</pre> <p>From there, z-index allows the textarea to stack in front the highlighted result:</p> <pre rel="CSS" data-line="">/* Move the textarea in front of the result */ #editing { z-index: 1; } #highlighting { z-index: 0; }</pre> <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> <pre rel="CSS" data-line="">/* Make textarea almost completely transparent */ #editing { color: transparent; background: transparent; caret-color: white; /* Or choose your favorite color */ }</pre> <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> <pre rel="HTML" data-line="">&lt;textarea spellcheck=&quot;false&quot; ...&gt;&lt;/textarea&gt;</pre> <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> <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> <p>Then, to make sure that the result scrolls <em>with</em> the textarea, we’ll update the HTML and JavaScript like this:</p> <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> <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> <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> <pre rel="CSS" data-line="">/* No resize on textarea */ #editing { resize: none; }</pre> <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 <pre class="brush:php;toolbar:false">&lt;code&gt; block ignores an empty final line for aesthetic reasons.&lt;/code&gt;</pre></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> <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> <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> <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> <pre rel="CSS" data-line="4">#editing, #highlighting, #highlighting * { /* Also add text styles to highlighing tokens */ /* etc. */ tab-size: 2; }</pre> <h3 id="The-final-result">The final result</h3> <p>Not too crazy, right? All we have are <textarea>, <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></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
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
@keyframes vs CSS Transitions: What is the difference?@keyframes vs CSS Transitions: What is the difference?May 14, 2025 am 12:01 AM

@keyframesandCSSTransitionsdifferincomplexity:@keyframesallowsfordetailedanimationsequences,whileCSSTransitionshandlesimplestatechanges.UseCSSTransitionsforhovereffectslikebuttoncolorchanges,and@keyframesforintricateanimationslikerotatingspinners.

Using Pages CMS for Static Site Content ManagementUsing Pages CMS for Static Site Content ManagementMay 13, 2025 am 09:24 AM

I know, I know: there are a ton of content management system options available, and while I've tested several, none have really been the one, y'know? Weird pricing models, difficult customization, some even end up becoming a whole &

The Ultimate Guide to Linking CSS Files in HTMLThe Ultimate Guide to Linking CSS Files in HTMLMay 13, 2025 am 12:02 AM

Linking CSS files to HTML can be achieved by using elements in part of HTML. 1) Use tags to link local CSS files. 2) Multiple CSS files can be implemented by adding multiple tags. 3) External CSS files use absolute URL links, such as. 4) Ensure the correct use of file paths and CSS file loading order, and optimize performance can use CSS preprocessor to merge files.

CSS Flexbox vs Grid: a comprehensive reviewCSS Flexbox vs Grid: a comprehensive reviewMay 12, 2025 am 12:01 AM

Choosing Flexbox or Grid depends on the layout requirements: 1) Flexbox is suitable for one-dimensional layouts, such as navigation bar; 2) Grid is suitable for two-dimensional layouts, such as magazine layouts. The two can be used in the project to improve the layout effect.

How to Include CSS Files: Methods and Best PracticesHow to Include CSS Files: Methods and Best PracticesMay 11, 2025 am 12:02 AM

The best way to include CSS files is to use tags to introduce external CSS files in the HTML part. 1. Use tags to introduce external CSS files, such as. 2. For small adjustments, inline CSS can be used, but should be used with caution. 3. Large projects can use CSS preprocessors such as Sass or Less to import other CSS files through @import. 4. For performance, CSS files should be merged and CDN should be used, and compressed using tools such as CSSNano.

Flexbox vs Grid: should I learn them both?Flexbox vs Grid: should I learn them both?May 10, 2025 am 12:01 AM

Yes,youshouldlearnbothFlexboxandGrid.1)Flexboxisidealforone-dimensional,flexiblelayoutslikenavigationmenus.2)Gridexcelsintwo-dimensional,complexdesignssuchasmagazinelayouts.3)Combiningbothenhanceslayoutflexibilityandresponsiveness,allowingforstructur

Orbital Mechanics (or How I Optimized a CSS Keyframes Animation)Orbital Mechanics (or How I Optimized a CSS Keyframes Animation)May 09, 2025 am 09:57 AM

What does it look like to refactor your own code? John Rhea picks apart an old CSS animation he wrote and walks through the thought process of optimizing it.

CSS Animations: Is it hard to create them?CSS Animations: Is it hard to create them?May 09, 2025 am 12:03 AM

CSSanimationsarenotinherentlyhardbutrequirepracticeandunderstandingofCSSpropertiesandtimingfunctions.1)Startwithsimpleanimationslikescalingabuttononhoverusingkeyframes.2)Useeasingfunctionslikecubic-bezierfornaturaleffects,suchasabounceanimation.3)For

See all articles

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

Hot Tools

MantisBT

MantisBT

Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

SecLists

SecLists

SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

EditPlus Chinese cracked version

EditPlus Chinese cracked version

Small size, syntax highlighting, does not support code prompt function

Atom editor mac version download

Atom editor mac version download

The most popular open source editor