James Newton
James Newton

Reputation: 7104

How to insert a zero-width line between two characters in a contenteditable HTML element

I am using React to create a web app to help people to spell words correctly in a foreign language. I want to provide feedback on where a word is misspelled. In particular, when the user has missed out a letter, I want show a mark in red to indicate where the missing letter should go.

Ideally, this would be a tall single-pixel line or a zero-width caret symbol, placed between the two adjacent letters. For example:

miss|ng
miss‸ng

Note that, in my representations above, the line and the caret actually have width, but this is exactly what I want to avoid. If the user typed...

missng

... then I want the letters to appear with exactly that spacing, and to place my red "you missed a letter" character between the "s" and the "n", without shifting the "n" to the right at all.

I have tried using a ZERO WIDTH SPACE (U+200B) followed by a COMBINING CIRCUMFLEX ACCENT BELOW (U+032D), but Chrome (for one) decides to show only the zero-width space, and fails to display the accent below. If I use the accent without the zero-width space, I have two problems:

  1. The accent appears below the preceding letter, not in the space
  2. The accent takes on the colour of the preceding letter (because they are combined)

My current workaround is to use the common pipe | character in a span with zero width, but this requires a positional tweak which depends on the font that is used to display the character:

<span
  style="
    position: relative;
    left: -0.1em;
    display: inline-block;
    width: 0px;
    color: red;
  "
>|</span>

Here's a jsfiddle which illustrates the shortcomings of my workaround. Is there a simple solution, or am I going to have to do some complex calculations to determine the positional adjustment to apply, depending on the user's font preferences?

div {
  position: relative;  
}
}div.serif {
  font-family: serif;
}
div.monospace {
  font-family: monospace;
}

p {
  top: 0;
  left: 0;
  margin: 0;
  font-size: 33vh;
  color: gray;
}
p.with-mark {
  position: absolute;
  top: 0.07em;
  color: blue;
}
span {
  position: relative;
  left: -0.1em;
  display: inline-block;
  width: 0px;
  color: red;
}
<div class="serif">
  <p class="without">missng</p>
  <p class="with-mark">miss<span>|</span>ng</p>
</div>
<div class="monospace">
  <p class="without">missng</p>
  <p class="with-mark">miss<span>|</span>ng</p>
</div>

Upvotes: 1

Views: 682

Answers (1)

Temani Afif
Temani Afif

Reputation: 273530

I would keep the span empty and add a box-shadow to create the line:

div {
  position: relative;  
}
}div.serif {
  font-family: serif;
}
div.monospace {
  font-family: monospace;
}

p {
  top: 0;
  left: 0;
  margin: 0;
  font-size: 33vh;
  color: gray;
}
p.with-mark {
  position: absolute;
  top: 0.03em;
  color: blue;
}
span {
  display:inline-block;
  height:0.7em;
  box-shadow:0px 0 0 2px red;
  position:relative;
}
span::after {
  content:"^";
  position:absolute;
  top:100%;
  left:50%;
  transform:translate(-50%);
  color:red;
  font-size:0.3em;
}
<div class="serif">
  <p class="without">missng</p>
  <p class="with-mark">miss<span></span>ng</p>
</div>
<div class="monospace">
  <p class="without">missng</p>
  <p class="with-mark">miss<span></span>ng</p>
</div>

Upvotes: 1

Related Questions