Tailwind has a plugin for line clamping @tailwindcss/line-clamp
that uses pure CSS to truncate text to the specified number of lines. What if you want to show an indicator to read the rest of the truncated text?
We can create a react component that dynamically truncates text and shows a toggle for expanding or hiding text.
import React, { FunctionComponent, useState, useCallback } from 'react';
type TextTruncateProps = {
text: string;
};
/* Note: The number of lines to truncate to needs to be static
otherwise the css classes won't be generated by tailwind. If you need
a different number of lines, create a new component */
export const TextTruncateThree: FunctionComponent<TextTruncateProps> = ({
text,
}) => {
const [shouldTruncate, setShouldTruncate] = useState<boolean>(false);
const [readMore, setReadMore] = useState<boolean>(false);
// Measure the element to calculate the number of lines and
// determine whether to truncate
const measuredRef = useCallback(
(node: any) => {
// Before the component mounts the node ref will be null
if (node?.parentElement) {
// Calculate the number of lines based on height
const elHeight = node.offsetHeight;
const styles = window.getComputedStyle(node);
const lineHeight = styles
.getPropertyValue('line-height')
.replace('px', '');
const elLineCount = elHeight / parseInt(lineHeight, 10);
setShouldTruncate(elLineCount > 3);
}
},
[text]
);
const shouldClamp = shouldTruncate && !readMore;
// Our toggle for expanding or hiding truncated text
let toggle;
if (readMore) {
toggle = (
<span onClick={() => setReadMore(false)}>
Show less
</span>
)
} else {
toggle = (
<span onClick={() => setReadMore(true)}>
Read more
</span>
);
}
return (
<div>
<p
ref={measuredRef}
className={`${shouldClamp ? 'line-clamp-3' : 'line-clamp-none'}`}
>
{text}
</p>
{shouldTruncate && toggle}
</div>
);
};
See also:
- Truncating text is complicated but this method keeps the height of the element consistent