TEXT

Word Counter

Real-time word, character, sentence and paragraph counter with reading time estimate.

0
Words
0
Characters (with spaces)
0
Characters (no spaces)
0
Sentences
0
Paragraphs
0 min
Reading Time
0 min
Speaking Time
0
Lines
Compress / Clean Text

About this tool

This free word counter updates instantly as you type. It counts words, characters (with and without spaces), sentences, paragraphs, and lines — and estimates how long your text would take to read or speak aloud.

Reading time is estimated at 200 words per minute, which is average for silent reading. Speaking time uses 130 words per minute, appropriate for presentations or voiceover scripts.

Useful for blog posts, essays, social media captions, cover letters, and anywhere you have a word or character limit to meet.

Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

const textarea = document.getElementById('text-input');
  const clearBtn = document.getElementById('clear-btn');

  const ids = {
    words: 'val-words',
    chars: 'val-chars',
    charsNs: 'val-chars-ns',
    sentences: 'val-sentences',
    paragraphs: 'val-paragraphs',
    read: 'val-read',
    speak: 'val-speak',
    lines: 'val-lines',
  };

  const cardIds = {
    words: 'card-words',
    chars: 'card-chars',
    charsNs: 'card-chars-ns',
    sentences: 'card-sentences',
    paragraphs: 'card-paragraphs',
    read: 'card-read',
    speak: 'card-speak',
    lines: 'card-lines',
  };

  function formatTime(words, wpm) {
    const mins = words / wpm;
    if (mins < 1) return '< 1 min';
    const m = Math.floor(mins);
    const s = Math.round((mins - m) * 60);
    if (m === 0) return `${s}s`;
    if (s === 0) return `${m} min`;
    return `${m}m ${s}s`;
  }

  function flash(cardId) {
    const el = document.getElementById(cardId);
    if (!el) return;
    el.classList.add('flash');
    setTimeout(() => el.classList.remove('flash'), 150);
  }

  function update() {
    const text = textarea.value;

    const words = text.trim() === '' ? 0 : text.trim().split(/\s+/).filter(w => w.length > 0).length;
    const chars = text.length;
    const charsNs = text.replace(/\s/g, '').length;
    const sentences = text.trim() === '' ? 0 : (text.match(/[^.!?]*[.!?]+/g) || []).length;
    const paragraphs = text.trim() === '' ? 0 : text.trim().split(/\n\s*\n/).filter(p => p.trim().length > 0).length;
    const lines = text === '' ? 0 : text.split('\n').length;
    const readTime = formatTime(words, 200);
    const speakTime = formatTime(words, 130);

    const vals = { words, chars, charsNs, sentences, paragraphs, read: readTime, speak: speakTime, lines };

    for (const [key, domId] of Object.entries(ids)) {
      const el = document.getElementById(domId);
      const newVal = String(vals[key]);
      if (el && el.textContent !== newVal) {
        flash(cardIds[key]);
        el.textContent = newVal;
      }
    }

    // auto-resize
    textarea.style.height = 'auto';
    textarea.style.height = Math.max(220, textarea.scrollHeight) + 'px';
  }

  textarea.addEventListener('input', update);

  clearBtn.addEventListener('click', () => {
    textarea.value = '';
    textarea.style.height = '220px';
    update();
    textarea.focus();
  });

  // ── Compress / clean helpers ──────────────────────────────────────
  function applyCompress(fn, btn) {
    const before = textarea.value;
    textarea.value = fn(before);
    if (textarea.value !== before) {
      update();
      btn.classList.add('applied');
      setTimeout(() => btn.classList.remove('applied'), 800);
    }
  }

  document.getElementById('btn-trim-spaces').addEventListener('click', function () {
    applyCompress(t => t.replace(/[^\S\n]+/g, ' '), this);
  });

  document.getElementById('btn-trim-lines').addEventListener('click', function () {
    applyCompress(t => t.split('\n').map(l => l.trim()).join('\n'), this);
  });

  document.getElementById('btn-remove-blank').addEventListener('click', function () {
    applyCompress(t => t.split('\n').filter(l => l.trim()).join('\n'), this);
  });

  document.getElementById('btn-single-line').addEventListener('click', function () {
    applyCompress(t => t.split('\n').map(l => l.trim()).filter(l => l).join(' '), this);
  });

  document.getElementById('btn-compress-all').addEventListener('click', function () {
    applyCompress(t => {
      return t
        .split('\n')
        .map(l => l.trim())
        .filter(l => l)
        .map(l => l.replace(/[^\S\n]+/g, ' '))
        .join('\n');
    }, this);
  });

  update();