Siteplane UI

Truncate

Cut text after one line or a number of lines with an ellipsis, and show the full text in a tooltip.

Markdown

Quarterly brand refresh for the Northlight Studio website and its landing pages

Updated 2 hours ago

12 tasks, 4 of them done. The review is on Friday.

"use client";

import { FileTextIcon } from "lucide-react";
import { Button } from "@/siteplane/components/button";
import {
  Card,
  CardAction,
  CardDescription,
  CardHeader,
  CardMedia,
  CardPanel,
  CardTitle,
} from "@/siteplane/components/card";
import { Truncate } from "@/siteplane/components/truncate";

export default function Example() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardMedia>
          <FileTextIcon />
        </CardMedia>
        <CardTitle render={<h3 />}>
          <Truncate>
            Quarterly brand refresh for the Northlight Studio website and its
            landing pages
          </Truncate>
        </CardTitle>
        <CardDescription>Updated 2 hours ago</CardDescription>
        <CardAction>
          <Button
            profile="inline"
            render={<a href="#brand-refresh" />}
            variant="link"
          >
            Open
          </Button>
        </CardAction>
      </CardHeader>
      <CardPanel>
        <p className="text-sm text-muted-foreground">
          12 tasks, 4 of them done. The review is on Friday.
        </p>
      </CardPanel>
    </Card>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/truncate

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/button @siteplane/card @siteplane/table

Usage

import { Truncate } from "@/siteplane/components/truncate";

<Truncate>Quarterly brand refresh for the Northlight Studio website</Truncate>;

One line

Truncate keeps its text on one line and ends it with an ellipsis where its box ends. The box is as wide as the text, up to the width of its container. While the text is cut, hovering it shows the full text in a Tooltip; text that fits shows none.

Put the Truncate inside the part it shortens, such as a CardTitle, a heading, a list row or a table cell. The part keeps its element and its text style, and the Truncate takes both. In a card header, as above, the title ends before the CardAction and starts after the CardMedia, and the header keeps its height. In a flex or grid row, a Truncate shrinks below the width of its text, so the icons and badges beside it keep their size. When the Truncate sits inside another element of the row, give that element min-w-0, as any text that ends in an ellipsis needs, or the row widens to fit the whole text.

Several lines

Set lines to wrap the text and end it after that many lines. A Truncate with lines={2} in CardDescription keeps the first two lines of a description. A long word without spaces, such as a URL, breaks inside a line instead of running past the edge.

Launch checklist

Collect the approved photography, the revised logo files and the new color tokens, then hand the marketing team a checklist for every landing page, the pricing page and the onboarding emails before the launch review on Friday.
"use client";

import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/siteplane/components/card";
import { Truncate } from "@/siteplane/components/truncate";

export default function Example() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle render={<h3 />}>Launch checklist</CardTitle>
        <CardDescription>
          <Truncate lines={2}>
            Collect the approved photography, the revised logo files and the new
            color tokens, then hand the marketing team a checklist for every
            landing page, the pricing page and the onboarding emails before the
            launch review on Friday.
          </Truncate>
        </CardDescription>
      </CardHeader>
    </Card>
  );
}

Width

A width class in className sets where the text ends, such as max-w-1/2 for half the width of its container, or max-w-48 for 192 px. The class wins over the kit's rules.

Quarterly brand refresh for the Northlight Studio website

The title ends at half the width of the card.
"use client";

import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/siteplane/components/card";
import { Truncate } from "@/siteplane/components/truncate";

export default function Example() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle render={<h3 />}>
          <Truncate className="max-w-1/2">
            Quarterly brand refresh for the Northlight Studio website
          </Truncate>
        </CardTitle>
        <CardDescription>
          The title ends at half the width of the card.
        </CardDescription>
      </CardHeader>
    </Card>
  );
}

Table cell

A table cell is as wide as its content, so cut text needs a width to end at. Give the Truncate a width class, such as max-w-56, and the column takes that width when its text is longer.

NameOwnerSize
northlight-brand-guidelines-2026-final-approved-by-marketing.pdfMara Ellis18.4 MB
logo.svgJonas Weber24 KB
landing-page-photography-selects-round-three-retouched.zipSofia Nguyen412 MB
"use client";

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/siteplane/components/table";
import { Truncate } from "@/siteplane/components/truncate";

const files = [
  {
    id: "brand-guide",
    name: "northlight-brand-guidelines-2026-final-approved-by-marketing.pdf",
    owner: "Mara Ellis",
    size: "18.4 MB",
  },
  { id: "logo", name: "logo.svg", owner: "Jonas Weber", size: "24 KB" },
  {
    id: "photos",
    name: "landing-page-photography-selects-round-three-retouched.zip",
    owner: "Sofia Nguyen",
    size: "412 MB",
  },
];

export default function Example() {
  return (
    <Table aria-label="Files">
      <TableHeader>
        <TableHead id="name" isRowHeader>
          Name
        </TableHead>
        <TableHead id="owner">Owner</TableHead>
        <TableHead id="size" align="end">
          Size
        </TableHead>
      </TableHeader>
      <TableBody>
        {files.map((file) => (
          <TableRow id={file.id} key={file.id} textValue={file.name}>
            <TableCell>
              <Truncate className="max-w-56">{file.name}</Truncate>
            </TableCell>
            <TableCell>{file.owner}</TableCell>
            <TableCell align="end">{file.size}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

Tooltip

The tooltip opens on hover and closes when the pointer leaves or the text is clicked. Under a TooltipProvider it takes the provider's delay. A Truncate that takes focus, such as a link, opens it on focus too.

The cut is visual: the element keeps the whole text, so assistive technology reads it once, and the tooltip, which repeats it, is hidden from assistive technology. The server renders no tooltip. The browser measures the text before the first paint and again whenever its box changes size, and the tooltip opens only while the text is cut.

Set tooltip={false} to turn it off. An object passes props to TooltipPopup, such as side, or children for content other than the text:

<Truncate tooltip={{ side: "bottom" }}>{fileName}</Truncate>

Element

Truncate renders a span. Set render for another element, such as a link, which keeps its own focus and click behavior:

<Truncate render={<a href="/projects/brand-refresh" />}>
  Quarterly brand refresh for the Northlight Studio website
</Truncate>

Metric Card cuts its title with a Truncate when you set its titleLines.

API reference

Component properties
PropTypeDefaultDescription
Truncate.linesnumber1Lines the text keeps before it ends in an ellipsis. 1 keeps the text on one line and cuts it where its box ends; more lines wrap the text and cut it after the last of them.
Truncate.tooltipboolean | TruncateTooltipPropstrueShows the full text in a Tooltip while the text is cut. false turns it off; an object passes props to TooltipPopup, such as side or children.
Truncate.renderReactElement | render functionspanAnother element for the text, such as a heading or a link.
Truncate.classNamestringunsetAdditional classes. A width class, such as max-w-1/2, sets where the text ends.
Truncate.childrenReactNodeunsetThe text.

TruncateProps extends native span props and accepts a ref. TruncateTooltipProps is the props of TooltipPopup. The cut is a CSS rule: one line uses text-overflow, several lines the line clamp, whose count comes from the style attribute. The tooltip is the installed Tooltip, and one shared ResizeObserver tells it whether the text is cut.

Read the installed source.

Usage notes

Use a Truncate for titles, names and file names that may be longer than their space, and for descriptions that should keep their first lines. Let text that people must read in full wrap, such as an error, an instruction or a price. Menu rows, Badge and Tag labels and Button labels end in an ellipsis themselves, so they need no Truncate.

The tooltip is a convenience for pointer users. Where the full text matters on a touch screen, link to a page or a dialog that shows it, or let the text wrap.

A Content Security Policy that blocks style attributes keeps two lines for any lines above one; the installation guide allows style attributes.