Split string into words and rejoin with additional data

You need to enclose all your alternatives within a non-capturing group, (?:...|...). Besides, to further counter eventual issues, I suggest replacing word boundaries with their lookaround unambiguous equivalents, (?<!\w)...(?!\w).

Here is a working C# snippet:

var text = "there are big widgets in this phrase blue widgets too";
var words = "big blue widgets";
var pattern = $@"(\s*(?<!\w)(?:{string.Join("|", words.Split(' ').Select(Regex.Escape))})(?!\w)\s*)";
var result = string.Concat(Regex.Split(text, pattern, RegexOptions.IgnoreCase).Select((str, index) =>
            index % 2 == 0 && !string.IsNullOrWhiteSpace(str) ? $"<b>{str}</b>" : str));
 Console.WriteLine(result);

NOTES

  • words.Split(' ').Select(Regex.Escape) - splits the words text with spaces and regex-escapes each item
  • string.Join("|",...) re-builds the string inserting | between the items
  • (?<!\w) negative lookbehind matches a location that is not immediately preceded with a word char, and (?!\w) negative lookahead matches a location that is not immediately followed with a word char.

I suggest implementing FSM (Finite State Machine) with 2 states (in and out selection) and Regex.Replace (we can keep the word as it is - word or replace it with <b>word, word<\b> or <b>word<\b>)

private static string MyModify(string text, string wordsToExclude) {
  HashSet<string> exclude = new HashSet<string>(
    wordsToExclude.Split(' '), StringComparer.OrdinalIgnoreCase);

  bool inSelection = false;

  string result = Regex.Replace(text, @"[\w']+", match => {
      var next = match.NextMatch();

      if (inSelection) {
        if (next.Success && exclude.Contains(next.Value)) {
          inSelection = false;

          return match.Value + "</b>";
        }
        else
          return match.Value;
      }
      else {
        if (exclude.Contains(match.Value))
          return match.Value;
        else if (next.Success && exclude.Contains(next.Value))
          return "<b>" + match.Value + "</b>";
        else {
          inSelection = true;
          return "<b>" + match.Value;
        }
      }
    });

  if (inSelection)
    result += "</b>";

  return result;
}

Demo:

string wordsToExclude = "big widgets blue if";

string[] tests = new string[] {
  "widgets for big blue",
  "big widgets are great but better if blue",
  "blue",
  "great but expensive",
  "big and small, blue and green",
};

string report = string.Join(Environment.NewLine, tests
  .Select(test => $"{test,-40} -> {MyModify(test, wordsToExclude)}"));

Console.Write(report);

Outcome:

widgets for big blue                     -> widgets <b>for</b> big blue
big widgets are great but better if blue -> big widgets <b>are great but better</b> if blue
blue                                     -> blue
great but expensive                      -> <b>great but expensive</b>
big and small, blue and green            -> big <b>and small</b>, blue <b>and green</b>