Display Qur'an text

Read by surah and ayah, and render what you read the way the printed muṣḥaf does: in its own font, right to left, with the pause marks and the ayah numbers in place. The whole thing is one import and about twenty lines. For the printed page as artwork, see Display a complete Muṣḥaf page.

Blocks used
Platforms
Browser · Node · React Native
Time
About 10 minutes

By the end, al-Fātiḥa is on the page as seven separately addressable ayahs, set in the King Fahd Complex Ḥafṣ font, reading right to left, with each ayah's number drawn the way the muṣḥaf draws it.

  1. Install, and load Ḥafṣ

    The JavaScript package is named quran-text, the name in its package.json. Ḥafṣ, the riwayah most of the world prints, is bundled with it, so the first call needs no file and no network.

    npm i quran-text
    
    import { Mushaf } from "quran-text";
    
    const m = await Mushaf.hafs();
    
    m.key;         // "hafs"
    m.ayahCount;   // 6236 — read it, never write it
    m.pageCount;   // 604
    m.wordCount;   // 77432
    
    Publishing is decided, not yet done

    At the commit this guide was read against, quran-text is not on npm. Until it is, install from the repository’s lib/js directory — the manifest above is the one that will be published, so nothing in the code changes.

  2. Read one ayah

    m.ayah(surah, n) takes a reference and returns the ayah as a span of words. n is this edition's own number; see the trap at the end of this step.

    const a = m.ayah(1, 4);
    
    a.key;    // "1:4"
    a.text;   // "مَٰلِكِ يَوۡمِ ٱلدِّينِ"   — the words, joined by spaces, no marks
    a.words;  // ["مَٰلِكِ", "يَوۡمِ", "ٱلدِّينِ"]
    a.render({ marks: true, ayahMarks: true });
              // "مَٰلِكِ يَوۡمِ ٱلدِّينِ ۝٤"  — with pause marks and the ayah number
    
    مَٰلِكِ يَوۡمِ ٱلدِّينِ ۝٤
    1:4 · Ḥafṣ · a.render({ marks: true, ayahMarks: true })

    text and render() are different strings. text is the words and nothing else. render({ marks: true }) adds the waqf marks the muṣḥaf prints above the line, and ayahMarks: true appends the end-of-ayah sign with its number in Arabic-Indic digits — the ۝٤ above. For a reading view you want the rendered form; for search, matching or storage you want text.

    Out-of-range numbers throw, and the message says what the real count is:

    m.ayah(1, 8);    // RangeError: Al-Fātiḥah has 7 āyāt in Ḥafṣ, not 8
    m.ayah(2, 287);  // RangeError: Al-Baqarah has 286 āyāt in Ḥafṣ, not 287
    
    An ayah number without its riwayah is not a reference

    The 255th ayah of al-Baqarah in Ḥafṣ is āyat al-Kursī. The 255th ayah of al-Baqarah in the Warsh edition is a different passage, because Warsh divides the surah into 285 ayat, not 286, and the boundaries before it fall elsewhere. warsh.ayah(2, 255) returns real Qur’anic text, just not the text you meant. Every number you accept from a user or a database belongs to one riwayah; Support multiple riwayat covers carrying that fact around.

  3. Set the font — this is not optional

    Each muṣḥaf file names the font its text was set in, and the Ḥafṣ package bundles the file. m.fontFace() returns a ready @font-face rule; m.font.family is the name to use in CSS.

    m.font.family;   // "KFGQPC HAFS Uthmanic Script"
    m.font.file;     // "UthmanicHafs-v-3.0.ttf"
    m.fontFace();    // '@font-face { font-family: "KFGQPC HAFS Uthmanic Script"; src: url("…/data/UthmanicHafs-v-3.0.ttf") format("truetype"); }'
    

    The URL inside fontFace() is resolved relative to the module, so a bundler that copies the package's data/ directory serves it as-is. If yours does not, host the .ttf yourself and pass its URL: m.fontFace("/fonts/UthmanicHafs-v-3.0.ttf").

    Blank or boxed text is a font problem, not missing data

    Qur’anic text uses code points that ordinary Arabic fonts do not draw — small signs above and below the line, and for Warsh, Qālūn and al-Sūsī a block of letters Unicode only added in 2021. In the wrong font those render as empty boxes or as nothing at all, while text.length and a code-point dump show the string is intact. Ship the font the file names: one per edition, not one for all seven.

    One detail: fontFace(url) always writes format("truetype"), whatever URL you pass. Point it at a .ttf. If you convert the font to WOFF2 for the web, write the @font-face rule yourself with format("woff2") and take only the family name from m.font.family.

  4. Direction and language on the container

    Arabic runs right to left, and the browser needs to be told twice: dir for layout, lang for font selection and hyphenation. Put both on the element that holds the text, and keep numbers and Latin labels outside it or in their own isolated element.

    <div id="quran" dir="rtl" lang="ar"></div>
    
    #quran {
      font-family: "KFGQPC HAFS Uthmanic Script";
      font-size: 28px;
      line-height: 2;          /* the marks above and below the line need room */
      unicode-bidi: isolate;   /* so a "1:4" label beside it cannot reorder it */
    }
    

    A ḥarakah is a separate code point that renders on the letter before it, so never truncate an ayah at a character index for a preview, and never split one for a two-column layout. Cut between words (a.words gives you them) or show the whole ayah.

  5. Render a whole surah, one element per ayah

    A surah is a span too, and it hands you its ayahs in order. Give each one its own element with the reference on it, and you have something that can be highlighted, linked and scrolled to later without any string matching.

    const root = document.querySelector("#quran");
    
    for (const ayah of m.surah(112).ayahs) {
      const el = document.createElement("span");
      el.dataset.key = ayah.key;                    // "112:1"
      el.textContent = ayah.render({ marks: true, ayahMarks: true }) + " ";
      root.append(el);
    }
    // قُلۡ هُوَ ٱللَّهُ أَحَدٌ ۝١  ٱللَّهُ ٱلصَّمَدُ ۝٢  لَمۡ يَلِدۡ وَلَمۡ يُولَدۡ ۝٣  وَلَمۡ يَكُن لَّهُۥ كُفُوًا أَحَدُۢ ۝٤
    

    If instead you want the printed page's own line breaks, m.page(n).render({ lines: true }) returns one line per printed line — page 1 of Ḥafṣ is seven lines. Lines are reconstructed, not read from the source, so treat them as a layout aid rather than a citable fact.

  6. Draw the basmalah yourself

    In the Ḥafṣ file the basmalah is in words only where it is numbered — as ayah 1:1. For surahs 2 to 114 it is not in the array: m.words.length equals m.wordCount, 77,432, and m.surah(2).words begins at الٓمٓ. The file tells you whether to print one and leaves the printing to you.

    m.surah(2).hasBasmalah;   // true   — draw it above 2:1
    m.surah(9).hasBasmalah;   // false  — never draw one here
    m.ayah(1, 1).text;        // "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ" — the text to draw
    

    In the editions that do not count it (Warsh, Qālūn, al-Dūrī, al-Sūsī) the four words sit before 1:1 as m.surah(1).basmalah, an unnumbered span, and 1:1 is al-ḥamdu lillāh. Either way the rule is the same: hasBasmalah decides, and surah 9 is always false.

  7. Put it together

    The complete module. It renders al-Fātiḥa into #quran with the font, the direction and the per-ayah elements from the steps above.

    import { Mushaf } from "quran-text";
    
    const m = await Mushaf.hafs();
    
    // The font, once.
    const style = document.createElement("style");
    style.textContent = m.fontFace();
    document.head.append(style);
    
    // The container: direction and language belong on it, not on the page.
    const root = document.querySelector("#quran");
    root.dir = "rtl";
    root.lang = "ar";
    root.style.fontFamily = `"${m.font.family}"`;
    
    // One element per ayah, keyed by reference.
    const surah = m.surah(1);
    for (const ayah of surah.ayahs) {
      const el = document.createElement("span");
      el.dataset.key = ayah.key;
      el.textContent = ayah.render({ marks: true, ayahMarks: true }) + " ";
      root.append(el);
    }
    

    Seven spans, data-key="1:1" to "1:7", and the text is the package's own.

Try it live

Look any ayah up by reference and see its text and word numbers, in any of the seven riwayat, from the real dataset.

Loading another riwayah

Mushaf.hafs() is the only edition that ships inside the package. The other six are files in the dataset's data/mushaf/ directory, 1.9–3.1 MB each, and load with Mushaf.fromJson in a browser or Mushaf.load in Node:

const warsh = Mushaf.fromJson(await (await fetch("/data/mushaf/warsh.json")).json());

warsh.ayahCount;              // 6214 — not 6236
warsh.font.family;            // "KFGQPC Warsh Uthmanic Script" — a different font
warsh.fontFace("/fonts/UthmanicWarsh-v-3.0.ttf");

Everything that changes with the riwayah (the count, the font, the boundaries, the spelling) is in Support multiple riwayat.

You wantUse instead
The printed page, exactly as typesetDisplay a complete Muṣḥaf page
Colour by recitation ruleAdd Tajwīd highlighting — its offsets are not into this text
A tappable word on a printed pageMake words clickable
Ḥafṣ numbers translated for another readingMap ayah references between counts

How this data is made

The text is extracted unedited from the packages the King Fahd Glorious Qurʾān Printing Complex publishes for each edition; every file names its source package and that package's SHA-256 in its provenance block, and the build fails if a letter moves. Word boundaries and the shared word numbering are derived, and checked by round-tripping every letter of every edition.