MiddleTruncation

A string shortened in the middle to fit the width its container gives it, keeping the start and the end: quarterly-rep…final-3.pdf. Use it for file names, URLs, identifiers, hashes and e-mail addresses, where the end carries as much meaning as the start and a trailing ellipsis would cut off the part that matters.

Status: experimental — implemented and unit-tested in all nine headless catalogs (2026-10-23). Blazor, Nunjucks and plain HTML differ in how they measure; see "Per-library notes".

Example

quarterly-report-final-version-3.pdf
Show demo markup
<span class="middle-truncation" data-strategy="min-end" data-truncated><span class="middle-truncation-visual" aria-hidden="true">quarterly-rep…ion-3.pdf</span><span class="middle-truncation-full">quarterly-report-final-version-3.pdf</span></span>
Usage example
<MiddleTruncation text="quarterly-report-final-version-3.pdf" ellipsis="…" strategy="min-end" end={6} />
Show Svelte source
// In your Svelte component:
import MiddleTruncation from "lily-design-system-svelte-headless/components/MiddleTruncation/MiddleTruncation.svelte";

<MiddleTruncation>
  <!-- content -->
</MiddleTruncation>

Usage

<MiddleTruncation text="quarterly-report-final-version-3.pdf" ellipsis="…" strategy="min-end" end={6} />
/* consumer CSS */
.middle-truncation { display: block; overflow: hidden; white-space: nowrap; }
.middle-truncation-full {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

Props

Prop Type Default Description
class string "" Appended to the base class
text string (required) — The full text
ellipsis string (required) — The cut marker. No default: the headless layer ships no text, and the marker differs by language and context
strategy "even" | "fixed-end" | "min-end" "even" How the room is shared
end number — Characters kept at the end for fixed-end and min-end
...restProps HTML attributes — Spread onto the root <span>

Per-library notes

  • Svelte, React, Vue, Angular, Web Components, Alpine measure in the browser exactly as described above; each carries a copy of shared/middle-truncation.ts (the Alpine copy is the same module with its types stripped). Angular has no rest-props spread; Web Components take text, ellipsis, strategy and end as attributes.
  • HTML: the snippet's <script> finds every .middle-truncation, reads the full text from .middle-truncation-full and data-ellipsis / data-strategy / data-end, and measures in the browser. The algorithm inside the script is generated from the shared module between its BEGIN/END markers.
  • Nunjucks: a server cannot measure, so the macro renders the markup and data attributes only (the HTML script, or the Web Components element, then shortens in the browser); shortened takes text computed on the server.
  • Blazor: no JS interop, so no pixel measuring. MaxCharacters is a budget of user-perceived characters, ellipsis included, and the same strategies apply; without it the full text is shown.

Keyboard Interactions

None. The component is not interactive.

ARIA

  • The shortened string is aria-hidden="true"; assistive technology, find-in-page and copy get the full text from .middle-truncation-full
  • No role and no aria-label; the text is the content
  • A title attribute is not used: it reaches hovering pointers only, not keyboard, touch or screen-reader users

When to Use

  • A file name, URL, hash, identifier or e-mail address in a bounded space where the end matters as much as the start
  • A table cell or list row of unpredictable text length that must stay on one line
  • When the full text must stay reachable by screen readers and copy

When Not to Use

  • Use CSS text-overflow: ellipsis — when cutting the end is fine and no JavaScript is wanted
  • Use Tooltip or HoverCard — when the full text should appear on demand; combine with this component, do not replace it
  • Use a wrapped or multi-line layout — when the text can simply take more room
  • Do not use it for prose: a sentence cut in the middle is unreadable

Headless

This component decides the text and its structure only: which characters stay, and that the full text remains in the accessibility tree. It decides no width, font, colour or hiding; the consumer's CSS supplies the bounded block and the visually hidden full text.

Styles

The 57 reference themes style .middle-truncation as a bounded one-line block and hide .middle-truncation-full with the clip-rect pattern. In your own CSS, target .middle-truncation (the bounded block), .middle-truncation-visual and .middle-truncation-full (visually hide this one, never display: none). [data-truncated] styles the shortened state, and [data-strategy] the strategy. No default styles are included.

Testing

  • Base class first, then the consumer class
  • The visual text is shortened to the measured width and re-measured on resize, changing only the visual string
  • The shortened text is aria-hidden; the full text is not
  • Each strategy keeps the right characters; end of 0 keeps none; a width below the ellipsis still returns the ellipsis
  • Grapheme clusters are never split
  • Full text when it fits or when there is no layout width
  • Rest props reach the root

Advice

Pass the ellipsis in the reader's language and writing system (… is not right everywhere). For a file name, min-end with end set to the extension plus a few characters keeps .pdf readable. Measure in the font actually used: load web fonts first, or the width is measured in a fallback and the result is a little off until the next resize.

The measure uses the root's own font, so do not put a different font on the inner spans.

Implementation Notes

  • Renders <span class="middle-truncation {class}" data-strategy> holding .middle-truncation-visual (aria-hidden="true", the shortened string) and .middle-truncation-full (the whole text)
  • The width is measured in the browser with canvas text metrics in the root's computed font and compared with the root's clientWidth; a ResizeObserver re-measures (at most once per animation frame)
  • Until measured, on the server, and when the root has no layout width, the full text is shown, so nothing is lost without JavaScript
  • Cuts on grapheme clusters (Intl.Segmenter, falling back to code points), never inside a surrogate pair or an emoji sequence
  • data-truncated is present while the text is shortened
  • The algorithm is the pure truncateMiddle() in shared/middle-truncation.ts; it takes the measuring function as an argument, so it is tested without a browser

Related components

  • tooltip
  • hover-card
  • clipboard-copy-button — copy the full value
  • code

References


Lily™ and Lily Design System™ are trademarks.

Canonical contract

Generated from this component's canonical metadata — the machine-checked source the implementations are held to.

Metadata

  • Component: middle-truncation
  • PascalCase: MiddleTruncation
  • Description: a string shortened in the middle to fit its width, keeping the start and the end
  • Status: experimental — implemented and tested in all nine headless catalogs (2026-10-23); Blazor (MaxCharacters), Nunjucks and HTML measure differently, see index.md
  • HTML tag: <span>
  • CSS class: .middle-truncation
  • Interactive: no

ARIA

  • The shortened string is aria-hidden="true"; the full text in .middle-truncation-full is what assistive technology, find-in-page and copy receive
  • No role and no aria-label: the text content is the accessible content (a title attribute would reach hovering pointers only)

Keyboard

  • None

Props

  • class: string (default: "") -- appended to the base class
  • text: string (required) -- the full text
  • ellipsis: string (required) -- the cut marker; no default, the headless layer ships no text
  • strategy: "even" | "fixed-end" | "min-end" (default: "even")
  • end: number (optional) -- characters kept at the end for fixed-end and min-end; ignored by even
  • ...restProps: HTML attributes -- spread onto the root <span>

Markdown

This page as Markdown, for people and AI tools: middle-truncation.md