Skip to content
KurdFonts
All guides

Kurdish type on the web

Kurdish text on the web fails in specific, repeatable ways: a wrong language tag, a subset that dropped the joining forms, a stylesheet written in left and right, a URL that jumps to the other end of the sentence. None of it is exotic. This is the order we would set a page up in, and what to check at each step.

Set lang and dir before anything else

The html element needs two attributes, and they do different jobs. dir="rtl" sets the base direction, which is what makes the paragraph start at the right edge and puts punctuation where it belongs. lang="ckb" says which language the text is in, and the browser passes that to the text shaper along with the font.

That second attribute is not cosmetic. The language tag selects an OpenType language system inside the font, and a language system replaces the default feature list rather than adding to it — so a font can join perfectly in Arabic and fall apart into isolated letters the moment the same text is tagged as Kurdish. Our guide on the letters that break explains that trap in full; the point here is that lang="ckb" is what triggers it, so it is also what you have to test with.

Mark the exceptions inline. A Kurmanji pull-quote inside a Kurdish page gets lang="kmr" dir="ltr" on its own element, an English code sample gets lang="en", and each is then shaped and font-matched as the language it actually is. Setting the language once on html and never again is the usual reason a mixed page looks right in one paragraph and wrong in the next.

Load one face, in WOFF2, and preload it

Serve WOFF2 and nothing else. Every browser that can run a modern site supports it, and the older formats add build steps and bytes and nothing else. Expect the files to be larger than the Latin fonts you are used to: an Arabic-script face carries several glyphs per letter, plus the GSUB tables that choose between them.

font-display decides what the reader sees while the file loads. swap shows the fallback first and replaces it, which is the safe default in Latin; in Arabic script the fallback may not have ڕ ڵ ۆ ێ ە at all, so the first paint can be full of boxes that then vanish. If that bothers you more than a delay does, font-display: optional or a short block period is the trade you want, and it should be a decision rather than an inherited auto.

Preload only the face that is actually above the fold — usually one weight of the body font. Use a link rel=preload with as="font", type="font/woff2" and the crossorigin attribute, which is required even when the file sits on your own origin; without it the browser fetches the file twice. Preloading every weight in the family makes them compete with the HTML, and the first paint gets slower, not faster.

Subsetting: the joining forms are not optional

Subsetting strips the glyphs a page does not use, and in Latin it is close to free. Arabic script breaks the assumption it rests on. The initial, medial and final forms of a letter are never characters in your source text — the font produces them at shaping time from one character — so a subsetter that keeps only the codepoints it found in your content throws all of them away.

The result is a font that passes every file-size check and renders every word as a row of separate letters. If you subset, use a tool that computes the glyph closure over the layout tables and keeps the features — pyftsubset with the layout features retained does this — and never hand a subsetter a bare list of characters. Dropping GSUB from an Arabic-script font is the same mistake wearing different clothes.

Splitting a face across unicode-range files carries a related hazard: put the Kurdish-only letters in one file and the shared Arabic letters in another, and a single word can need two fonts, which no shaper will join across. Test the subset you are shipping, in a browser, on a page tagged lang="ckb" — not the original file on your desktop. When you are unsure, ship the whole face; a font that is bigger than it needed to be still reads.

Write the CSS once, in logical properties

Use margin-inline-start instead of margin-left, padding-inline instead of padding-left and padding-right, text-align: start instead of text-align: left, and inset-inline-start instead of left. Each one resolves against the element's direction, so the same declaration means "the reading-start side" in Kurdish and in English.

This gives you one stylesheet for both directions instead of a base sheet plus a mirrored RTL override. The override approach works on the day you write it and drifts afterwards, because every new physical rule is a bug that only shows up in the direction you were not looking at. We build this site RTL-first — the default locale is Kurdish — and LTR is the mirrored case, which is the opposite of how most stylesheets are written.

Some things are genuinely physical and should stay that way. A box-shadow offset, a background-position, a transform and a play icon do not flip; a next arrow, a back chevron and a progress bar do. Decide each one on its own rather than converting everything with search and replace, then look at both directions in a browser.

Fallbacks, numerals and the URL that jumps

Your font-family stack ends somewhere, and the end of it matters more in Kurdish than in English. If the browser falls through to a system font that has the Arabic alphabet but not ڕ ڵ ۆ ێ ە, those five letters get pulled from yet another font, at another weight and another proportion, in the middle of otherwise fine words. Name a fallback you have checked for the Kurdish letters yourself, and check it on the platforms your readers use rather than the one you develop on.

Latin fragments inside Kurdish text — a brand name, a version number, a URL, a phone number — are reordered by the bidi algorithm relative to what surrounds them. The classic symptom is a trailing bracket, slash or full stop that leaps to the wrong end of the line, or a URL that reads correctly but sits in the wrong place in the sentence. The text is not corrupted; it is being laid out by a rule you never told it to stop applying.

Isolate the run and it stops. In HTML, wrap it in a bdi element, or set unicode-bidi: isolate on the element that holds it; in plain text, where you cannot add markup, the isolate characters U+2066 to U+2069 do the same job. Use direction and dir only for content that really is in the other direction, and test with a real URL and a real number inside a real sentence, because this is a class of bug that never appears in placeholder text.