JSFiddle - React, Tailwind, and code Playground

HTML

<div id="p" contenteditable="true" class='input'>I like this thing</div>

CSS

div.input{ margin:10px; height:1.5em; padding:10px; border:3px solid #CCC; outline:none; position:relative; }

textarea{ color:transparent; position:absolute; top:0; left:0; bottom:0; right:0; border:0; padding:10px; outline:none; }

.exceeds{ color:#CCC; }

small{ padding:10px; font-size:12px; color:blue; }

JavaScript

$('div').on('focus', function(){
    var that = this;
    $(that).data('lastValue', $(that).text());

    // USER WRITES SOMETHING
    $(document)
        .on('keypress', function(e){
            setTimeout(function(){
               validateInput(that, e.which);
            },0);
        }).on('keydown', function(e){
            //console.log(e.which);
            if( e.which == 8 ){
                validateInput(that, e.which);
            }
        }).on('paste', function(e){
            setTimeout(function(){
               validateInput(that, e.which);
            },0);
        });
});

function validateInput(el, key){
    var s = $(el).text();
    var normalized = $.trim(s.replace(/\s+/g, ' '));
    var numOfCharsRemoved = s.length - normalized.length;
    
    var caretPos = selection.save(el);
    caretPos.start -= numOfCharsRemoved;
    caretPos.end -= numOfCharsRemoved;
    
    // if no change
    if( normalized == $(el).data('lastValue') )
       return false;
    
    var newValue = wordsLimiter(s);
    // TEXT IS PARSED AND HTML IS CHANGED ACCORDINGLY
    el.innerHTML = newValue;
    $(el).data('lastValue', $(el).text());
    
    // NOW NEED TO RESTORE CARET POSITION (REMEMBER - HTML HAS CHANGED)
    selection.restore(el, caretPos);
   
}

// stop listening to keypresses
$('div').on('blur', function(){
    $(document).off('keyup keydown keypress paste');
});

// validate words and returned parsed result
function wordsLimiter(s){
    s = $.trim(s.replace(/\s+/g, ' '));
    var maxWords = 5,
        splitted = s.split(' '),
        output = splitted.slice(0,maxWords).join(' '),
        exceededWordsArr = splitted.slice(maxWords);

    if( exceededWordsArr.length ){
        output += '<span class="exceeds"> ' + exceededWordsArr.join(" ") + '</span>';
    }

    return output;
}

//-------- SELECTION METHODS --------//
var selection = {
    save : function(containerEl) {
        var charIndex = 0, start = 0, end = 0, foundStart = false, stop = {},
    ...