TEXT

Reading Time Estimator

Paste any text to instantly estimate reading time, speaking time, complexity, and more.

Reading speed
Slow 150 wpm
Average 200 wpm
Fast 300 wpm
Reading Time
Speaking Time
0
Words
0
Characters
0
Sentences
0
Unique Words
0
Avg Word Length
Longest Word
Complexity Analysis
Avg sentence length: words
Reading level:

About this tool

This free reading time estimator updates live as you type. It calculates how long your text takes to read or speak aloud, and gives you a complexity breakdown based on sentence length.

Reading speeds: Slow readers average ~150 wpm, most adults read at ~200 wpm, and fast readers reach ~300 wpm. Speaking time is fixed at 130 wpm, suitable for presentations and narration.

Reading level is based on average sentence length: under 10 words per sentence is Easy, 10–20 is Medium, and over 20 is Complex.

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 wpmToggle = document.getElementById('wpm-toggle');

  let selectedWpm = 200;

  // WPM toggle
  wpmToggle.addEventListener('click', e => {
    const item = e.target.closest('.toggle-item');
    if (!item) return;
    wpmToggle.querySelectorAll('.toggle-item').forEach(i => i.classList.remove('active'));
    item.classList.add('active');
    selectedWpm = parseInt(item.dataset.wpm, 10);
    update();
  });

  function formatTime(words, wpm) {
    if (words === 0) return '—';
    const mins = words / wpm;
    if (mins < 1) return '< 1 min';
    const h = Math.floor(mins / 60);
    const m = Math.floor(mins % 60);
    if (h > 0) return m > 0 ? `${h} hr ${m} min` : `${h} hr`;
    return `${m} min`;
  }

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

  function setVal(id, cardId, val) {
    const el = document.getElementById(id);
    const newVal = String(val);
    if (el && el.textContent !== newVal) {
      if (cardId) flash(cardId);
      el.textContent = newVal;
    }
  }

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

    // Word list
    const wordList = trimmed === '' ? [] : trimmed.split(/\s+/).filter(w => w.length > 0);
    const words = wordList.length;

    // Chars
    const chars = text.length;

    // Sentences
    const sentenceMatches = trimmed === '' ? [] : (trimmed.match(/[^.!?]*[.!?]+/g) || []);
    const sentences = sentenceMatches.length;

    // Unique words (case-insensitive, strip punctuation)
    const cleanWords = wordList.map(w => w.toLowerCase().replace(/[^a-z0-9']/g, ''));
    const unique = new Set(cleanWords.filter(w => w.length > 0)).size;

    // Avg word length
    const totalChars = cleanWords.reduce((s, w) => s + w.length, 0);
    const avgWordLen = words > 0 ? (totalChars / words).toFixed(1) : '0';

    // Longest word
    const longestWord = words > 0
      ? wordList.reduce((a, b) => (a.replace(/[^a-zA-Z]/g,'').length >= b.replace(/[^a-zA-Z]/g,'').length ? a : b), '')
      : '—';

    // Avg sentence length
    const avgSentLen = sentences > 0 ? (words / sentences).toFixed(1) : '—';

    // Reading level
    let level = 'none';
    if (sentences > 0) {
      const avg = words / sentences;
      if (avg < 10) level = 'easy';
      else if (avg <= 20) level = 'medium';
      else level = 'complex';
    }

    // Times
    const readTime  = formatTime(words, selectedWpm);
    const speakTime = formatTime(words, 130);

    // Update DOM
    setVal('val-readtime',  'card-readtime',  readTime);
    setVal('val-speaktime', 'card-speaktime', speakTime);
    setVal('val-words',     'card-words',     words);
    setVal('val-chars',     'card-chars',     chars);
    setVal('val-sentences', 'card-sentences', sentences);
    setVal('val-unique',    'card-unique',    unique);
    setVal('val-avgword',   'card-avgword',   avgWordLen);

    // Longest word
    const longestEl = document.getElementById('val-longest');
    if (longestEl) {
      const display = longestWord.length > 16 ? longestWord.slice(0, 14) + '…' : longestWord;
      if (longestEl.textContent !== display) longestEl.textContent = display;
      longestEl.title = longestWord !== '—' ? longestWord : '';
    }

    // Complexity
    setVal('val-avgsentlen', null, avgSentLen);
    document.getElementById('badge-easy').classList.add('hidden');
    document.getElementById('badge-medium').classList.add('hidden');
    document.getElementById('badge-complex').classList.add('hidden');
    if (level !== 'none') document.getElementById('badge-' + level).classList.remove('hidden');

    // Chapter estimates
    const chapterCard = document.getElementById('chapter-card');
    if (words > 100) {
      chapterCard.classList.remove('hidden');
      const chapterWords = 2000;
      const chapters = Math.ceil(words / chapterWords);
      const chapterReadTime = formatTime(chapterWords, selectedWpm);
      const lastChapterWords = words % chapterWords || chapterWords;
      const lastChapterTime  = formatTime(lastChapterWords, selectedWpm);

      setVal('val-chapters', null, chapters);
      setVal('val-chapter-readtime', null, chapterReadTime);

      const detail = document.getElementById('chapter-detail');
      if (chapters > 1) {
        detail.textContent = `${chapters - 1} full chapter${chapters - 1 > 1 ? 's' : ''} of ~${chapterWords.toLocaleString()} words + 1 partial chapter of ~${lastChapterWords.toLocaleString()} words (${lastChapterTime} to read).`;
      } else {
        detail.textContent = `Your text fits in a single chapter of ~${words.toLocaleString()} words.`;
      }
    } else {
      chapterCard.classList.add('hidden');
    }

    // Auto-resize textarea
    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();
  });

  update();