JSFiddle - React, Tailwind, and code Playground

HTML

<input type="text" />

<p>
  <b>Lorem ipsum dolor sit āmet</b>, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, nò
  sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
  et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
  At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie çonsequat, vel illum dolore eu
  feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit prāesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet,
</p>

JavaScript

/*
 * Takes in an array of consecutive TextNodes and returns a document fragment with `word` highlighted
 */
function highlight_text_nodes($nodes, word) {
  if (!$nodes.length) {
    return;
  }

  var text = '';

  // Concatenate the consecutive nodes to get the actual text
  for (var i = 0; i < $nodes.length; i++) {
    text += $nodes[i].textContent;
  }

  var $fragment = document.createDocumentFragment();

  while (true) {
    // Tweak this if you want to change the highlighting behavior
    var index = text.toLowerCase().indexOf(word.toLowerCase());

    if (index === -1) {
      break;
    }

    // Split the text into [before, match, after]
    var before = text.slice(0, index);
    var match = text.slice(index, index + word.length);
    text = text.slice(index + word.length);

    // Create the <mark>
    var $mark = document.createElement('mark');
    $mark.className = 'found';
    $mark.appendChild(document.createTextNode(match));

    // Append it to the fragment
    $fragment.appendChild(document.createTextNode(before));
    $fragment.appendChild($mark);
  }

  // If we have leftover text, just append it to the end
  if (text.length) {
    $fragment.appendChild(document.createTextNode(text));
  }

  // Replace the nodes with the fragment
  $nodes[0].parentNode.insertBefore($fragment, $nodes[0]);

  for (var i = 0; i < $nodes.length; i++) {
    var $node = $nodes[$nodes.length - i - 1];
    $node.parentNode.removeChild($node);
  }
}


/*
 * Highlights all instances of `word` in `$node` and its children
 */
function highlight($node, word) {
  var $children = $node.childNodes;
  var $current_run = [];

  for (var i = 0; i < $children.length; i++) {
    var $child = $children[i];

    if ($child.nodeType === Node.TEXT_NODE) {
      // Keep track of consecutive text nodes
      $current_run.push($child);
    } else {
      // If we hit a regular element, highlight what we have and start over
      highlight_text_nodes($current_run, word);
     ...