Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

truncateSafe

Truncates a string by character length without breaking words. Appends an ellipsis (or custom suffix) if truncation occurs.

TypeScript
Copied!
1/**
2 * Truncates a string by character length without breaking words.
3 * Appends an ellipsis (or custom suffix) if truncation occurs.
4 *
5 * @param str - The input string.
6 * @param maxChars - The maximum allowed number of characters (excluding suffix).
7 * @param suffix - Optional suffix to append if truncated (default is '...').
8 * @returns Truncated string without breaking words.
9 */
10export function truncateSafe(
11  str: string,
12  maxChars: number,
13  suffix: string = '...'
14): string {
15  if (maxChars < 0) throw new Error('maxChars must be non-negative');
16
17  const words = str.trim().split(/\s+/);
18  let result = '';
19  let lengthSoFar = 0;
20
21  for (const word of words) {
22    const wordWithSpace = (result ? ' ' : '') + word;
23    if (lengthSoFar + wordWithSpace.length > maxChars) break;
24    result += wordWithSpace;
25    lengthSoFar += wordWithSpace.length;
26  }
27
28  return result.length < str.trim().length ? result + suffix : str;
29}
  • Prevents Mid-Word Breaks

    Ensures no words are split during truncation, improving text readability and aesthetics.

  • Character-Aware Logic

    Respects a strict character limit while preserving whole words, useful for UI constraints.

  • Customizable Suffix

    Supports a custom trailing string (e.g., '...', ' [more]'), allowing contextual customization.

  • Whitespace Robustness

    Handles leading, trailing, and excess intermediate whitespace gracefully with .trim() and regex splitting.

  • Fail-Safe Guard

    Throws a clear error for negative input, avoiding unexpected behavior.

Tests | Examples

TypeScript
Copied!
1test('truncateSafe - no truncation needed', () => {
2  expect(truncateSafe('Hello world', 20)).toBe('Hello world');
3});
4
5test('truncateSafe - truncates without breaking word', () => {
6  expect(truncateSafe('The quick brown fox jumps', 15)).toBe('The quick...');
7});
8
9test('truncateSafe - breaks before first word', () => {
10  expect(truncateSafe('Hello', 2)).toBe('...');
11});
12
13test('truncateSafe - exact cutoff', () => {
14  expect(truncateSafe('One two three', 11)).toBe('One two three');
15});
16
17test('truncateSafe - custom suffix', () => {
18  expect(truncateSafe('Longer sentence here', 10, ' [more]')).toBe('Longer [more]');
19});
20
21test('truncateSafe - empty string', () => {
22  expect(truncateSafe('', 5)).toBe('');
23});
24
25test('truncateSafe - zero maxChars', () => {
26  expect(truncateSafe('Some text', 0)).toBe('...');
27});
28
29test('truncateSafe - throws on negative input', () => {
30  expect(() => truncateSafe('Text', -1)).toThrow('maxChars must be non-negative');
31});

Common Use Cases

  • Product or Article Previews

    Display a concise snippet with whole words up to a visual/character limit.

  • Meta Descriptions & SEO Tags

    Format dynamic content to fit meta tag limits (e.g., 150–160 characters) without word breaks.

  • UX Text Constraints

    Useful in UI components where content must stay within strict pixel or character boundaries (e.g., cards, tables).

  • Push Notification Bodies

    Truncate long messages for previews without making them look abruptly cut.

  • CMS or Blog Editors

    Dynamically preview truncated summaries with clean word boundaries for editors or authors.

Codebase: Utilities -> Strings -> truncateSafe | Yevhen Klymentiev