vue-clamp
v1.4.0
Published
Clamping primitives for Vue.
Maintainers
Readme
vue-clamp
Clamping primitives for Vue 3. vue-clamp measures real browser layout so text, inline content,
and wrapped items fit the space they are actually rendered into.
- Live docs and demos: vue-clamp.void.app
- Migration guide: MIGRATION.md
- Release notes: CHANGELOG.md
Install
pnpm add vue-clampvue-clamp has a peer dependency on Vue ^3.3.0. Install Vue too if your project does not already
depend on it.
Components
| Component | Use it for |
| ----------------- | ------------------------------------------------------------------- |
| <LineClamp> | Multiline plain text with optional start, middle, or end ellipsis. |
| <RichLineClamp> | Trusted inline HTML that should keep formatting while clamping. |
| <InlineClamp> | One-line strings with fixed affixes and configurable ellipsis. |
| <WrapClamp> | Wrapped atomic items such as tags, filters, chips, and breadcrumbs. |
The package has named exports only:
import { InlineClamp, LineClamp, RichLineClamp, WrapClamp } from "vue-clamp";Quick start
<script setup lang="ts">
import { ref } from "vue";
import { LineClamp } from "vue-clamp";
const expanded = ref(false);
const text = "Ship review-ready notes with browser-fit text truncation and keep the toggle inline.";
</script>
<template>
<LineClamp v-model:expanded="expanded" :text="text" :max-lines="2">
<template #after="{ clamped, expanded, toggle }">
<button v-if="clamped" type="button" @click="toggle">
{{ expanded ? "Less" : "More" }}
</button>
</template>
</LineClamp>
</template>Plain text
Use <LineClamp> when the source is plain text and the browser should decide line wrapping.
<LineClamp :text="title" :max-lines="2" location="middle" boundary="word" ellipsis="..." />Useful props:
text: source text. Defaults to"".max-lines: maximum visible line count.max-height: maximum visible height. Numbers are treated as pixels.ellipsis: string inserted into clamped output. Defaults to….location:start,middle,end, or a number from0to1. Defaults toend.boundary:graphemeorword. Defaults tographeme. Usewordto avoid partial words; single-line word-boundary clamping uses the measured JS path instead of nativetext-overflow.expanded: show the full text. Supportsv-model:expanded.
before and after slots render inline with the text and receive
{ expand, collapse, toggle, clamped, expanded }.
Trusted rich text
Use <RichLineClamp> for trusted or already-sanitized inline markup.
<RichLineClamp v-model:expanded="expanded" :html="html" :max-lines="2">
<template #after="{ clamped, expanded, toggle }">
<button v-if="clamped" type="button" @click="toggle">
{{ expanded ? "Less" : "More" }}
</button>
</template>
</RichLineClamp>Rich clamping is intentionally scoped:
htmlis rendered as HTML. Sanitize untrusted input before passing it in.- Rich content clamps from the end only.
boundarycan begraphemeorword. Defaults tographeme;wordavoids partial words inside supported text runs.- Inline elements can participate when they can be cloned back into the DOM and stay in inline flow.
- Leaf elements without light DOM content are treated as atomic inline units, including custom elements.
br,wbr,img, and outersvgelements have explicit handling when they stay in inline flow.- Inline rich images must have deterministic rendered dimensions before loading, set by attributes or CSS.
- Unsupported markup falls back to the original HTML unchanged.
before and after slots receive the same control props as <LineClamp>.
Single-line strings
Use <InlineClamp> for one-line text where part of the string should remain fixed while the body
shrinks. The location prop controls how body text is kept around the ellipsis; in tight spaces,
the body can become just the ellipsis.
<script setup lang="ts">
import { InlineClamp } from "vue-clamp";
const file = "summer-campaign-panorama-final.jpeg";
function splitFileName(text: string) {
const extension = text.match(/\.[^.]+$/)?.[0];
return extension ? { body: text.slice(0, -extension.length), end: extension } : { body: text };
}
</script>
<template>
<InlineClamp :text="file" :split="splitFileName" location="middle" />
</template>Useful props:
text: required source string.ellipsis: string inserted into the rewritten body. Defaults to….location: how body text is kept around the ellipsis:start,middle,end, or a number from0to1. Defaults toend.boundary:graphemeorword. Defaults tographeme;wordavoids partial words in the rewritten body, falling back to grapheme cuts when no whole word can fit.split: optional function returning{ start?: string, body: string, end?: string }.as: root tag name. Defaults tospan.
<InlineClamp> has no slots or expansion API.
Wrapped items
Use <WrapClamp> when each item must stay whole.
<script setup lang="ts">
import { ref } from "vue";
import { WrapClamp } from "vue-clamp";
const expanded = ref(false);
const labels = [
{ id: "perf", label: "Performance" },
{ id: "a11y", label: "Accessibility" },
{ id: "docs", label: "Docs" },
{ id: "qa", label: "Needs QA" },
];
</script>
<template>
<WrapClamp v-model:expanded="expanded" :items="labels" item-key="id" :max-lines="2">
<template #item="{ item }">
<span class="tag">{{ item.label }}</span>
</template>
<template #after="{ clamped, expanded, hiddenItems, toggle }">
<button v-if="expanded || clamped" type="button" @click="toggle">
{{ expanded ? "Less" : `+${hiddenItems.length} more` }}
</button>
</template>
</WrapClamp>
</template>Useful props:
items: ordered source items. Defaults to[].item-key: string field name or(item, index) => string | numberkey resolver.max-lines: maximum visible wrapped line count.max-height: maximum visible height. Numbers are treated as pixels.expanded: show the full item list. Supportsv-model:expanded.
The item slot receives { item, index }. The before and after slots receive
{ expand, collapse, toggle, clamped, expanded, hiddenItems }.
Events and instance methods
<LineClamp>, <RichLineClamp>, and <WrapClamp> emit:
clampchange:(clamped: boolean), emitted when truncation turns on or off.update:expanded:(expanded: boolean), emitted forv-model:expanded.
They also expose expand(), collapse(), toggle(), clamped, and expanded through a template
ref.
Styling hooks
Stable styling hooks use data-part attributes:
| Component | Parts |
| ----------------- | -------------------------------------------- |
| <LineClamp> | root, content, before, body, after |
| <RichLineClamp> | root, content, before, body, after |
| <InlineClamp> | root, start, body, end |
| <WrapClamp> | root, content, before, item, after |
Do not rely on internal DOM nesting as a styling contract.
Notes
1.xis the Vue 3 line. See the migration guide when upgrading from0.x.ResizeObserveris part of the browser baseline.
