76

I have a <textarea> element as in the code below. How can I display line numbers along the left margin of it?

<TEXTAREA name="program" id="program" rows="15" cols="65" ></TEXTAREA>
1
  • Summary of answers as of July 2024: There is no simple solution that is user-friendly and has good accessibility. Use CodeMirror or a similarly robust solution. Commented Jul 18, 2024 at 19:20

11 Answers 11

29

TLDR: Use CodeMirror

Someone else here recommended CodeMirror, and I can't hardly recommend it enough! But this answer didn't really provide any technical details.

Other solutions: Everything else I tried here has problems with line numbers not matching up with lines. I believe this is because I have monitor DPI (dots per inch) at 120%, and these solutions didn't take this into account.

So, how do you use CodeMirror??? Easy! Just look at the 21,000 words of the documentation! I hope to explain 99% of your questions on it in less than page or two.

Demo it Up!

100% working demo, and it's working perfectly in the StackOverflow sandbox:

var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
    lineNumbers: true,
    mode: 'text/x-perl',
    theme: 'abbott',
});
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/perl/perl.min.js"></script>

<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css"></link>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/abbott.min.css"></link>

<textarea id="code" name="code">
if($cool_variable) {
    doTheCoolThing();     # it's PRETTY cool, imho
}</textarea>

Features!

  • Insert/overwrite mode.
  • Multiple row indenting simultaneously.
  • Tons of themes.

TLDR: How to Use CodeMirror, in a Page or Less

Step 1 - Load Basic Core Libraries

Add this to your <head> block...

<script language="javascript" type="text/javascript" src="/static/js/codemirror-5.62.0/lib/codemirror.js"></script>
<link rel="stylesheet" type="text/css" href="/static/js/codemirror-5.62.0/lib/codemirror.css"></link>

And, if you like to have extra-bracket color matching, also load this:

<script language="javascript" type="text/javascript" src="/codemirror-5.62.0/addon/edit/matchbrackets.js"></script>

Step 2 - Setup Syntax Highlighting

Check the /codemirror-5.62.0/mode/ dir to see what language matches the language you'll be coding in. There is extensive support in this area.

Add this to your <head> block...

<script language="javascript" type="text/javascript" src="/static/js/codemirror-5.62.0/mode/perl/perl.js"></script>

Step 3 - Initialize and Display CodeMirror

Have some textarea to use....

<textarea id="code" name="code"></textarea>

Initialize and set your codemirror in the JS. You need to use the Mimetype to indicate the mode you want to use, in this case, I'm indicating the Perl Mimetype...

var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
    lineNumbers: true,
    mode: 'text/x-perl',
    matchBrackets: true,
});

Step 4 - Pick a Theme

Choose some theme you like, 'liquibyte', 'cobalt' and 'abbott' are both pretty decent dark-mode-ish themes. Run this after defining editor...

editor.setOption('theme', 'cobalt');

And that's it!

Sign up to request clarification or add additional context in comments.

2 Comments

This must be one of the best documented answers on SO. And I can recommend the simplemode plugin if the OP wants to display something other than the supported languages.
CodeMirror 6 doesn't have pre-built browser bundles unfortunately
22

This is a very simple, but effective trick. It inserts an image with the line numbers already added.

The only catch is you may need to create your own image to match your UI design.

textarea.numbered {
    background: url(http://i.imgur.com/2cOaJ.png);
    background-attachment: local;
    background-repeat: no-repeat;
    padding-left: 35px;
    padding-top: 10px;
    border-color:#ccc;
}
<textarea cols="50" rows="10" class="numbered"></textarea>

Credit goes to: Aakash Chakravarthy

8 Comments

This solution doesn't scroll properly. Also, if a line of text is wider than the textarea, it will wrap. That means the text will continue on the next line but the line numbers won't keep up.
This scrolls acceptably in Chrome and Safari, but not at all in Firefox and I cant figure out why …
It also doesn't scale properly when zooming in/out
If people are wondering, this image goes up to the number 1500
For the sake of accessibility, don't use this. It doesn't adhere to the kerning, padding, sizing, font, and antialiasing of the browser (inherited from the OS). It also isn't useful for screen readers except those with OCR.
|
20

No one tried to do this using HTML5 Canvas object and by painting line numbers on it. So I've managed to put canvas and textarea, one next to the other, and painted numbers on canvas.

//
// desc: demonstrates textarea line numbers using canvas paint 
// auth: nikola bozovic <nigerija@gmail>
//
var TextAreaLineNumbersWithCanvas = function() {
  var div = document.getElementById('wrapper');
  var cssTable = 'padding:0px 0px 0px 0px!important; margin:0px 0px 0px 0px!important; font-size:1px;line-height:0px; width:auto;';
  var cssTd1 = 'border:1px #345 solid; border-right:0px; vertical-align:top; width:1px; background: #303030';
  var cssTd2 = 'border:1px #345 solid; border-left:0px; vertical-align:top;';
  var cssButton = 'width:120px; height:40px; border:1px solid #333 !important; border-bottom-color: #484!important; color:#ffe; background-color:#222;';
  var cssCanvas = 'border:0px; background-color:#1c1c20; margin-top:0px; padding-top:0px;';

  // LAYOUT (table 2 panels)
  var table = document.createElement('table');
  table.setAttribute('cellspacing', '0');
  table.setAttribute('cellpadding', '0');
  table.setAttribute('style', cssTable);
  var tr = document.createElement('tr');
  var td1 = document.createElement('td');
  td1.setAttribute('style', cssTd1);
  var td2 = document.createElement('td');
  td2.setAttribute('style', cssTd2);
  tr.appendChild(td1);
  tr.appendChild(td2);
  table.appendChild(tr);

  // TEXTAREA
  var ta = this.evalnode = document.getElementById('mytextarea');

  // TEXTAREA NUMBERS (Canvas)
  var canvas = document.createElement('canvas');
  canvas.width = 48; // must not set width & height in css !!!
  canvas.height = 500; // must not set width & height in css !!!
  canvas.setAttribute('style', cssCanvas);
  ta.canvasLines = canvas;
  td1.appendChild(canvas);
  td2.appendChild(ta);
  div.appendChild(table);

  // PAINT LINE NUMBERS
  ta.paintLineNumbers = function() {
    try {
      var canvas = this.canvasLines;
      if (canvas.height != this.clientHeight) canvas.height = this.clientHeight; // on resize
      var ctx = canvas.getContext("2d");
      ctx.fillStyle = "#303030";
      ctx.fillRect(0, 0, 42, this.scrollHeight + 1);
      ctx.fillStyle = "#808080";
      ctx.font = "11px monospace"; // NOTICE: must match TextArea font-size(11px) and lineheight(15) !!!
      var startIndex = Math.floor(this.scrollTop / 15, 0);
      var endIndex = startIndex + Math.ceil(this.clientHeight / 15, 0);
      for (var i = startIndex; i < endIndex; i++) {
        var ph = 10 - this.scrollTop + (i * 15);
        var text = '' + (1 + i); // line number
        ctx.fillText(text, 40 - (text.length * 6), ph);
      }
    } catch (e) {
      alert(e);
    }
  };
  ta.onscroll = function(ev) {
    this.paintLineNumbers();
  };
  ta.onmousedown = function(ev) {
    this.mouseisdown = true;
  }
  ta.onmouseup = function(ev) {
    this.mouseisdown = false;
    this.paintLineNumbers();
  };
  ta.onmousemove = function(ev) {
    if (this.mouseisdown) this.paintLineNumbers();
  };

  // make sure it's painted
  ta.paintLineNumbers();
  return ta;
};

var ta = TextAreaLineNumbersWithCanvas();
ta.value = TextAreaLineNumbersWithCanvas.toString();
#mytextarea {
  width: auto;
  height: 500px;
  font-size: 11px;
  font-family: monospace;
  line-height: 15px;
  font-weight: 500;
  margin: 0;
  padding: 0;
  resize: both;
  color: #ffa;
  border: 0;
  background-color: #222;
  white-space: pre;
  overflow: auto;
}


/* supported only in opera */
#mytextarea {
  scrollbar-arrow-color: #ee8;
  scrollbar-base-color: #444;
  scrollbar-track-color: #666;
  scrollbar-face-color: #444;
  /* outer light */
  scrollbar-3dlight-color: #444;
  /* inner light */
  scrollbar-highlight-color: #666;
  /* outer dark */
  scrollbar-darkshadow-color: #444;
  /* inner dark */
  scrollbar-shadow-color: #222;
}


/* chrome scrollbars */

textarea::-webkit-scrollbar {
  width: 16px;
  background-color: #444;
  cursor: pointer;
}

textarea::-webkit-scrollbar-track {
  background-color: #333;
  cursor: pointer;
}

textarea::-webkit-scrollbar-corner {
  background-color: #484;
  -webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.3);
}

textarea::-webkit-scrollbar-thumb {
  background-color: #444;
  -webkit-box-shadow: inset 0 0 6px rgba(255, 255, 255, 0.3);
  cursor: pointer;
}
<div id="wrapper">
  <textarea id="mytextarea" cols="80" rows="10"></textarea>
</div>

There is a limitation in that we can't handle word-wrap easily in the Paint() function without iterating the entire textarea content and drawing to a hidden object for measurements of each line height, which would yield very complex code.

6 Comments

True, didn't test it in FireFox. But I do not see any reason why it shouldn't work in FireFox. So far it is working in IE11, Opera12, Edge, Chrome and variants, WebKit and variant, and some other browsers that support W3C Canvas object. Any explanation what exactly isn't working? thanks for info.
in Firefox, I unable to add the new line, whatever i enter it is in single line (although i hit "return" button).
Top of my head think Firefox is not properly handing line-break characters, try css "white-space: pre;" or some other combination. I've noticed in past this was huge issue with browsers, it seams some still suffer from the same.
It doesn't work well because after many lines the line numbers are not inline with the textarea lines. I tried to tweak it but gave up after several tries. I didn't want it at first but CodeMirror was the solution for me.
Not sure what you have tweaked, how many lines you have used, or what browser you were using. I've just tried one million lines in Chrome, line alignment works just fine. Noticed only chrome tends to slow down on one million lines, measured paintLineNumbers() function and it takes only 0~1 ms, guess textarea control is uber slow in Chrome.
|
8

Consider the use of a contenteditable ordered list <ol> instead of <textarea>

ol {
  font-family: monospace;
  white-space: pre; 
}

li::marker {
  font-size: 10px;
  color: grey;
}
<ol contenteditable><li>lorem ipsum      
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum,\ 
<li>lorem ipsum.
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
<li>lorem ipsum
<li>lorem      
<li>ipsum
<li>&gt;&gt; lorem ipsum
<li>lorem ipsum
</ol> 

However, ::marker styling seems limited (list-style-type). E.g. removing the period or vertical-align: super seems to needs other workarounds (back to li:before and counter).

Bonus: <li> also does not need the closing tag </li> (https://html.spec.whatwg.org/multipage/syntax.html#optional-tags), which saves typing.

Also as far as I understand, the <textarea> in codemirror just works in the background (Pseudo contenteditable: how does codemirror works?).

1 Comment

It's very clever, but unfortunately breaks very quickly.
4

Line Numbers

Code:

const textarea = document.querySelector("textarea");
      const numbers = document.querySelector(".numbers");
      textarea.addEventListener("keyup", (e) => {
        const num = e.target.value.split("\n").length;
        numbers.innerHTML = Array(num).fill("<span></span>").join("");
        
      });
      textarea.addEventListener("keydown", (event) => {
        if (event.key === "Tab") {
          const start = textarea.selectionStart;
          const end = textarea.selectionEnd;

          textarea.value =
            textarea.value.substring(0, start) +
            "\t" +
            textarea.value.substring(end);

          event.preventDefault();
        }
      });
   body {
        font-family: Consolas, "Courier New", Courier, monospace;
      }
      .editor {
        display: inline-flex;
        gap: 10px;
        font-family: Consolas, "Courier New", Courier, monospace;
        line-height: 21px;
        background-color: #282a3a;
        border-radius: 2px;
        padding: 20px 10px;
      }
      textarea {
        line-height: 21px;
        overflow-y: hidden;
        padding: 0;
        border: 0;
        background: #282a3a;
        color: #fff;
        min-width: 500px;
        outline: none;
        resize: none;
        font-family: Consolas, "Courier New", Courier, monospace;
      }
      .numbers {
        width: 20px;
        text-align: right;
      }

      .numbers span {
        counter-increment: linenumber;
      }

      .numbers span::before {
        content: counter(linenumber);
        display: block;
        color: #506882;
      }
<div class="editor">
      <div class="numbers">
        <span></span>
      </div>
      <textarea cols="30" rows="10"></textarea>
    </div>
From: https://www.webtips.dev/add-line-numbers-to-html-textarea

It actually works quite well.

The line numbers do not occur instantly but it workds quite fast.

2 Comments

If a line wraps the next line's number will be displayed incorrectly.
@InSync A work-around for this is using white-space: nowrap.
2

I've created a line numbering system that works good on textarea with line wrapped. I haven't customized it for single line overflowing code, but it's good if you want wrapped lines.

CodePen Demo

'use scrict';
var linenumbers = document.getElementById('line-numbers');
var editor = document.getElementById('codeblock');
    
function getWidth(elem) {
    return elem.scrollWidth - (parseFloat(window.getComputedStyle(elem, null).getPropertyValue('padding-left')) + parseFloat(window.getComputedStyle(elem, null).getPropertyValue('padding-right')))
}

function getFontSize(elem) {
    return parseFloat(window.getComputedStyle(elem, null).getPropertyValue('font-size'));
}

function cutLines(lines) {
    return lines.split(/\r?\n/);
}
    
    
function getLineHeight(elem) {
    var computedStyle = window.getComputedStyle(elem);
    var lineHeight = computedStyle.getPropertyValue('line-height');
    var lineheight;
    
    if (lineHeight === 'normal') {
        var fontSize = computedStyle.getPropertyValue('font-size');
        lineheight = parseFloat(fontSize) * 1.2;
    } else {
        lineheight = parseFloat(lineHeight);
    }
    
    return lineheight;
}

function getTotalLineSize(size, line, options) {
    if (typeof options === 'object') options = {};
    var p = document.createElement('span');
    p.style.setProperty('white-space', 'pre');
    p.style.display = 'inline-block';
    if (typeof options.fontSize !== 'undefined') p.style.fontSize = options.fontSize;
    p.innerHTML = line;
    document.body.appendChild(p);
    var result = (p.scrollWidth / size);
    p.remove();
    return Math.ceil(result);
}

function getLineNumber() {
    var textLines = editor.value.substr(0, editor.selectionStart).split("\n");
    var currentLineNumber = textLines.length;
    var currentColumnIndex = textLines[textLines.length-1].length;
    return currentLineNumber;
}
    
function init() {
    var totallines = cutLines(editor.value), linesize;
    linenumbers.innerHTML = '';
    for (var i = 1; i <= totallines.length; i++) {
        var num = document.createElement('p');
        num.innerHTML = i;
        linenumbers.appendChild(num);
            
        linesize = getTotalLineSize(getWidth(editor), totallines[(i - 1)], {'fontSize' : getFontSize(editor)});
        if (linesize > 1) {
            num.style.height = (linesize * getLineHeight(editor)) + 'px';
        }
    }
        
    linesize = getTotalLineSize(getWidth(editor), totallines[(getLineNumber() - 1)], {'fontSize' : getFontSize(editor)});
    if (linesize > 1) {
        linenumbers.childNodes[(getLineNumber() - 1)].style.height = (linesize * getLineHeight(editor)) + 'px';
    }
        
    editor.style.height = editor.scrollHeight;
    linenumbers.style.height = editor.scrollHeight;
}



editor.addEventListener('keyup', init);
editor.addEventListener('input', init);
editor.addEventListener('click', init);
editor.addEventListener('paste', init);
editor.addEventListener('load', init);
editor.addEventListener('mouseover', init);
#source-code {
    width: 100%;
    height: 450px;
    background-color: #2F2F2F;
    display: flex;
    justify-content: space-between;
    overflow-y: scroll;
    border-radius: 10px;
}

#source-code * {
    box-sizing: border-box;
}

#codeblock {
    white-space: pre-wrap;
    width: calc(100% - 30px);
    float: right;
    height: auto;
    font-family: arial;
    color: #fff;
    background: transparent;
    padding: 15px;
    line-height: 30px;
    overflow: hidden;
    min-height: 100%;
    border: none;
}

#line-numbers {
    min-width: 30px;
    height: 100%;
    padding: 15px 5px;
    font-size: 14px;
    vertical-align: middle;
    text-align: right;
    margin: 0;
    color: #fff;
    background: black;
}

#line-numbers p {
    display: block;
    height: 30px;
    line-height: 30px;
    margin: 0;
}

#codeblock:focus{
    outline: none;
}
<div id="source-code">
    <div id="line-numbers"><p>1</p></div>
    <textarea id="codeblock"></textarea>
</div>

2 Comments

Run code snippet, input 10-15 short (3-5 symbols) lines, then input a long line that gets wrapped - now all the line numbers are uneven with content lines. Same in current Firefox and Chrome. imgur.com/a/lzNazBe
This breaks pretty hard when you paste into it.
1

First step: create two textarea

Create two textarea, a textarea to stock the numbers and a textarea to stock the code.

Two step: Add the CSS

three step: Add JS

Here, the JS part of the code, to set the number textarea scroll when the code textarea scroll, and the number of line in the code textarea equal the number of line in the number textarea.

let textareasHere = Array.from(document.querySelectorAll(".textareaHere > textarea"));
for(let i=0;i<textareasHere.length;i++){
  console.log(i);
  if(i!=0 && i%2==1){
    textareasHere[i].addEventListener("scroll", function(e){
      textareasHere[i-1].scrollTop = textareasHere[i].scrollTop;
      textareasHere[i-1].scrollLeft = textareasHere[i].scrollLeft;
    });
    textareasHere[i].addEventListener("input", function(e){
      textareasHere[i-1].textContent = "";
      const numberOfLinesHere = Math.max(textareasHere[i].value.split("\n").length, 1);
      for(let h = 0; h < numberOfLinesHere; h++){
        textareasHere[i-1].textContent += (h+1).toString()+"\n";
      }
      textareasHere[i-1].setAttribute("cols", numberOfLinesHere.toString().length.toString());
    });
    const numberOfLinesHereZ = Math.max(textareasHere[i].value.split("\n").length, 1);
    for(let h = 0; h < numberOfLinesHereZ; h++){
      textareasHere[i-1].textContent += (h+1).toString()+"\n";
    }
    textareasHere[i-1].setAttribute("cols", numberOfLinesHereZ.toString().length.toString());
  }
}
body{
  background: #111;
}
.textareaHere{
  display: flex;
  background: #1d1e22;
  flex-grow: 1;
  height: 70vh;
  border-left: 1px solid #353535;
}
.textareaHere > textarea{
  border: none;
  margin: 0;
  background: #1d1e22;
  resize: none;
  outline: none;
  font-size: 13px;
  margin: 5px;
}
.codeTextarea{
  flex-grow: 1;
  color: #eee;
  text-wrap: nowrap
}
.numbersHereNow{
  overflow: hidden;
  color: #414141;
}
<div class="textareaHere">
  <textarea class="numbersHereNow" cols="5" readonly></textarea>
  <textarea class="codeTextarea"></textarea>
</div>

Comments

0

It's quite simple really, just take the value of the <textarea> tag and matchAll("\n");. Then you can Array.from(str);, and that leaves us with usable results. Example: Arr.length+1; is the only thing you need to get the amount of lines. My result:

var txt = txt_area.value;


//Retrieves Line number

var ln = (Array.from(txt.matchAll("\n")).length)+1;

Then based off that (and line height) you could put the line numbers in text tags off to the side a little, hope this helped!

1 Comment

Sure, getting the line count is quite easy. It's using that line count to actually generate line numbers in a stable manner that's hard, so this doesn't really answer the question unfortunately.
0

Many great answers above and in the end probably best to embed CodeMirror or Ace editor if using code as contents. Nevertheless here's one based on insights from above for a minimal 2x <textarea> solution.

let elms = {
  genList: document.getElementById('code'),
  genNumbers: document.getElementById('code-numbers'),
}

elms.genList.addEventListener('scroll', (e) => {
  elms.genNumbers.scrollTop = elms.genList.scrollTop
})

function genNumbers() {
  elms.genNumbers.value = ''

  var ln = (Array.from(elms.genList.value.matchAll('\n')).length) + 1
  for (let i = 1; i <= ln; i++) {
    elms.genNumbers.value += i + '\n'
  }
}

genNumbers()
#code-holder {
  display: flex;
  height: 200px;
  background: #666;
  border: 1px solid #666;
}

textarea {
  outline: none;
  background: #333;
  color: #00ff00;
  border: none;
  height: 99%;
  resize: none;
}

#code-numbers {
  width: 3em;
  text-align: right;
  pointer-events: none;
  overflow: hidden;
  padding-right: 3px;
  color:rgba(0, 255, 0, .7);
}

#code {
  width: 100%;
  padding-left: 3px;
  margin-left: 1px;
  white-space: nowrap;
}
<div id="code-holder">
  <textarea id="code-numbers"></textarea>
  <textarea id="code" oninput="genNumbers()" onchange="genNumbers()">
hello
world</textarea>
</div>

Comments

0

how to use ace editor?

try going in this link: ace.

this ace in html is easy! and check this code snippet:

var editor = ace.edit("editor", {
  theme: "ace/theme/tomorrow_night",
  mode: "ace/mode/javascript",
});
#editor {
  width: 100%;
  height: 100vh;
}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.13/ace.js"></script>
<div id="editor">function foo(what_is = "h"){
    let string = what_is;
    return 0;
  }</div>

easy, right!

features!

  1. line numbers

  2. syntax highlighting

  3. errors in number lines

choose an theme:

editor.setTheme("ace/theme/tomorrow_night")

choose an mode:

editor.session.setMode("ace/mode/html")

my favorite version is 1.4.13,

so the html code editor uses <script>.

1 Comment

... but that's a div that becomes a parent to something that's... not a texteditor (has one, that is used for the caret)
-2

var editor = CodeMirror.fromTextArea(document.getElementById('code'), {
    lineNumbers: true,
    mode: 'text/x-perl',
    theme: 'dracula',
    matchBracklets: true
});
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/mode/perl/perl.min.js"></script>

<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css"></link>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/dracula.min.css"></link>

<textarea id="code" name="code">
if(time_left = 0){
  return xxxx()
}</textarea>

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.