One line of text with the middle removed so that both ends stay readable, fitted to whatever width the container actually gives it. Use it for file names — where ordinary CSS truncation eats the extension and every row ends up reading "quarterly-report-2026-fin…" — and for file paths and breadcrumbs, URLs, S3 and object-storage keys, IPFS CIDs, git SHAs and commit hashes, docker image digests, wallet and contract addresses, API keys, tokens and JWTs, request, trace and session IDs, branch and artifact names, email addresses in a narrow column, and any other identifier whose tail is the part that tells two of them apart. The places it usually goes: a file or storage browser, an uploads list, a table cell in a fixed-width column, a sidebar file tree, a build or deploy log, a commit list, an API-keys settings page, a connected-wallet button, and any breadcrumb that has to survive a narrow window. Common asks it answers: "truncate the middle of a string", "middle ellipsis react", "truncate middle component", "truncate a filename but keep the extension", "ellipsis in the middle of text", "shorten a wallet address to 0x1234…abcd", "truncate ethereum address react", "truncate a long path from the middle", "text-overflow ellipsis but centered", "css truncate middle", "tailwind truncate middle", "abbreviate a long ID", "responsive text truncation", "react-middle-truncate alternative", "react-truncate alternative", "smart ellipsis". CSS genuinely cannot do this — text-overflow: ellipsis only ever cuts the end, and there is no middle variant to reach for — and official shadcn/ui has no truncation primitive anywhere: its table and card are layout, scroll-area scrolls text it never shortens, and tooltip can show the full value but cannot decide what the short one should be. So it is normally hand-rolled as a fixed character count, which is wrong at every container width except the one it was tuned for. Distinct from pulld read-more, which expands a long passage the reader may want all of, from char-counter, which counts what is being typed rather than shortening what is shown, and from code-block, which wraps or scrolls rather than eliding. Pairs with copy-field and copy-button (the shortened key is the one you show, the full one is the one you copy), with upload-list and tree-view, whose rows are exactly these names. This measures the rendered text against the box it has to fit and binary-searches the cut point, so it fills the space exactly; it re-measures when the column resizes and again once web fonts have loaded, because a font swap changes every glyph width without changing the box. It cuts on grapheme boundaries using Intl.Segmenter, so an emoji, flag or accented letter landing on the cut does not become a replacement glyph the way a raw slice() would — which matters more here than elsewhere, since the cut point moves every time the container resizes. The full string stays in the DOM and only the visible copy is shortened: screen readers get the whole value instead of "0x4f2a ellipsis 91bc", find-in-page still matches it, and selecting the line copies the full text exactly once rather than the shortened form. Hovering shows the full value as a tooltip. It takes its width from its container — a flex row, a grid track, or a fixed width — and needs no min-w-0 to shrink; inside a shrink-to-fit parent there is nothing to fit to, so it simply renders in full. Zero npm dependencies, one file, and it inherits whatever type styles surround it rather than imposing its own.
pnpm dlx shadcn@latest add "https://pulld.pages.dev/r/middle-truncate.json"