quran-madina-html
v1.0.1
Published
A Madina Quran HTML Renderer without images.
Readme
Quran Madina Html (no-images)
A Quran renderer for the browser that looks like the printed Madina Mushaf — without a single image. Every glyph is real, selectable, copy-pasteable text laid out with pure HTML/CSS, wrapped in one custom element:
<quran-madina-html sura="2" aya="255"></quran-madina-html>See it live → (view the page source too —
GitHub strips <script> tags from this README, so the tag can't render here, but it's just as simple as
the snippet above once it's on your own page.)
Why
- Pixel-faithful to the printed Madina Mushaf — same line breaks, same justification, same page layout.
- Pure HTML/CSS, no images — text stays selectable, searchable, and copy-pasteable; pages load fast.
- One HTML tag — no build step, no framework required.
- Any font — Hafs, Uthman, Amiri Quran (plain or tajweed-colored), or your own.
- Word-level addressing — render any word range, even across ayas, pages, and suras.
See it in action
Page 106, rendered by the library next to a scan of the actual printed page:
One engine, any font
The same JSON-driven layout engine re-flows for whichever font you point it at —
data-font="Hafs" (default), Uthman, Amiri Quran, Amiri Quran Colored (tajweed rules
color-coded), or the bundled me_quran.
Verse ranges — framed or frameless
sura/aya renders just the ayat you ask for, laid out exactly as they sit on the Madina page.
By default you get a header (sura name + copy/translate icons); add headless="true" to strip
that chrome and keep only the Quran text:
<quran-madina-html sura="5" aya="1-2">
</quran-madina-html><quran-madina-html sura="5" aya="1-2"
headless="true">
</quran-madina-html>Word-level selection
The words attribute picks out a word range — 1-based, inclusive — and hides the rest of the
text in place, so the original Madina line justification is preserved instead of being
reflowed. Counting continues past the end of the given aya, so a selection can cross aya, page,
and even sura boundaries:
<quran-madina-html sura="1" aya="1"
words="1:2">
</quran-madina-html>first two words of Al-Fatiha; the dashed outline marks the hidden remainder of the line
<quran-madina-html sura="1" aya="7"
words="1-14">
</quran-madina-html>words 1-14 counted from Al-Fatiha's last aya spill into Surat Al-Baqara — its basmala counts as words 10-13
Highlighting words
highlight (soft yellow) and error (soft red) mark a word range — same 1-based, inclusive
format as words — on top of whatever is being shown. Unlike words, they work with or without
a words selection, and even on a full page render:
<quran-madina-html sura="1" aya="1" highlight="2-3"></quran-madina-html>Getting Started
Installation
Install via npm:
npm install quran-madina-htmlOr include directly via CDN script tag in your HTML <head>:
<script type="text/javascript" src="https://unpkg.com/quran-madina-html"></script>- Supported
data-nameparameters are: Madina05 (default), others are under development (Shemerly, Qaloon, Newer Madina) - Supported
data-fontparameters are: Hafs (default), Uthman, Amiri Quran, Amiri Quran Colored - Other options include:
data-font-sizewhich defaults to 16 (px). Any size from 6 to 100 works: 16 and 24 have pre-built databases, and every other size is fitted at load time by linear interpolation between those two anchors (page/line width interpolated, letter stretching re-scaled accordingly)
Then in your body, just add the tag.
<quran-madina-html sura="2" aya="8-10"></quran-madina-html>If the selected aya(s) fit on a single line, the default is to generate an inline <span> element, otherwise a <div> is generated.
You can also restrict the output to a specific word sequence with the words attribute (1-based, inclusive; start-end, start:end or a single index). Word indices are counted starting from the given sura/aya and may run past it, so the rendered selection can span multiple ayas — and even cross page and sura boundaries. The basmala counts as 4 real, individually selectable words — matching the flat Tanzil word indexing most consumers count against (At-Tawba has none; Al-Fatiha's basmala is its real aya 1). This holds both when the selection crosses into a new sura and when it is anchored at a sura's aya 1 (word 1 is then بسم and word 5 the aya's first word), while the sura title stays uncounted decoration, shown for context only. Display follows the printed Mushaf: when all 4 basmala words fall inside the selection (or on a full page render) they show as the traditional ﷽ ligature; a partial selection (1–3 of the 4) renders the individual words. Either way the basmala occupies 4 word indices. The non-selected words are kept in place so the original Madina line layout is preserved — only the chosen words are shown. Aya-number markers are not counted.
Changed in 0.9.0: the basmala used to be a never counted decoration; a cross-sura
wordsindex now lands 4 words later than in 0.8.x.Changed in 0.9.3: a
wordswalk anchored at a sura's aya 1 now counts that sura's own basmala too (0.9.0–0.9.2 only counted crossed-into basmalas, so aya-1-anchored word indices landed 4 words early relative to the Tanzil flat indexing).Changed in 0.9.5: a
wordswalk that runs past the end of An-Nas now wraps around to Al-Fatiha and keeps counting (previously it stopped, silently truncating the selection); Al-Fatiha's title stays uncounted decoration, and its basmala — being its real aya 1 — renders as normal aya words.
<quran-madina-html sura="1" aya="1" words="1:2"></quran-madina-html> <!-- first two words only -->
<quran-madina-html sura="1" aya="1" words="3-10"></quran-madina-html> <!-- words 3..10, spanning ayas 1-3 -->
<quran-madina-html sura="1" aya="7" words="1-14"></quran-madina-html> <!-- spans Al-Fatiha into Al-Baqara:
9 words + 4 basmala words + الٓمٓ -->The words attribute is ignored when rendering a full page.
To embed a selection inside your own chrome (e.g. a quiz that already labels the sura/aya), the notitle attribute hides the crossed-into sura's name text while keeping its decorated title line (the ornamental frame stays, empty) — and the basmala, being real counted words, still renders:
<quran-madina-html sura="1" aya="7" words="1-14" notitle="true"></quran-madina-html>Highlighting and marking words
Two more range attributes lay a visual mark over a sub-range of whatever is being shown —
highlight (soft yellow) and error (soft red) — using the same 1-based, inclusive range
format as words (start-end, start:end, or a single index). Unlike words, they
work in every render mode: alongside a words selection, on a plain sura/aya verse
(counted from that aya's first word), and even on a full page render (counted from the
page's first aya — a page never starts mid-aya, so this is always well-defined). A range must
fall entirely within whatever is actually displayed (the words selection if one is set,
otherwise the whole verse/page); an out-of-range or malformed value is ignored with a console
warning rather than breaking the render. When highlight and error overlap on the same
word, error wins.
<quran-madina-html sura="1" aya="7" words="1-14" highlight="10-13" error="1-2"></quran-madina-html>
<quran-madina-html sura="2" aya="255" highlight="5-8"></quran-madina-html> <!-- no words needed -->
<quran-madina-html page="2" error="1-4"></quran-madina-html> <!-- counted from the page's first aya -->Colors come from the --qmh-highlight/--qmh-error CSS custom properties on
quran-madina-html (defaults: soft yellow/soft red) — override them, or the
.quran-madina-html-word-highlight/.quran-madina-html-word-error classes directly, from
your own stylesheet.
Another option exists to render a complete quran page:
<quran-madina-html page="106"></quran-madina-html>By default a multiline render gets a header (sura name + copy/translate icons); clicking any aya (inline or multiline) opens a small copy/translate popup scoped to it. Set headless="true" to drop the header and render only the Quran text:
<quran-madina-html sura="2" aya="8-10" headless="true"></quran-madina-html> <!-- no header -->Attributes Reference
| Attribute | Type | Default | Description |
|---|---|---|---|
| sura | number \| string | - | Sura number (1–114). Required when using aya. |
| aya | number \| string | - | Single aya or verse range (e.g., 255, "8-10"). |
| page | number \| string | - | Full Mushaf page number (1–604). Mutually exclusive with sura/aya. |
| words | string | - | 1-based word index or range (e.g., "1:2", "3-10", or single index "5"). |
| highlight | string | - | 1-based word range to mark soft yellow (same format as words). Works with or without words, and with page too. |
| error | string | - | 1-based word range to mark soft red (same format as words). Wins over highlight on overlap. |
| headless | boolean \| string | false | Set to "true" to remove top sura header chrome and copy/translate icons. |
| notitle | boolean \| string | false | Set to "true" to hide sura name text while retaining title frame decoration. |
| data-font | string | "Hafs" | Font family: Hafs, Uthman, Amiri Quran, Amiri Quran Colored, me_quran. |
| data-name | string | "Madina05" | Layout database dataset name. |
| data-font-size | number \| string | 16 | Font size in px (6–100; interpolated between 16 and 24). |
React wrapper
For React apps (web, or mobile via a Capacitor WebView), use
@tarekeldeeb/quran-madina-react,
a thin component wrapping the <quran-madina-html> tag — lives in this repo's react/
folder and is released in lockstep with this package.
npm install @tarekeldeeb/quran-madina-react reactimport QuranMadinaHtml from "@tarekeldeeb/quran-madina-react";
<QuranMadinaHtml page={106} font="Hafs" />See react/README.md for the full prop reference and the Capacitor
(iOS/Android) packaging guide.
Dev Setup
The project is published on npm npm install quran-madina-html, with sources, assets and distributables.
Alternatively, you can fork this repo, then clone it.
apt install python3-distutils nodejs npm chromium-driver
npm install // install components and scripts
npm run build-db // build all json Db files
npm run release // build the dist with dependencies
Demo
https://tarekeldeeb.github.io/quran-madina-html/demo/index.html
Don't forget to see the page source!
