@absoluteui/helpers/string
Case conversion, grapheme-safe truncation, slugs, masking, templates, fuzzy search, and escaping.
import { ... } from '@absoluteui/helpers/string';How to use
Section titled “How to use”Unicode-aware text helpers for what applications display and search: fuzzy search with highlighting, safe templates, plurals and lists, masking, slugs, and case conversion. Length and truncation count user-perceived characters (grapheme clusters), so emoji and accented names are never cut in half. Formatting that depends on language takes a locale; for dates and numbers, see the Internationalization guide.
How do I search a people list and highlight the match?
Section titled “How do I search a people list and highlight the match?”fuzzyMatch ranks candidates across several keys (accents ignored by default); highlight splits the text into matched and unmatched segments, so you render the match with DOM nodes instead of HTML strings.
import { fuzzyMatch, highlight } from '@absoluteui/helpers/string';
const people = [ { name: 'Zoë Martin', email: 'zoe.martin@example.com' }, { name: 'Marta Olsen', email: 'marta.o@example.com' }, { name: 'Ben Ross', email: 'ben.ross@example.com' },];const hits = fuzzyMatch('zoe', people, { keys: ['name', 'email'], limit: 5 });hits.map((h) => h.item.name); // => ['Zoë Martin']highlight('Zoë Martin', 'zoe'); // => [{ text: 'Zoë', match: true }, { text: ' Martin', match: false }]How do I build a notification message safely?
Section titled “How do I build a notification message safely?”template fills {placeholders} without evaluating code; pass escape: escapeHtml when the result goes into HTML. pluralize picks the plural form for the locale and formatList joins names.
import { escapeHtml, formatList, pluralize, template } from '@absoluteui/helpers/string';
const approvers = ['Maya', 'Omar', 'Lin'];const days = pluralize(3, { one: '{count} day', other: '{count} days' }, { locale: 'en' });const text = template('{who} requested {days} of leave. Approvers: {list}.', { who: 'Ben <b>', days, list: formatList(approvers, { locale: 'en' }),}, { escape: escapeHtml });text; // => 'Ben <b> requested 3 days of leave. Approvers: Maya, Omar, and Lin.'How do I hide personal data in lists and logs?
Section titled “How do I hide personal data in lists and logs?”mask keeps a chosen number of characters visible and can skip formatting characters; maskEmail keeps the first character and the domain.
import { mask, maskEmail } from '@absoluteui/helpers/string';
mask('DE89 3704 0044 0532 0130 00', { visibleEnd: 4, preserve: ' ' }); // => '•••• •••• •••• •••• ••30 00'mask('123-45-6789', { visibleEnd: 4, preserve: '-', maskChar: '*' }); // => '***-**-6789'maskEmail('asha.rao@example.com'); // => 'a•••••••@example.com'How do I make file names, URLs, and avatar labels from names?
Section titled “How do I make file names, URLs, and avatar labels from names?”slugify removes accents and punctuation (Unicode letters are kept unless ascii: true); initials and truncate fit names into small spaces.
import { initials, slugify, truncate } from '@absoluteui/helpers/string';
`${slugify('Zoë Martin – Q3 Review', { maxLength: 40 })}.pdf`; // => 'zoe-martin-q3-review.pdf'initials('Zoë Martin'); // => 'ZM'truncate('Senior Payroll and Benefits Specialist', 20, { wordBoundary: true }); // => 'Senior Payroll and…'How do I convert between naming conventions?
Section titled “How do I convert between naming conventions?”Case helpers split words on separators, case changes, and digits, so API field names map cleanly to column ids and labels.
import { camelCase, constantCase, sentenceCase, snakeCase } from '@absoluteui/helpers/string';
camelCase('hire_date'); // => 'hireDate'snakeCase('employeeId'); // => 'employee_id'sentenceCase('annualLeaveBalance'); // => 'Annual leave balance'constantCase('leave-type'); // => 'LEAVE_TYPE'API reference
Section titled “API reference”General
Section titled “General”Functions
Section titled “Functions”between
Text between the first start and the next end; undefined when either is missing.
function between(s: string, start: string, end: string): string | undefined;Example:
between('key=[value]', '[', ']'); // => 'value'byteLengthUtf8
UTF-8 byte length without allocating (lone surrogates count as U+FFFD, 3 bytes).
function byteLengthUtf8(s: string): number;Example:
byteLengthUtf8('€'); // => 3camelCase
camelCase. Acronyms are lowered (XMLHttpRequest -> xmlHttpRequest).
function camelCase(s: string, options?: LocaleOptions): string;Example:
camelCase('user_first-name'); // => 'userFirstName'capitalize
Uppercases the first character (locale-aware: { locale: 'tr' } maps i -> İ).
function capitalize(s: string, options?: CapitalizeOptions): string;Example:
capitalize('élan'); // => 'Élan'center
Centers s in width grapheme columns with fill.
function center(s: string, width: number, fill?: string): string;Example:
center('ab', 6, '*'); // => '**ab**'charCount
Length in the given unit: grapheme (default), codePoint, or codeUnit (UTF-16).
function charCount(s: string, options?: { readonly unit?: 'grapheme' | 'codePoint' | 'codeUnit';}): number;Example:
charCount('😀', { unit: 'codeUnit' }); // => 2collapseWhitespace
Trims and collapses every whitespace run (including newlines) to one space.
function collapseWhitespace(s: string): string;Example:
collapseWhitespace(' a \n\t b '); // => 'a b'compareIgnoreCase
Case-insensitive locale comparison (-1, 0, 1) for sorting.
function compareIgnoreCase(a: string, b: string, options?: IgnoreCaseOptions): -1 | 0 | 1;Example:
['b', 'A', 'a'].sort((x, y) => compareIgnoreCase(x, y)); // => ['A', 'a', 'b']constantCase
CONSTANT_CASE.
function constantCase(s: string, options?: LocaleOptions): string;Example:
constantCase('maxRetryCount'); // => 'MAX_RETRY_COUNT'containsIgnoreCase
Case-insensitive (optionally accent-insensitive) substring test with locale-aware case folding.
function containsIgnoreCase(haystack: string, needle: string, options?: IgnoreCaseOptions): boolean;Example:
containsIgnoreCase('Crème Brûlée', 'BRULEE', { ignoreAccents: true }); // => truecountOccurrences
Counts occurrences of a literal substring (non-overlapping by default).
function countOccurrences(s: string, sub: string, options?: { readonly overlapping?: boolean;}): number;Example:
countOccurrences('aaaa', 'aa'); // => 2countOccurrences('aaaa', 'aa', { overlapping: true }); // => 3damerauLevenshtein
Damerau-Levenshtein (optimal string alignment): adjacent transpositions cost 1. O(min(n, m)) memory.
function damerauLevenshtein(a: string, b: string, options?: DistanceOptions): number;Example:
damerauLevenshtein('ca', 'ac'); // => 1dedent
Removes the longest common leading whitespace of non-blank lines.
function dedent(s: string): string;Example:
dedent(' a\n b'); // => 'a\n b'dotCase
dot.case.
function dotCase(s: string, options?: LocaleOptions): string;Example:
dotCase('userFirstName'); // => 'user.first.name'endsWithAny
True when s ends with any suffix.
function endsWithAny(s: string, suffixes: Iterable<string>): boolean;Example:
endsWithAny('photo.JPG'.toLowerCase(), ['.jpg', '.png']); // => trueequalsIgnoreCase
Case-insensitive equality via Intl.Collator (sensitivity accent, or base with ignoreAccents).
function equalsIgnoreCase(a: string, b: string, options?: IgnoreCaseOptions): boolean;Example:
equalsIgnoreCase('Straße', 'STRASSE', { locale: 'de' }); // true where the collator treats ß as ssescapeHtml
Escapes & < > " ' for HTML text and attribute values. Not a sanitizer.
function escapeHtml(s: string): string;Example:
escapeHtml('<a href="x">Tom & Jerry\'s</a>'); // => '<a href="x">Tom & Jerry's</a>'escapeRegExp
Escapes RegExp syntax characters so s matches literally (valid with and without the u flag).
function escapeRegExp(s: string): string;Example:
new RegExp(escapeRegExp('1+1=2?')).test('1+1=2?'); // => trueformatList
Locale-aware list with Intl.ListFormat.
function formatList(items: Iterable<string>, options?: FormatListOptions): string;Example:
formatList(['red', 'green', 'blue']); // => 'red, green, and blue' // enformatList(['tea', 'coffee'], { type: 'disjunction' }); // => 'tea or coffee'fuzzyMatch
Ranked fuzzy search: case- and accent-insensitive substring and subsequence matching with
word-start and contiguity bonuses. Results carry ranges for accessible highlighting.
Ties keep shorter texts first, then input order. An empty query returns every candidate with score 1.
function fuzzyMatch<T>(query: string, candidates: readonly T[], options?: FuzzyOptions<T>): FuzzyMatch<T>[];Example:
const [best] = fuzzyMatch('jsmi', ['John Smith', 'Jane Doe']);best?.item; // => 'John Smith'best?.ranges; // => [[0, 1], [5, 8]]const people = [{ name: 'Joanna Kay', email: 'jkay@x.io' }, { name: 'Ben Ross', email: 'ann.ross@x.io' }, { name: 'Cy Diaz', email: 'cy@x.io' }];fuzzyMatch('ann', people, { keys: ['name', (p) => p.email], limit: 5 }).map((m) => m.item.name); // => ['Ben Ross', 'Joanna Kay']graphemeLength
Number of user-perceived characters.
function graphemeLength(s: string): number;Example:
graphemeLength('👨\u200d👩\u200d👧'); // => 1graphemes
User-perceived characters (extended grapheme clusters via Intl.Segmenter, with a fallback).
function graphemes(s: string): string[];Example:
graphemes('👍🏽e\u0301'); // => ['👍🏽', 'e\u0301'] // 2 graphemeshighlight
Splits text into matched and unmatched segments for a query (case/accent-insensitive) or for
explicit ranges (from fuzzyMatch). Render matches with <mark> using textContent.
function highlight(text: string, query: string | readonly TextRange[], options?: HighlightOptions): HighlightSegment[];Example:
highlight('Crème brûlée', 'brulee'); // => [{ text: 'Crème ', match: false }, { text: 'brûlée', match: true }]indent
Prefixes each line with indent (a string or a number of spaces). Blank lines stay blank unless indentEmpty.
function indent(s: string, prefix?: string | number, options?: { readonly indentEmpty?: boolean;}): string;Example:
indent('a\nb', 2); // => ' a\n b'initials
Initials from a name (grapheme-safe, locale-aware uppercase). With more words than max, keeps
the first max - 1 and the last.
function initials(name: string, options?: LocaleOptions & { readonly max?: number;}): string;Example:
initials('John Ronald Reuel Tolkien'); // => 'JT'initials('élodie durand'); // => 'ÉD'isAlpha
True for one or more Unicode letters (combining marks allowed after a letter).
function isAlpha(s: string): boolean;Example:
isAlpha('Ñandú'); // => trueisAlphanumeric
True for one or more Unicode letters or decimal digits.
function isAlphanumeric(s: string): boolean;Example:
isAlphanumeric('abc123'); // => trueisNumeric
True for one or more Unicode decimal digits (\p{Nd}, including non-Latin digits such as ٣).
function isNumeric(s: string): boolean;Example:
isNumeric('0123'); // => trueisNumeric('1.5'); // => falsekebabCase
kebab-case.
function kebabCase(s: string, options?: LocaleOptions): string;Example:
kebabCase('XMLHttpRequest'); // => 'xml-http-request'levenshtein
Levenshtein distance over code points: O(n|m) time, O(min(n, m)) memory, optional early exit.
function levenshtein(a: string, b: string, options?: DistanceOptions): number;Example:
levenshtein('kitten', 'sitting'); // => 3levenshtein('kitten', 'sitting', { maxDistance: 1 }); // => 2 // exceededmask
Masks all but the visible start/end characters (grapheme-safe). If the visible parts would reveal everything, everything is masked.
function mask(s: string, options?: MaskOptions): string;Example:
mask('4111111111111111', { visibleEnd: 4 }); // => '••••••••••••1111'mask('4111 1111 1111 1111', { visibleEnd: 4, preserve: ' ' }); // => '•••• •••• •••• 1111'maskEmail
Masks the local part of an email address.
function maskEmail(email: string, options?: MaskEmailOptions): string;Example:
maskEmail('john.doe@example.com'); // => 'j•••••••@example.com'maskEmail('ann@example.com', { maskDomain: true }); // => 'a••@e••••••.com'normalizeWhitespace
Normalizes line endings (CRLF, CR, U+2028/9 -> LF), turns other whitespace (tabs, NBSP, thin spaces) into plain spaces, and drops BOM/zero-width spaces. Structure (runs, newlines) is kept.
function normalizeWhitespace(s: string): string;Example:
normalizeWhitespace('a\u00a0b\r\nc'); // => 'a b\nc'pascalCase
PascalCase.
function pascalCase(s: string, options?: LocaleOptions): string;Example:
pascalCase('user first name'); // => 'UserFirstName'pluralize
Picks the plural form for count with Intl.PluralRules and replaces {count} with the
locale-formatted number. An explicit zero form wins for 0 in every locale.
function pluralize(count: number, forms: PluralForms, options?: LocaleOptions & { readonly type?: Intl.PluralRuleType;}): string;Example:
pluralize(1, { one: '{count} file', other: '{count} files' }); // => '1 file'pluralize(3, { one: '{count} plik', few: '{count} pliki', many: '{count} plików', other: '{count} pliku' }, { locale: 'pl' }); // => '3 pliki'removeDiacritics
Removes Latin/Greek/Cyrillic diacritics (é -> e, ł -> l); vowel signs of other scripts are kept.
function removeDiacritics(s: string): string;Example:
removeDiacritics('Crème Brûlée, Łódź'); // => 'Creme Brulee, Lodz'replaceAllSafe
Replaces every occurrence of a literal string; $ patterns in replacement are not interpreted.
function replaceAllSafe(s: string, search: string, replacement: string): string;Example:
replaceAllSafe('a.b.c', '.', '$&'); // => 'a$&b$&c'reverseGraphemes
Reverses by grapheme clusters, keeping emoji and combining marks intact.
function reverseGraphemes(s: string): string;Example:
reverseGraphemes('ab👍🏽'); // => '👍🏽ba'sentenceCase
Sentence case (first word capitalized, words joined by spaces).
function sentenceCase(s: string, options?: LocaleOptions): string;Example:
sentenceCase('userFirstName'); // => 'User first name'similarity
Normalized similarity 0-1 (1 - Levenshtein / longer length); two empty strings are 1.
function similarity(a: string, b: string, options?: IgnoreCaseOptions & { readonly ignoreCase?: boolean;}): number;Example:
similarity('color', 'colour'); // => ≈ 0.833slugify
URL slug: NFKD, diacritics removed, locale-aware lowercasing, non-word runs to one separator.
function slugify(s: string, options?: SlugifyOptions): string;Example:
slugify('Crème Brûlée & Café!'); // => 'creme-brulee-cafe'slugify('İstanbul', { locale: 'tr' }); // => 'istanbul'snakeCase
snake_case.
function snakeCase(s: string, options?: LocaleOptions): string;Example:
snakeCase('userFirstName'); // => 'user_first_name'splitLines
Splits on LF, CRLF, CR, U+2028 and U+2029.
function splitLines(s: string): string[];Example:
splitLines('a\r\nb\nc'); // => ['a', 'b', 'c']startsWithAny
True when s starts with any prefix.
function startsWithAny(s: string, prefixes: Iterable<string>): boolean;Example:
startsWithAny('https://x', ['http://', 'https://']); // => truestripControlChars
Removes C0/C1 control characters and, by default, bidi override controls.
function stripControlChars(s: string, options?: StripControlOptions): string;Example:
stripControlChars('a\u0000b\u202ec\n'); // => 'abc\n'template
Safe {name} / {a.b} interpolation: no code evaluation, own properties only, unsafe keys
(__proto__, constructor, prototype) never resolve. {{ and }} produce literal braces.
Primitives are stringified, dates use ISO 8601; objects count as missing.
function template(str: string, values: object, options?: TemplateOptions): string;Example:
template('Hello {user.name}, you have {count} tasks', { user: { name: 'Ann' }, count: 3 }); // => 'Hello Ann, you have 3 tasks'titleCase
Title Case from any identifier or phrase (words joined by spaces).
function titleCase(s: string, options?: TitleCaseOptions): string;Example:
titleCase('the_lord-of the rings', { minorWords: ENGLISH_MINOR_WORDS }); // => 'The Lord of the Rings'toCodePoints
Code points of s.
function toCodePoints(s: string): number[];Example:
toCodePoints('a😀'); // => [97, 128512]truncate
Truncates to at most length grapheme clusters including the omission. Never splits emoji or accents.
function truncate(s: string, length: number, options?: TruncateOptions): string;Example:
truncate('Hello wonderful world', 12, { wordBoundary: true }); // => 'Hello…'truncate('👍🏽👍🏽👍🏽', 2); // => '👍🏽…'truncateMiddle
Truncates in the middle, keeping the start and end (file names, IDs).
function truncateMiddle(s: string, length: number, options?: { readonly omission?: string;}): string;Example:
truncateMiddle('annual-report-2026-final.pdf', 15); // => 'annual-…nal.pdf'uncapitalize
Lowercases the first character.
function uncapitalize(s: string, options?: LocaleOptions): string;Example:
uncapitalize('Hello World'); // => 'hello World'unescapeHtml
Decodes & < > " ' and numeric references (', ').
Invalid code points become U+FFFD.
function unescapeHtml(s: string): string;Example:
unescapeHtml('Tom & Jerry's 😀'); // => "Tom & Jerry's 😀"wordCount
Word count using Intl.Segmenter word boundaries (works for scripts without spaces, such as Japanese).
function wordCount(s: string, options?: LocaleOptions): number;Example:
wordCount("Don't stop, it's 2026!"); // => 4wrapText
Greedy word wrap to width grapheme columns; existing line breaks are kept.
function wrapText(s: string, width: number, options?: WrapTextOptions): string;Example:
wrapText('The quick brown fox', 10); // => 'The quick\nbrown fox'Constants
Section titled “Constants”ENGLISH_MINOR_WORDS
Common English minor words for titleCase({ minorWords }).
const ENGLISH_MINOR_WORDS: readonly string[];Example:
titleCase('war and peace', { minorWords: ENGLISH_MINOR_WORDS }); // => 'War and Peace'Interfaces
Section titled “Interfaces”CapitalizeOptions
interface CapitalizeOptions extends LocaleOptions { readonly lowerRest?: boolean;}DistanceOptions
interface DistanceOptions { readonly maxDistance?: number;}FormatListOptions
interface FormatListOptions extends LocaleOptions { readonly type?: Intl.ListFormatType; readonly style?: Intl.ListFormatStyle;}FuzzyMatch
interface FuzzyMatch<T> { readonly item: T; readonly index: number; readonly score: number; readonly key: number; readonly text: string; readonly ranges: readonly TextRange[];}FuzzyOptions
interface FuzzyOptions<T> extends LocaleOptions { readonly limit?: number; readonly threshold?: number; readonly keys?: readonly FuzzyKey<T>[]; readonly ignoreAccents?: boolean;}HighlightOptions
interface HighlightOptions extends LocaleOptions { readonly ignoreAccents?: boolean; readonly words?: boolean;}HighlightSegment
A piece of text and whether it matched, for rendering <mark> via textContent (no HTML).
interface HighlightSegment { readonly text: string; readonly match: boolean;}IgnoreCaseOptions
interface IgnoreCaseOptions extends LocaleOptions { readonly ignoreAccents?: boolean;}LocaleOptions
Options for locale-sensitive helpers. Without locale, case mapping is locale-independent.
interface LocaleOptions { readonly locale?: Intl.LocalesArgument;}MaskEmailOptions
interface MaskEmailOptions { readonly maskChar?: string; readonly visible?: number; readonly maskDomain?: boolean;}MaskOptions
interface MaskOptions { readonly visibleStart?: number; readonly visibleEnd?: number; readonly maskChar?: string; readonly preserve?: string;}PluralForms
Plural forms keyed by Intl.PluralRules category; other is required.
interface PluralForms { readonly zero?: string; readonly one?: string; readonly two?: string; readonly few?: string; readonly many?: string; readonly other: string;}SlugifyOptions
interface SlugifyOptions extends LocaleOptions { readonly separator?: string; readonly lowercase?: boolean; readonly ascii?: boolean; readonly maxLength?: number;}StripControlOptions
interface StripControlOptions { readonly keepWhitespace?: boolean; readonly stripBidi?: boolean;}TemplateOptions
interface TemplateOptions { readonly missing?: 'keep' | 'empty' | 'error'; readonly escape?: (value: string) => string;}TitleCaseOptions
interface TitleCaseOptions extends LocaleOptions { readonly minorWords?: Iterable<string>;}TruncateOptions
interface TruncateOptions { readonly omission?: string; readonly wordBoundary?: boolean;}WrapTextOptions
interface WrapTextOptions { readonly breakWords?: boolean; readonly newline?: string;}FuzzyKey
type FuzzyKey<T> = ((item: T) => string | null | undefined) | { [K in keyof T]: T[K] extends string | null | undefined ? K : never;}[keyof T];TextRange
Half-open [start, end) UTF-16 range in the original text.
type TextRange = readonly [start: number, end: number];Functions
Section titled “Functions”splitWords
Splits identifiers and phrases into words: separators, lower->upper humps, acronym boundaries
(XMLHttpRequest -> XML, Http, Request), optionally digits. Combining marks stay with
their letter; apostrophes inside words are kept (don't).
function splitWords(input: string, options?: SplitWordsOptions): string[];Example:
splitWords('XMLHttpRequest'); // => ['XML', 'Http', 'Request']splitWords('öffne_DieTür-2x'); // => ['öffne', 'Die', 'Tür', '2x']Interfaces
Section titled “Interfaces”SplitWordsOptions
Unicode-aware word splitting for case conversion.
interface SplitWordsOptions { readonly splitDigits?: boolean;}