# Truncate

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

Card title cut after one line, beside an icon and a link

[Complete TSX file](/examples/truncate/default.tsx)

```tsx
"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

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/truncate
```

With npm: `npx shadcn@4.18.0 add @siteplane/truncate`. [Registry JSON](/registry/truncate.json) lists source files and dependencies.

For this page’s examples, also install:

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

## Usage

```tsx
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](/docs/components/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.

Description cut after two lines

[Complete TSX file](/examples/truncate/lines.tsx)

```tsx
"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.

Card title cut at half the width of the card

[Complete TSX file](/examples/truncate/width.tsx)

```tsx
"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.

File names cut in a table column

[Complete TSX file](/examples/truncate/table-cell.tsx)

```tsx
"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:

```tsx
<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:

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

[Metric Card](/docs/patterns/metric-card/#long-titles) cuts its title with a Truncate when you set its `titleLines`.

## API reference

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| Truncate.lines | number | 1 | Lines 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.tooltip | boolean \| TruncateTooltipProps | true | Shows 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.render | ReactElement \| render function | span | Another element for the text, such as a heading or a link. |
| Truncate.className | string | unset | Additional classes. A width class, such as max-w-1/2, sets where the text ends. |
| Truncate.children | ReactNode | unset | The 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](/source/truncate).

## 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](/docs/installation/) allows style attributes.
