JSFiddle - React, Tailwind, and code Playground

HTML

<div contenteditable class="empty"></div>

<table>
    <caption>Current Selection Info</caption>
    <tr><th><th>Container<th>Offset</tr>
    <tr><th>Start<td><td></tr>
    <tr><th>End<td><td></tr>
</table>

<b>Instructions</b>:
<ol>
<li>Click in the white box. Note that the input caret is <i>before</i> the “type here” text.
<li>Press the ← and → keys. Note that you cannot move the input caret within the “type here” text.
<li>Type a few characters in the box.
<li>Select all of the text you typed (⌘-A or Ctrl-A).
<li>Press Backspace or Delete.
<li>Note that the input caret is now <i>after</i> the “type here” text and has changed color. You can now move the input caret within the “type here” text using the ← and → keys. Also note that attempting to access information about the selection range throws an exception.
</ol>

CSS

body {
    padding: 10px 20px;
    background: lightgray;
}
[contenteditable] {
    background: white;
}
[contenteditable].empty::before {
    content: 'type here';
    color: #ccc;
}
[contenteditable]:focus {
    outline: none;
}

table { margin: 20px 50px }
caption { margin-bottom: 10px }
th { min-width: 70px }
td { text-align: center }

JavaScript

document.querySelector('[contenteditable]').addEventListener('input', function () {
    this.className = this.textContent ? '' : 'empty';
});

var dd = document.getElementsByTagName('td');
function updateSelectionInfo() {
    var sel = window.getSelection();
    var r = sel.getRangeAt(0);
    try {
        dd[0].textContent = r.startContainer.tagName || (r.startContainer.nodeType === 3 ? 'TEXT' : '?');
    } catch (e) {
        dd[0].textContent = e;
    }
    try {
        dd[1].textContent = r.startOffset;
    } catch (e) {
        dd[1].textContent = e;
    }
    try {
        dd[2].textContent = r.endContainer.tagName || (r.endContainer.nodeType === 3 ? 'TEXT' : '?');
    } catch (e) {
        dd[2].textContent = e;
    }
    try {
        dd[3].textContent = r.endOffset;
    } catch (e) {
        dd[3].textContent = e;
    }
}
updateSelectionInfo();
setInterval(updateSelectionInfo, 200);