Search code examples
htmlcssreactjsfonts

How to ensure consistent text rendering between split and non-split text?


I am working on a React project where I need to animate individual letters in a string, so I am splitting the string into separate span elements for each letter. However, I'm facing an issue where the text looks different when split compared to when it is rendered normally as a continuous string.

Note: The text is split until character level, with each character wrapped with span.

Here is a simplified version of my code:

import { ReactNode } from "react";

export function splitWord(input: string): string[] {
  return input.match(/\S+|\s+/g) || [];
}

export function splitLetter(input: string): string[] {
  const characters: string[] = [];
  const regex = /[\s\S]/gu;
  let match;
  while ((match = regex.exec(input)) !== null) {
    characters.push(match[0]);
  }
  return characters;
}

export function splitStringWithSpan(input: string): ReactNode {
  return (
    <>
      {splitWord(input).map((wordOrSpace, wordIndex) => (
        <span
          className="inline-block [&>*]:overflow-y-clip [&>*]:inline-flex leading-tight"
          key={wordIndex}
        >
          {splitLetter(wordOrSpace).map((letter, letterIndex) => (
            <span key={letterIndex}>
              <span>{letter === " " ? "\u00A0" : letter}</span>
            </span>
          ))}
        </span>
      ))}
    </>
  );
}

const Help = () => {
  return (
    <div className="text-5xl font-semibold flex flex-col">
      <div>
        <div className="text-xs">Splitted</div>
        {splitStringWithSpan("Young")}
      </div>
      <div>
        <div className="text-xs">Not Splitted</div>Young
      </div>
    </div>
  );
};

export default Help;

In the code, I render the text "Young" in two ways:

  1. Split into individual letters using splitStringWithSpan.
  2. Rendered as a normal string.

Issue:

example The splitted text looks slightly wider from the non-split text. You can see there's slightly different width at the character "Y" in the example.

Question:

How can I make the splitted text look the same as the non-split text in terms of character width and spacing?

Attempts:

I tried changing the max width of each individual character to
max-width: 0.94em;
It works for some character like "W", but still can't handle all character's spacing.


Solution

  • There isn't any extra width in the splitted text.

    The lack of the gap between the Y and the o in the non-splitted text is a result of kerning pairs in the font you are using. If you set a different font (through font-mono or similar), the rendering will become consistent between both versions.

    The horizontal spacing is disrupted in the splitted version because you set [&>*]:inline-flex on the individual letter spans, which forces each to be an individual container and prevents the kerning pairing from taking place. You can see this by either removing [&>*]:inline-flex, or by splitting on every second letter (which causes the first span to contain Yo together), either of which allow the kerning to happen properly (removing the extra space).

    Possible fixes - which is correct will depend on the context you are using this in:

    • remove [&>*]:inline-flex - not sure why it's necessary here
    • use a different font without kerning pairs