Dynamic line clamp in React

Picture

Since there is currently no way to set the value of the -webkit-line-clamp attribute to auto, I've created a component to dynamically clamp the number of lines displayed for a given text depending on the height of its container.

Let's get to it!

Steps

  1. Assuming you have already created a React project, create a file called DynamicLineClamp.tsx. I usually put it in the shared components folder of my projects.

  2. Write the component code:

    import React, { useEffect, useRef, useState } from 'react';
     
    type Props = {
      text: string;
      className?: string;
    };
     
    const DynamicLineClamp: React.FC<Props> = ({ text, className }) => {
      // Store references to the container and paragraph DOM elements, respectively.
      const containerRef = useRef<HTMLDivElement>(null);
      const paragraphRef = useRef<HTMLParagraphElement>(null);
     
      // Keep track of the maximum number of lines to display. It is initialized with a default value of 3.
      const [maxLines, setMaxLines] = useState(3);
     
      // Recalculate the maximum number of lines (`maxLines`) whenever the `text` prop changes. 
      useEffect(() => {
        if (containerRef.current && paragraphRef.current) {
          // Calculate the available height in the container and divide it by the line height 
          // of the paragraph to determine the number of lines that can be displayed.
          const lineHeight = parseFloat(getComputedStyle(paragraphRef.current).lineHeight);
          const availableHeight = containerRef.current.clientHeight;
          const calculatedMaxLines = Math.floor(availableHeight / lineHeight);
     
          // The result is then set to `maxLines`.
          setMaxLines(calculatedMaxLines);
        }
      }, [text]);
     
      // Inline CSS styles used to implement line clamping.
      const textStyles: React.CSSProperties = {
        display: "-webkit-box",
        WebkitBoxOrient: 'vertical',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        WebkitLineClamp: maxLines,
      };
     
      // Render the component. Notice the `flex-1` Tailwind utility 
      // class to take up the available space within a flex container.
      return (
        <div ref={containerRef} className="flex-1 overflow-hidden">
          <p ref={paragraphRef} style={textStyles} className={className}>
            {text}
          </p>
        </div>
      );
    }
     
    export default DynamicLineClamp;

Example of use

Let's import DynamicLineClamp and use it in another React component. Notice that I'm employing TailwindCSS utility classes.

import DynamicLineClamp from "@/components/shared/DynamicLineClamp";
 
const SimpleComponent = () => {
  return (
    <div className="flex flex-col h-40">
      <h1 className="text-xl">Fixed text</h1>
      <DynamicLineClamp 
        text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
        className="text-md sm:text-lg"
      />
    </div>
  );
};
 
export default SimpleComponent;

This is the result of using SimpleComponent:

Sample

Note that you must set a height for the parent container. Otherwise, the DynamicLineClamp will have the same effect as a p element taking up all the space available in the parent to display the full text.