SKUs and product codes in Arabic UI
Learn why SKUs and model numbers reorder in Arabic interfaces and how to preserve their identity across product pages, search results and carts.
By Omran Khleifat, Founder of Ritla
Guide14 min read
A cabinet handle has the SKU 580216-CHR, where the suffix identifies its chrome finish. Put that value after an Arabic label without isolation, and the visible code can become CHR-580216. Someone comparing the page with a supplier catalogue now sees a different sequence, even though copying the value may return the original string.
The requirement is specific: an Arabic reader should read the label in Arabic and inspect the SKU in its established character order. A product identifier does not acquire a different group order when the surrounding language changes. Its finish description can be translated; its identity must survive unchanged.
This guide follows that handle through product details, variant selection, search and cart rows. The fix starts with a boundary around the complete code, but a useful implementation also keeps model names separate from quantities and prevents display changes from leaking into product data.
Reproduce the problem with a digit-first SKU#
Start with the component that actually fails:
<p lang="ar" dir="rtl">رمز الصنف: 580216-CHR</p>The intended code is 580216-CHR. In this context, its displayed sequence is CHR-580216. Adding a plain span around the value does not fix it:
<p lang="ar" dir="rtl"> رمز الصنف: <span>580216-CHR</span></p>The span supplies a styling hook, but no directional boundary. The digits, hyphen and Latin letters still participate in the surrounding paragraph's bidirectional processing.
Keep the same label and value, then declare the value's direction:
<p lang="ar" dir="rtl"> رمز الصنف: <span dir="ltr">580216-CHR</span></p>The isolated value displays as 580216-CHR. The Arabic label stays outside the left-to-right span. That placement matters: making the entire paragraph LTR changes the treatment of the label rather than describing the code.
The rendering patterns used here were measured in Chromium 151 on September 24, 2026. Firefox and Safari were not included in those measurements; verify the fixtures in the browsers your product supports.
Do not begin debugging with a letter-first code that already looks right. It conceals the missing boundary. An isolated digit-first fixture provides a visible before-and-after result without changing the product data.
Why one supplier's codes work and another's fail#
The browser does not know which substring is a SKU. It applies the Unicode Bidirectional Algorithm to characters and their context. Latin letters have a strong left-to-right direction; Western digits and separators behave differently. A Latin prefix can keep a sequence together where a numeric prefix does not.
These catalogue fixtures expose the distinction. The unisolated column assumes each value follows an Arabic label within the same RTL paragraph:
| Stored value | Unisolated displayed sequence | What it tests |
|---|---|---|
HANDLE-580216 | HANDLE-580216 | Latin prefix masks the missing boundary |
580216-CHR | CHR-580216 | Numeric prefix with a Latin finish suffix |
580216-0937 | 0937-580216 | Numeric groups separated by a hyphen |
580216 0937 | 0937 580216 | Numeric groups separated by a space |
Hyphenated numeric groups can look correct when displayed alone in a bare RTL cell, yet reorder when an Arabic label precedes them. Spaced numeric groups reorder even in a bare RTL paragraph. That is why a successful test of one table cell does not establish that the same value works in a product summary.
After Arabic text, Unicode rule W2 changes how Western digits are treated during bidi resolution. It does not convert the stored digits into Arabic-Indic characters. The distinction is about display processing, not a translation of the identifier. The underlying algorithm is specified in Unicode Standard Annex #9.
A size range and a SKU may contain the same separator but have different reading requirements. A range can be read from its right-hand starting point in Arabic. The handle's code has a fixed catalogue sequence. Judge it against the supplier's identifier, not against a general rule that every hyphenated value must look English.
Put the direction contract on the value#
For a catalogue whose SKU format is explicitly Latin letters, Western digits and separators, use dir="ltr" on the complete value. Make that part of the component's contract, so every caller gets the same treatment regardless of the current prefix.
The W3C guidance for inline bidi markup recommends tightly wrapping an opposite-direction phrase and declaring its direction. Here, the phrase is the entire SKU. Keep the Arabic label, finish description and explanatory punctuation outside that wrapper.
Do not isolate every hyphen-delimited segment separately. The relation between the numeric family and finish suffix is exactly what needs protection. One boundary should contain both.
When a field genuinely accepts names or codes in several scripts, its direction is unknown. A bdi element can be appropriate:
<p lang="ar" dir="rtl"> رمز المورد: <bdi>580216-CHR</bdi></p>Here the first strong letter is Latin, so automatic direction selects LTR. A default bdi with only Western digits and punctuation also resolves LTR in the measured behavior. However, moving an Arabic label inside the automatic wrapper changes the first strong character and therefore changes the result. Wrap the value alone.
Do not use automatic direction to avoid deciding what your own identifier field contains. A known LTR SKU benefits from an explicit declaration. A multilingual supplier reference needs a documented policy and fixtures covering its permitted scripts. MDN's dir reference explains the available values; bidi isolation in HTML covers the broader markup choices.
This recommendation is scoped to the stated SKU format. Spaced Arabic-Indic digit groups have different bidi behavior and can reorder even inside an LTR isolate. If a supplier permits those strings, test them separately rather than transliterating them or applying a blanket directional override.
Variant changes must update the same identity#
A finish selector adds another failure that can resemble a bidi bug: the visible finish changes, but the SKU still belongs to the previous variant. Isolation cannot repair stale product state.
For the handle, chrome and bronze are distinct sellable variants. Their translated labels belong to the interface; their SKU suffixes belong to the catalogue. Keep them together in the variant record:
<div lang="ar" dir="rtl"> <p>التشطيب: <span id="variant-finish"></span></p> <p>رمز الصنف: <span id="variant-sku" dir="ltr"></span></p></div>Run this JavaScript after that markup:
const variants = new Map([ ["chrome", { finish: "كروم مصقول", sku: "580216-CHR" }], ["bronze", { finish: "برونز داكن", sku: "580216-BRNZ" }]]); function renderVariant(key) { const variant = variants.get(key); if (!variant) throw new Error("Unknown handle variant"); document.getElementById("variant-finish").textContent = variant.finish; document.getElementById("variant-sku").textContent = variant.sku; return variant.sku;} renderVariant("chrome");This example renders an already-selected variant. Your selector must call it with the selected key, and your purchase action must use that same selected record. The returned string demonstrates the association; it does not implement a cart.
The code's directional wrapper stays in place while its text changes. Setting the textContent property inserts the value as text rather than interpreting supplier content as markup. Use your framework's escaped text binding for the equivalent operation.
For QA, switch to bronze, inspect the suffix, add the item, and compare the cart's variant identity with the selected record. Switch back and repeat. A reversed suffix on the right variant is a rendering failure. A correctly displayed suffix on the wrong variant is a selection failure. Record those separately so the fix addresses the responsible layer.
Handle loading and unavailable variants explicitly#
Some catalogues fetch the SKU after a finish changes. During that interval, keeping the previous code under the new finish label presents a false association. Give the pending selection an explicit loading state, and enable the purchase action only when the selected variant has the identity it requires.
Two rapid changes create a useful test. Select bronze, immediately select chrome, then arrange for the bronze response to arrive last. The final finish and SKU must still represent chrome. Tie each response to the current selection before applying it. Directional markup can be completely correct while an older response overwrites the right value.
Unavailable is another distinct state. A variant with no available inventory may still have a valid SKU, so keeping that identifier visible can help with a stock enquiry. A finish combination that has no catalogue record has no SKU to display. Do not substitute the parent product's model number and label it as the variant's code.
Keep Arabic state messages outside the LTR code node. If the code is missing, hide the value or leave it empty and render the explanation in a separate element that follows the interface direction. Putting an Arabic loading message into a permanently LTR SKU span mixes two content contracts and makes the component harder to reason about.
Test these states before the initial request completes, after a failed request and after retrying. Check both what the user can see and which record the purchase action would use. A disabled action with a stale visible SKU still misinforms someone copying the code for a supplier enquiry. A correct visible SKU with an outdated purchase record is a separate failure. Both associations must follow the same selected variant.
A model number can belong to the name#
A product title, a manufacturer model and a sellable SKU are different fields. A model can legitimately contain a number as part of its name. A quantity next to that name is a separate value.
Consider a handle sold under the model name FerroLine 64. The number belongs inside the model boundary. A following quantity does not:
Problem
<p lang="ar" dir="rtl">أضف FerroLine 64 6 قطع إلى السلة</p>The quantity can join the preceding Latin run and become visually associated with the model name, away from the Arabic noun it counts. The intended reading is a model name followed by a quantity in the Arabic sentence. Treat those as separate units:
<p lang="ar" dir="rtl"> أضف <bdi>FerroLine 64</bdi> 6 قطع إلى السلة</p>The model and its intrinsic number share one isolate; the quantity remains in the surrounding sentence. Do not wrap the model and quantity together merely because both contain digits. That preserves the wrong semantic boundary.
For an application-controlled model format known to be LTR, an explicitly LTR span is also suitable. For names supplied in different languages, automatic isolation lets the name establish its own direction. If a model contains nested Arabic and Latin segments, inspect that exact string rather than assuming every model follows this example's shape.
The separation also matters when a catalogue is localized. Translate a descriptive title where appropriate, retain the actual model designation, and keep the sellable variant's SKU in its own field. A search result may need all three. Using the SKU as the product title makes translation awkward; using the translated title as the identifier makes exact lookup unreliable.
If product copy must compose a full Arabic sentence around the model and quantity, have that sentence reviewed for grammar too. A bidi boundary fixes visual association, but it cannot supply the appropriate Arabic quantity wording.
Keep code direction local in carts and comparison tables#
A cart row still belongs to an Arabic interface. Making the whole row LTR to protect its SKU also affects the surrounding Arabic content. Keep the direction declaration on the identifier itself.
A finish comparison table can preserve Arabic column order and descriptions while protecting the codes:
<table lang="ar" dir="rtl"> <caption>تشطيبات المقبض</caption> <thead> <tr><th scope="col">التشطيب</th><th scope="col">رمز الصنف</th></tr> </thead> <tbody> <tr> <th scope="row">كروم مصقول</th> <td><span dir="ltr">580216-CHR</span></td> </tr> <tr> <th scope="row">برونز داكن</th> <td><span dir="ltr">580216-BRNZ</span></td> </tr> </tbody></table>The code span does not change the table's direction. It describes one value within a cell. Reuse that same value component in the product details and cart rather than relying on whatever direction each parent happens to provide.
Responsive layouts need another check. A desktop table may show the value alone; a mobile card may insert an Arabic label into the same paragraph. That context change can expose a numeric-group code that appeared correct on desktop. Preserve the wrapper when converting between presentations.
Also inspect alternate cart surfaces: an expanded item row, a compact confirmation panel and a saved-items list may use different templates. Check that they retain the same identifier boundary and selected finish. Testing the main product page alone leaves those independent render paths unverified.
Search inputs and highlighted results need separate decisions#
A dedicated SKU lookup field has a known input contract. Use a text field with an explicit LTR direction, while leaving its Arabic label in the RTL form:
<form lang="ar" dir="rtl"> <label for="sku-lookup">ابحث برمز الصنف</label> <input id="sku-lookup" name="sku" type="text" dir="ltr" spellcheck="false" autocapitalize="off"> <button type="submit">بحث</button></form>The field is text because the identifier can contain letters, separators and significant leading zeroes. A numeric field is the wrong data model. Do not assume type="search" would establish LTR direction: search inputs inherit the page direction unless they declare otherwise.
A general catalogue search has a different contract. It may accept an Arabic product description as well as a SKU, so automatic direction is a reasonable choice there. Test an Arabic query, a Latin model name, a digit-first SKU and the empty field. Automatic direction cannot infer the intended grammar of a product identifier; it selects a base direction from the entered content. Use a persistent label rather than depending on placeholder placement to explain the field.
Search highlighting must retain the boundary around the entire result code:
<p lang="ar" dir="rtl"> رمز الصنف: <span dir="ltr">580216-<mark>CHR</mark></span></p>Highlighting the finish suffix does not turn it into a separate identifier. Avoid putting an isolate only around the highlighted substring while leaving the numeric prefix exposed to the Arabic paragraph. Confirm that removing the highlight gives exactly the original SKU text.
Keep matching policy separate from display. If lookup ignores case or accepts alternative separators, specify that as search behavior and test it against the catalogue. Do not silently rewrite the displayed identifier to resemble a normalized query. The result should still show the exact code associated with the selected variant.
Preserve the catalogue string through formatting and copying#
Store and transport the SKU as a string. Do not pass its numeric-looking portions through number formatting, convert their digit script for the Arabic locale, or drop leading zeroes while building a display value. Those operations change an identifier rather than localizing a quantity.
Keep translated attribute names separate from code suffixes. The chrome finish label can be Arabic, but replacing CHR inside the actual SKU with that translation creates a new string that the supplier may not recognize. If customers benefit from an explanation of the suffix, show it as explanatory text beside the unchanged code.
Reversing characters before rendering is particularly fragile. The browser still applies bidi processing afterward, and the modified value can escape into search, copy actions or purchase data. Reversing groups is no safer: it bakes one paragraph's visual accident into the catalogue identity.
For an HTML interface, retain direction in markup rather than adding invisible directional characters to the stored SKU. Otherwise two visually similar values can differ as strings. If imported data already contains hidden characters, inspect the character sequence and resolve it under the catalogue's validation policy. Do not run a broad character-removal routine over arbitrary supplier identifiers without understanding their permitted format.
A copy action should read the canonical SKU from the selected variant record. Do not reconstruct it from a whole card's text, which may also contain the model, finish and quantity. Manual selection is still worth testing, but copying an unisolated value can return the correct logical string even while its display is wrong. Copy success therefore proves a different property from visual correctness.
Long codes need a visibility policy#
Isolation controls bidi context. It does not guarantee that the complete code fits in a narrow cart row. A correctly ordered but clipped finish suffix can still prevent someone from distinguishing the chrome variant from bronze.
Decide whether the interface permits a code to wrap. For a detail panel where the entire identifier should remain available, this is a reasonable starting point:
.product-code { overflow-wrap: anywhere;}Apply the class to the same isolated value element. MDN documents overflow-wrap as a way to allow breaks in otherwise unbreakable strings. It does not insert a separator into the stored SKU.
For a compact comparison row, keeping the code on one line may make scanning easier, but reserve enough space or provide an explicit way to reveal the full value. An ellipsis is a presentation choice with a cost: the hidden suffix may carry the variant distinction. Do not depend solely on a hover tooltip for access to that missing information.
Test wrapping with the actual font, container and supported zoom settings. On a multi-line value, verify that the reading sequence remains understandable and that copying returns the exact catalogue string. Avoid inserting manual hyphens or spaces to force a break. Those characters can become indistinguishable from meaningful separators when someone transcribes the code.
If the problem appears only in a narrow flex or grid child, inspect its available width and minimum sizing before changing direction. Reordering and clipping can occur together, but they need different fixes.
Diagnose identity, direction and visibility independently#
When a report says a product code is reversed, first compare three things: the selected variant record, the element's text and the visible sequence. This separates a data problem from bidi rendering and from a partially hidden value.
For the variant example, this browser-console inspection reads the text and relevant computed styles:
const code = document.getElementById("variant-sku");if (!code) throw new Error("Variant SKU element is missing"); console.table({ text: code.textContent, dir: code.getAttribute("dir"), direction: getComputedStyle(code).direction, unicodeBidi: getComputedStyle(code).unicodeBidi});The text should equal the selected record's SKU. Inspect the actual value node, not just its paragraph: an RTL paragraph containing an LTR identifier is intentional. If the wrapper looks correct but the display still fails, inspect custom CSS that changes direction or unicode-bidi, then reproduce the value without those overrides.
Next compare an Arabic-labelled paragraph with a bare value cell. If only the labelled version fails, that difference is evidence about the bidi context. It is not evidence that adding labels is inherently unsafe. Restore the complete identifier boundary in both presentations.
If the text itself differs from the selected record, stop adjusting CSS. Trace variant selection, response mapping and any display formatter. If the text matches but only part is visible, examine clipping, wrapping and neighbouring controls. If the full code is visible in a different sequence, investigate directional boundaries.
Keep a screenshot alongside the canonical fixture string when recording the failure. DOM text assertions and accessibility-tree inspection read logical order and do not establish visual order. Screen reader checks remain useful for labels and product association, but they cannot replace looking at the code.
Build a release fixture around the product family#
Use a small catalogue fixture with explicit expected identities, then exercise it through the real customer journey. Include both finish variants, a Latin-prefix code that already displays correctly, a hyphenated numeric code and a spaced numeric code. The passing Latin-prefix case guards against accidental data changes; the others expose missing context boundaries.
For each supported browser, perform these checks:
- Open the Arabic product page and compare the visible SKU with the canonical record, including the finish suffix.
- Change the finish repeatedly and confirm the label, SKU and cart item all follow the selected variant.
- Compare the product model with a separate quantity, making sure the quantity reads with its Arabic noun.
- Search by the full SKU, inspect the highlighted result and open the matching variant.
- Review the narrow cart layout and full value at supported zoom levels, including wrapping or reveal behavior.
- Copy the code into an exact lookup and compare the resulting string, separately from the visual check.
Repeat the identity checks on the LTR route. A shared component should preserve the same identifier in both languages. Include an update after initial rendering, because server-rendered markup can be correct while a later variant update replaces the protected element with plain text.
Treat screenshots, data assertions and interaction checks as complementary evidence. A screenshot can show a reordered suffix but cannot establish what was submitted. A payload assertion can establish the selected SKU but cannot show whether the shopper saw it correctly. The fixture should establish both.
For Latin SKU tokens inside Arabic text, a free Ritla scan can report : Unisolated bidi hazards: phones, emails, Latin tokens inside Arabic text. Numeric-only codes, variant identity and exact visual order still need the targeted tests above. Continue with bidi isolation in HTML for component boundaries or Arabic text overflow when the correct SKU is not fully visible.