Lightweight text utilities for JavaScript/TypeScript. Format, clean, search, and style text with zero dependencies.
- Tiny - Lightweight with zero dependencies
- Tree-shakeable - Import only what you need
- Type-safe - Full TypeScript support with complete type definitions
# npm
npm install textal
# yarn
yarn add textal
# pnpm
pnpm add textal
import { titleCase, truncate, highlight, linkify } from 'textal';
// Formatting
console.log(titleCase('hello world')); // "Hello World"
console.log(truncate('This is a long text', 10)); // "This is a..."
// Styling
console.log(highlight('Error: something went wrong', 'Error', 'text-red-500'));
// <span class="text-red-500">Error</span>: something went wrong
console.log(linkify('Visit https://example.com'));
// Visit <a href="https://example.com">https://example.com</a>
capitalizeText(str: string): string
- Capitalize first lettertitleCase(str: string): string
- Convert to Title CasecamelCase(str: string): string
- Convert to camelCasepascalCase(str: string): string
- Convert to PascalCasekebabCase(str: string): string
- Convert to kebab-casesnakeCase(str: string): string
- Convert to snake_caseconstantCase(str: string): string
- Convert to CONSTANT_CASEsentenceCase(str: string): string
- Convert to Sentence casenoCase(str: string): string
- Convert to space-delimited lowercaseslugify(str: string, opts?: { lower?: boolean, separator?: string }): string
- Convert to URL-friendly slugtruncate(str: string, length: number, end?: string): string
- Truncate string by character counttruncateWords(str: string, count: number, end?: string): string
- Truncate string by word countpad(str: string, length: number, options?: { side?: 'start' | 'end' | 'both', fillChar?: string }): string
- Pad string on specified sidestripChars(str: string, chars?: string): string
- Remove specified characters
stripHtml(str: string): string
- Remove HTML tagsescapeHtml(str: string): string
- Escape HTML entitiesunescapeHtml(str: string): string
- Unescape HTML entitiesnormalizeWhitespace(str: string, opts?: { collapse?: boolean, trim?: boolean, collapseNewlines?: boolean }): string
- Normalize whitespace with optionsremovePunctuation(str: string): string
- Remove punctuationonlyAlpha(str: string): string
- Keep only alphabetic charactersonlyNumeric(str: string): string
- Keep only numeric characterstoAscii(str: string): string
- Convert to ASCII (remove diacritics)filterChars(str: string, opts: { alpha?: boolean, numeric?: boolean, spaces?: boolean, punctuation?: boolean, extras?: RegExp }): string
- Filter characters based on criteriastripAnsi(str: string): string
- Remove ANSI escape codesdedent(str: string): string
- Remove common leading indentationcollapseNewlines(str: string, max?: number): string
- Collapse consecutive newlinestrimLines(str: string): string
- Trim whitespace from each line
contains(str: string, term: string, opts?: { caseSensitive?: boolean }): boolean
- Check if string contains termcount(str: string, term: string, opts?: { caseSensitive?: boolean }): number
- Count term occurrencesextract(str: string, pattern: RegExp): string[]
- Extract all regex matchesbetween(str: string, start: string, end: string): string
- Extract text between delimiters (first match)betweenAll(str: string, start: string, end: string): string[]
- Extract all text between delimitersstartsWith(str: string, term: string, opts?: { caseSensitive?: boolean }): boolean
- Check if string starts with termendsWith(str: string, term: string, opts?: { caseSensitive?: boolean }): boolean
- Check if string ends with termindexOfAll(str: string, term: string, opts?: { caseSensitive?: boolean }): number[]
- Find all positions of termnthIndexOf(str: string, term: string, n: number, opts?: { caseSensitive?: boolean }): number
- Find nth occurrence position
wrap(str: string, tag?: string, className?: string): string
- Wrap text in HTML taghighlight(str: string, term: string, className?: string, opts?: { caseSensitive?: boolean, wholeWord?: boolean }): string
- Highlight search termslinkify(str: string, opts?: { target?: '_blank' | '_self', rel?: string, className?: string }): string
- Convert URLs to linksnl2br(str: string): string
- Convert newlines to HTML<br>
tags
wordCount(str: string): number
- Count wordscharCount(str: string, opts?: { excludeSpaces?: boolean }): number
- Count charactersreadingTime(str: string, wpm?: number): { minutes: number, seconds: number, words: number }
- Estimate reading timelineCount(str: string): number
- Count linessentenceCount(str: string): number
- Count sentencesbyteLength(str: string): number
- Get byte length in UTF-8graphemeCount(str: string): number
- Count grapheme clusters (user-perceived characters)
escapeRegex(str: string): string
- Escape special regex characters for use in RegExptokenizeWords(str: string): string[]
- Split string into word tokenssplitLines(str: string): string[]
- Split string into linesjoinLines(lines: string[]): string
- Join lines into stringuniqueLines(str: string): string
- Remove duplicate lines
parseDate(str: string): Date
- Parse string into Date objectformatDate(date: Date, format?: string): string
- Format Date object using a format string (e.g. "yyyy-MM-dd")
Token | Output | Description |
---|---|---|
yyyy |
2024 | 4-digit year |
yy |
24 | 2-digit year |
MMMM |
January | Full month name |
MMM |
Jan | Short month name |
MM |
01-12 | 2-digit month |
M |
1-12 | Month |
dd |
01-31 | 2-digit day |
d |
1-31 | Day |
EEEE |
Monday | Full day name |
EEE |
Mon | Short day name |
HH |
00-23 | 2-digit 24-hour |
H |
0-23 | 24-hour |
hh |
01-12 | 2-digit 12-hour |
h |
1-12 | 12-hour |
mm |
00-59 | 2-digit minutes |
m |
0-59 | Minutes |
ss |
00-59 | 2-digit seconds |
s |
0-59 | Seconds |
SSS |
000-999 | Milliseconds |
A / AA |
AM/PM | Uppercase AM/PM |
a / aa |
am/pm | Lowercase am/pm |
import { camelCase, kebabCase, snakeCase, titleCase } from 'textal';
console.log(camelCase('hello-world')); // "helloWorld"
console.log(kebabCase('helloWorld')); // "hello-world"
console.log(snakeCase('helloWorld')); // "hello_world"
console.log(titleCase('hello world')); // "Hello World"
import { stripHtml, toAscii, normalizeWhitespace } from 'textal';
console.log(stripHtml('<p>Hello <b>world</b>!</p>')); // "Hello world!"
console.log(toAscii('café')); // "cafe"
console.log(normalizeWhitespace(' Hello world! ')); // "Hello world!"
import { contains, count, between } from 'textal';
console.log(contains('Hello world', 'WORLD')); // true (case-insensitive by default)
console.log(count('banana', 'ana')); // 2 (overlapping matches)
console.log(between('Hello [world]', '[', ']')); // "world"
import { highlight, linkify } from 'textal';
// Highlight search terms
const text = 'Error: Something went wrong';
const highlighted = highlight(text, 'error', 'text-red-500');
// <span class="text-red-500">Error</span>: Something went wrong
// Convert URLs to links
const message = 'Visit https://example.com for more info';
const linked = linkify(message, {
target: '_blank',
rel: 'noopener noreferrer',
className: 'text-blue-500'
});
// Visit <a href="https://example.com" target="_blank" rel="noopener noreferrer" class="text-blue-500">https://example.com</a> for more info
import { wordCount, charCount, readingTime } from 'textal';
const text = 'This is a sample text.';
console.log(wordCount(text)); // 5
console.log(charCount(text)); // 22
console.log(readingTime(text, 200));
// { minutes: 0, seconds: 2, words: 5 }
import { parseDate, formatDate } from 'textal';
// Parse date strings
const date = parseDate('2024-01-15T14:30:45');
// Format dates with various patterns
console.log(formatDate(date, 'yyyy-MM-dd')); // '2024-01-15'
console.log(formatDate(date, 'MMMM d, yyyy')); // 'January 15, 2024'
console.log(formatDate(date, 'EEEE, MMM d')); // 'Monday, Jan 15'
console.log(formatDate(date, 'h:mm A')); // '2:30 PM'
console.log(formatDate(date, 'HH:mm:ss')); // '14:30:45'
console.log(formatDate(date, 'MM/dd/yy')); // '01/15/24'
Textal works in all modern browsers and Node.js 16+. For older environments, you may need to include polyfills for:
String.prototype.normalize
(fortoAscii
)Object.fromEntries
(for some utility functions)
MIT © Aidan McAlister
Contributions are welcome! Please read our contributing guide to get started.