وب مدرن، رابط کاربری و تجربهی وب · فصل ۲ — HTML، CSS، Responsive و RTL · 40
Unicode, RTL, and Bidi in a Persian Product
Implement a bounded Persian interface that separates document/base direction, inline bidi isolation and layout behavior, then verify mixed-direction strings and interaction order with explicit evidence. میتواند «Unicode, RTL و Bidi در محصول فارسی» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
Lesson summary
Implement a bounded Persian interface that separates document/base direction, inline bidi isolation and layout behavior, then verify mixed-direction strings and interaction order with explicit evidence.
میتواند «Unicode, RTL و Bidi در محصول فارسی» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
This lesson produces
میتواند «Unicode, RTL و Bidi در محصول فارسی» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
Who it is for
Foundation
Worked baseline, explicit evidence table, safe local fixture and one guided failure probe.
Repair
Reduce to one failing contract, observe evidence, repair, then restore full case.
Experienced
Skip syntax walkthrough; require independent implementation, mutation evidence and residual-risk statement.
Prerequisites
A Persian screen can look right while its text model is wrong
A notification row in a Persian account page begins with Persian text, contains a username such as alex_dev, includes order code AB-2048, and ends with punctuation. On one demo string it may look acceptable after a developer right-aligns the container. The same implementation can fail when the username becomes Arabic, starts with digits, contains an email address, or mixes Persian and Latin characters. Punctuation can appear attached to the wrong run, a code can visually drift, and a desperate “fix” may reverse source or DOM order so the data no longer matches the message.
This lesson treats direction as a data-and-structure contract, not a paint effect. Unicode text is stored as a sequence of characters in logical order. The Unicode Bidirectional Algorithm determines visual presentation from directional properties and context. HTML direction metadata establishes a base direction for a document or bounded subtree. Mixed or unknown-direction inline content can require isolation so its internal direction does not disturb surrounding text. Layout direction and CSS logical edges matter, but they are not substitutes for bidi text semantics.
The artifact is RTL/Bidi Product Evidence Record v1. It must contain the implementation artifact, observable browser/DOM behavior, at least one deliberate failure mutation, the repair result, a changed-context transfer case, and one residual limitation. A screenshot alone is insufficient because a visually plausible sample can still be backed by reversed logical order or fragile markup.
Four claims define the lesson. Logical storage order and visual presentation are different concepts. Base direction belongs in structural direction metadata such as dir, not in text-align:right. Unknown or opposite-direction inline runs sometimes need isolation, especially around neutral punctuation. Finally, a Persian product must be tested with realistic mixed strings—usernames, codes, numbers, punctuation, and user-generated values—not one comfortable sample.
The non-goals are equally explicit: RTL does not mean reversing strings or DOM order; right alignment is not equivalent to dir="rtl"; CSS logical properties do not solve bidi ordering inside text; and <bdo> is not a normal-purpose mixed-text repair.
WEB019-LAB1 is a local HTML/CSS/JavaScript fixture that starts with visually motivated shortcuts. State the contract before editing, repair only the owned direction/bidi boundary, run a mutation that breaks one critical dimension, and then prove the method on a changed notification case. No browser result is pre-claimed; your evidence must distinguish what you actually observed from what you inferred.
Three routes, one evidence bar
Foundation route. Separate four terms before coding: logical source order, base direction, bidi isolation, and layout direction. Inspect the starter data and DOM before its CSS. Use a simple Persian sentence with one Latin token, then move to the full notification fixture. Avoid visual recipes such as “put direction: rtl everywhere.”
Repair route. Begin from a concrete symptom: punctuation appears unexpectedly, a username perturbs surrounding text, or a page is right-aligned while its base direction remains LTR. Reduce the problem to a minimal string. Decide whether the cause is base direction, missing isolation, source-order corruption, or a layout choice. Repair that cause and rerun the same evidence row.
Experienced route. Skip elementary Unicode vocabulary and start from component boundaries. Declare which subtree owns the Persian base direction, which inline values have known direction, which values are unknown user content, and how you will prove that source order was never reversed. Then run baseline, mutation, and transfer evidence. Framework experience does not waive the evidence requirement.
Two prerequisites remain bounded. WEB-009 is hard: the interface must already have defensible semantic HTML. This lesson does not repair arbitrary structure by scattering direction attributes. WEB-013 is soft: focus and keyboard behavior should remain intact, but focus management and widget keyboard patterns are not re-taught here.
A useful working rule is: preserve logical data order; declare base direction structurally; isolate only the inline runs that need isolation; verify with realistic strings.
This prevents a category error. Character/run direction, paragraph or component base direction, and physical box arrangement in layout are related but not interchangeable. If a “fix” changes the wrong layer, the next content variation exposes it.
Mental model: source order → base direction → isolation → visual order → evidence
Logical order is the source of truth
Unicode text is not stored “as it looks on screen.” A Persian message containing a Latin username and order code should remain in business-meaning order in the data and DOM. Do not reverse substrings or DOM nodes merely to force a screenshot.
The first evidence checkpoint is therefore the raw sequence before rendering. Record the source fields or DOM sequence. If the UI looks correct only after business order changes, the implementation has violated the contract even before visual testing.
Base direction supplies context
A predominantly Persian document normally needs an RTL base direction. HTML provides structural direction metadata through dir. At a document boundary, a typical form is:
<html lang="fa" dir="rtl">
The relevant point is the semantic boundary, not the exact element chosen in every product. text-align:right moves line content; it does not establish RTL base direction for bidi resolution. A Persian component inside an LTR shell can instead own dir="rtl" locally.
Isolation protects surrounding context
User-generated inline content is not safe to classify by today's sample. A username may be Latin, Arabic, numeric-prefixed, or mixed. If that token participates directly in surrounding bidi context, strong characters inside it can influence adjacent neutral punctuation.
HTML provides <bdi> for isolation. For unknown-direction content:
<span>کاربر <bdi dir="auto">${displayName}</bdi> سفارش</span>
The value is isolated from its surroundings, while dir="auto" lets the value establish its own direction from content. If a product code is guaranteed to follow an LTR representation, that known direction can be marked explicitly instead of guessed.
Visual order is an output, not a storage plan
The Unicode Bidirectional Algorithm resolves directional runs for display. Application code should normally provide correct logical order and correct boundaries rather than recreating display reordering itself. A single sample may look fine by accident; another sample with different letters, digits, hyphens, parentheses, or an RTL username can expose the missing boundary.
Layout direction is separate
CSS logical properties such as padding-inline, margin-inline-start, border-inline-start, and inset-inline-end express layout relative to writing direction. They are useful for reducing left/right duplication. They do not isolate a mixed-direction token, and they do not repair a missing HTML base direction.
Likewise, reversing a flex row merely because the locale is Persian can make visual and DOM orders diverge. Layout reversal is not a bidi text repair strategy.
Evidence separates observation from inference
Observation is what you inspected: source order, the dir boundary, DOM structure, or how a specific test string rendered in a named environment. Inference is the conclusion drawn from those observations. A strong record explicitly distinguishes the two and never upgrades one browser run into a universal statement about every browser, font, assistive technology, or Unicode sequence.
Foundation: minimum markup and CSS vocabulary
dir is structural metadata
For a Persian subtree:
<main lang="fa" dir="rtl">
<h1>سفارشهای من</h1>
</main>
If the whole document is Persian, placing lang="fa" dir="rtl" on <html> is often clearer because descendants inherit the context. If only one subtree is Persian, scope direction there. lang identifies language; dir establishes direction. They solve different problems.
dir="auto" derives direction from content
For a bounded value whose direction is unknown in advance, dir="auto" can derive direction from the value's first strong directional character. Use it when unknown direction is part of the data contract, not as a blanket attribute on every element.
<bdi> isolates an inline value
A notification can preserve its logical sentence while isolating dynamic tokens:
<p class="notice" dir="rtl">
کاربر <bdi dir="auto">alex_dev</bdi>
سفارش <bdi dir="ltr">AB-2048</bdi> را ثبت کرد.
</p>
The username is unknown-direction. The code is known LTR in this bounded example. The Persian sentence remains in logical source order.
<bdo> is an override, not a generic repair
<bdo> forces an override. It is appropriate only when intentional overriding is genuinely required. It is not the default answer to “this mixed string looks odd.” If the issue is an independent unknown-direction inline value, isolation is the relevant mechanism.
Logical CSS handles edges, not bidi semantics
.notification {
padding-inline: 1rem;
border-inline-start: 4px solid currentColor;
}
.notification__code {
margin-inline-start: .5rem;
}
This makes layout direction-aware. It does not replace dir or <bdi>. Correctness must still be demonstrated with the mixed-content contract.
Inspect source and DOM explicitly
During debugging, log the raw values and inspect resulting node order. This catches visual fixes that secretly reverse the semantic sequence. In this lesson, preserving source order is a blocking requirement.
Why mixed-direction product strings fail
Bidi failures often become visible near punctuation because punctuation is neutral: it does not carry the same strong directional signal as Persian or Latin letters. Digits, separators, and symbols follow additional bidi rules. The browser resolves them in context. If a dynamic run leaks its directional context into surrounding text, the final visual arrangement can surprise you even though every character is present.
Consider the conceptual message:
کاربر [username] سفارش [orderCode] را ثبت کرد.
The logical order is fixed by the message semantics. [username] might be alex_dev, an Arabic-script name, 42dev, an email address, or a mixed value. A visual workaround tuned to one input is therefore fragile.
The stable move is to identify boundaries. The surrounding Persian sentence owns an RTL base. The username is a distinct run whose direction may be unknown. The order code may have a defined LTR representation. Mark those boundaries instead of rearranging the sentence.
Alignment and base direction can mimic each other in one screenshot
text-align:right can make an LTR paragraph sit at the right edge and therefore resemble an RTL paragraph in a static image. The mechanisms are still different. Alignment affects positioning; base direction participates in bidi resolution and inline context. The lab deliberately removes structural RTL while leaving right alignment to expose this false equivalence.
The expected conclusion is not that every string must look dramatically broken. The conclusion is that visual right alignment cannot satisfy the structural direction contract, even when a particular sample appears acceptable.
Isolation is about boundary effects
A dynamic value can have its own direction and still influence surrounding content if it is not isolated. <bdi> gives the run an explicit isolation boundary. This is especially useful for user names, generated identifiers, chat authors, and other values whose direction comes from data.
Do not respond by wrapping every Latin word. Static prose often renders correctly under ordinary Unicode bidi behavior. Isolation should follow the product's dynamic-data boundary, not a crude “foreign characters” rule.
Do not solve text order with reversal
Reversing arrays, strings, or DOM nodes until the screenshot matches can damage reading order, copy/paste sequence, accessibility order, and the relation between labels and values. Some reversed visual layouts are legitimate product choices, but reversal is not a bidi text fix.
In WEB019-LAB1, source sequence is evidence. The fixture carries explicit sequence markers so the learner can prove that business order stays intact while visual bidi presentation changes.
Representative strings matter
A realistic evidence set should vary direction and punctuation, not just text length. Include a known Latin username, an RTL username, a mixed/unknown username, a numeric or code token, and punctuation adjacent to those runs. The transfer case changes the content combination rather than merely resizing the same string.
Passing that bounded set does not prove all Unicode cases. The evidence record must state this residual limitation.
Worked example: a Persian order notification
Assume source data is:
const notice = {
username: "alex_dev",
message: "سفارش جدید ثبت کرد",
orderCode: "AB-2048"
};
The business sequence is username → message → order code. Keep it in the DOM. A bounded implementation can look like:
<article class="notification" dir="rtl" lang="fa">
<span class="notification__user"><bdi dir="auto">alex_dev</bdi></span>
<span class="notification__message">سفارش جدید ثبت کرد</span>
<span class="notification__code"><bdi dir="ltr">AB-2048</bdi></span>
<span aria-hidden="true">.</span>
</article>
This is a reasoning example, not a universal template. It establishes an RTL base for the Persian component, isolates the unknown username, isolates a known-LTR code, and leaves logical order untouched.
Record an evidence matrix before styling:
| Dimension | Requirement | Observation |
|---|---|---|
| logical/source order | no reversal | DOM/source remains username → message → code |
| base direction | Persian component has RTL base | inspect dir boundary |
| isolation | dynamic username is independent | inspect <bdi dir="auto"> and test variants |
| known code | code has bounded LTR direction | inspect local code run |
| product evidence | representative values are tested | record each exact input and result |
Layout can then use logical edges:
.notification {
display: grid;
gap: .375rem;
padding: 1rem;
border-inline-start: 4px solid currentColor;
}
Notice the absence of a reversed data array, row-reverse used as a bidi fix, or right alignment used as proof.
Open the fixture in the browser environment you actually use and record that environment. Test alex_dev, کاربر۱۲, dev_42@example.com, and a mixed value such as علی-dev. Record concrete observations around punctuation and the adjacent code. Do not write “works everywhere.”
For the first mutation, remove dir="rtl" while retaining right alignment. Predict which contract dimension fails before running it: structural base direction. A second mutation can replace <bdi dir="auto"> around the username with a plain span. Predict that the run is no longer isolated. The exact visible symptom can vary, so record what actually happened instead of inventing a guaranteed failure shape.
Repair only the missing boundary. Do not reverse message data. Rerun the same inputs and record the post-repair observations.
Failure modes that create false confidence
Right alignment masquerades as RTL. The page sits at the right edge, so the developer assumes direction is correct. Signal: no structural dir boundary. Repair: establish base direction and treat alignment separately.
Only one Latin username is tested. The sample happens to look fine. Signal: the evidence matrix has no RTL or mixed dynamic values. Repair: vary direction and isolate the value when its independent direction is part of the contract.
Source/DOM order is reversed. The screenshot improves while semantic order becomes wrong. Signal: source sequence no longer matches the business message. Repair: restore logical order and solve direction at the correct layer.
Logical CSS is treated as bidi semantics. margin-inline-start exists, so the team assumes text direction is solved. Repair: keep logical properties for layout and separately declare direction/isolation.
<bdo> becomes a generic patch. An override is forced until one sample looks right. Repair: return to base direction and isolation; use overrides only for rare requirements that genuinely need them.
Experienced bridge: direction as a component API
In a mature product, direction is an explicit component concern. A renderer should know which values inherit locale direction, which have a known independent direction, and which are unknown user content.
A notification component can classify data instead of guessing from presentation:
- localized
message: inherits Persian RTL base; displayName: user-generated, isolated with direction derived from content;orderCode: product identifier with known LTR representation;- punctuation: belongs to the surrounding sentence, not to an arbitrarily concatenated token.
Keep data order stable at API boundaries. If a backend sends {actor, action, object}, do not reverse the array for Persian. Localization can choose a sentence template, but bidi rendering should not mutate the semantic order produced by that template.
Tests should cover static invariants and rendered observations. Static checks can verify that direction/isolation markup exists and that no reversal operation corrupts source data. Browser evidence can inspect representative mixed strings. Neither category alone proves the full bounded outcome.
Avoid blanket rules such as “all nested spans are RTL.” Over-constraining children can damage codes, URLs, or user values whose independent direction matters. Inheritance is useful precisely because the surrounding sentence can own the base while explicit boundaries are added only where required.
Alternatives and trade-offs
Root dir="rtl" versus component dir="rtl". A Persian-first document benefits from a root direction. A mixed-language shell may need local component boundaries. Choose by semantic ownership; do not scatter attributes to silence symptoms.
<bdi> versus a directional span. When isolation is the requirement, <bdi> expresses that intent directly. A plain span with direction establishes direction for its contents but does not express the same isolation boundary. The frozen guidance for this lesson prefers dedicated bidi markup for normal HTML direction structure rather than using CSS as the primary semantic mechanism.
dir="auto" versus known ltr/rtl. Use known direction when the data contract guarantees it. Use automatic direction when content direction is genuinely unknown and deriving it from content is appropriate. Flexibility alone is not a reason to use auto.
Logical versus physical CSS properties. Logical properties are useful when layout edges should follow writing direction. A physical side can still be correct when the product requirement is physically anchored. This is a layout judgment, not a bidi doctrine.
Screenshots versus evidence records. Screenshots can catch visual regressions, but they cannot prove logical source order or why an isolation boundary exists. Use them as one observation surface inside a broader evidence record.
Guided practice, then independent transfer
Start WEB019-LAB1 exactly as supplied. It is intentionally incomplete. Before editing, write one sentence describing the owned contract and one explicit non-goal. Then inspect the notification data and record its intended logical sequence: actor → username → message → order code → numeric order ID → amount → completion.
For the guided repair, establish structural RTL direction for the Persian component. Handle the unknown username, known LTR code, numeric-only order ID, and amount-like numeric value according to their data contracts. Preserve source/DOM order. Run the public checker only for package structure; it does not certify browser rendering.
Create one disposable mutation with the provided tool: no-dir, no-isolation, or reverse-source. Record a prediction first. Open the mutated fixture, inspect the concrete input, and record an observation. Repair the original submission—not the disposable mutation—and record the post-repair result.
The transfer fixture changes the content mix to a user-generated display name and a ticket/reference token in another Persian sentence. Solve it independently. Preserve source order, justify direction/isolation boundaries, test representative values, and state one residual limitation.
If your proposed repair requires redesigning keyboard focus, responsive layout architecture, or the translation workflow, stop and name the adjacent boundary. A valid WEB-019 repair is primarily explainable in source order, base direction, isolation, and bounded layout direction.
RTL/Bidi Product Evidence Record v1
Each meaningful scenario records scenario_id, exact input_string, intended source_order, structural base_direction, isolation_mechanism, relevant layout_mechanism, direct dom_observation, direct visual_observation, and the mutation chain: mutation, prediction, observed_failure, diagnosis, repair, post_repair_observation. It also records whether the row is the changed_context, separates observation_vs_inference, names the test environment, and states a residual_limitation.
The artifact prevents a CSS patch from being accepted merely because one screenshot improves. A blocking assessment must be traceable to source order, structural direction, isolation, and an observed mutation/repair cycle.
Static and browser checks have different authority. Static checks can prove that a dir or <bdi> boundary exists and that source code has not deliberately reversed the data. They cannot prove what a user saw in a specific browser. Browser observation can show the tested rendering, but it cannot prove structural soundness for every input. The evidence record requires both where applicable and must not fabricate either.
Assessment and transfer
WEB-019-S1 — Retrieval. Explain logical source order, base direction, bidi isolation, and layout direction; include one non-goal.
WEB-019-S2 — Application — blocking. Repair the main lab so the Persian component has structural RTL direction, dynamic inline values are handled intentionally, and source order remains unchanged. Submit direct evidence.
WEB-019-S3 — Diagnosis — blocking. Run one deliberate mutation. Predict the failing contract dimension, observe the actual result, diagnose it without overclaiming, and make the smallest repair.
WEB-019-S4 — Decision. Defend a mechanism choice, such as <bdi dir="auto"> for a user-generated name, against a plausible alternative. State what evidence would change the decision.
WEB-019-S5 — Transfer — blocking. Solve the changed notification fixture independently. Preserve source order, justify direction/isolation boundaries, test representative mixed inputs, and state a residual limitation.
The lesson fails if a critical mutation is not detected, visual success is used as sole proof, or the hard prerequisite/chapter boundary is violated.
References, freshness, and claim boundaries
The frozen source set contains four authorities and is not expanded in this authoring pass:
- Unicode Standard Annex #9, Unicode Bidirectional Algorithm — logical order and bounded bidi display behavior:
https://www.unicode.org/reports/tr9/ - W3C Internationalization, structural markup and text direction in HTML —
diranddir="auto":https://www.w3.org/International/questions/qa-html-dir - W3C Internationalization, inline markup and bidirectional text — mixed inline content,
<bdi>, and rare override cases:https://www.w3.org/International/articles/inline-bidi-markup/ - W3C Internationalization, bidi markup versus CSS — directionality as document structure and dedicated markup for normal HTML content:
https://www.w3.org/International/questions/qa-bidi-css-markup
The Bible records retrieval on 2026-09-02 and review due on 2026-12-31. The job profile marks FRESHNESS_SENSITIVE=false, so this authoring pass preserves the frozen source set without a new network recheck. Future updates must respect the same claim boundaries unless governance authorizes a source-set change.
Browser and assistive-technology rendering can vary; evidence must name the tested environment and must not universalize one observation. Product requirements can also change the exact component/layout mechanism. The stable outcome is the bounded contract-and-evidence method for Persian mixed-direction product text.
Assessment
There is no answer key in this lesson pack; answer it yourself and mark honestly. "Needs review" sends the lesson to your review queue.
WEB-019-S1 retrieval
Distinguish logical order, visual order, base direction and bidi isolation in a Persian mixed-text component. Include one explicit non-claim about right alignment, CSS logical properties, source reversal or bdo.
WEB-019-S2 application
Repair WEB019-LAB1 so the Persian notification owns structural base direction and dynamic unknown/opposite-direction inline values use justified isolation or explicit direction, while business/source order remains unchanged. Complete baseline evidence.
WEB-019-S3 diagnosis
Run one deliberate mutation: remove structural RTL direction, remove isolation from a dynamic value, or reverse source/DOM order. Predict first, observe, diagnose the failed critical dimension, repair only that boundary, and re-observe.
WEB-019-S4 decision
For a dynamic identifier, choose between bdi dir=auto, an explicit known direction, or no additional bidi boundary. Defend the choice from the value contract and state evidence that would change the decision.
WEB-019-S5 transfer
Independently solve the changed-context support-ticket notification with different user content and punctuation. Preserve logical source order, choose direction/isolation boundaries from the new contract, record changed-context evidence and one residual limitation.
Assessment rubric
Blocking assessments are S2, S3 and S5. Passing evidence must preserve logical source order, establish the owned base-direction boundary structurally, justify isolation/known direction from the value contract, and distinguish direct observation from inference. S3 additionally requires a pre-run prediction, an observed mutation result, diagnosis of one critical dimension, bounded repair and post-repair observation. S5 must use the changed-context transfer fixture independently and include a residual limitation.
S1 and S4 are nonblocking but must not repeat the lesson non-goals as positive guidance. No assessment key is exposed to the learner. Static structure checks are not browser or assistive-technology evidence.
References & freshness
Case studies
Lab files
This fixture is intentionally incomplete. Do not treat its visual right alignment as a correct RTL implementation.
- From
WEB019-LAB1, runpython run_checks.py --structure-only. This checks the failure fixture only; it does not certify rendering. - Copy
learner/starter/to a sibling directory namedlearner/submission/. Repair submission, not starter. - Record the logical baseline sequence:
actor → username → message → order code → numeric order ID → amount → completion. The fixture deliberately includes a Latin username, an alphanumeric code, numeric-only order ID2048, amount-like value1,250,000, and punctuation. - Repair the owned Persian base-direction boundary structurally; do not reverse strings or DOM order. Classify dynamic values from their data contracts and isolate unknown/opposite-direction inline values when justified.
- Run the repaired baseline in your own browser and record direct DOM/visual observations in
learner/evidence/learner_evidence.json. Do not invent observations. - Run exactly one deliberate mutation with
python mutation_probe.py --mutation <no-dir|no-isolation|reverse-source>. The tool useslearner/submission/by default and creates a disposablemutated/copy. Predict first, then observe, diagnose, repair the original submission, and re-observe. - Keep baseline, mutation, and changed-context transfer as distinct evidence rows. Solve the support-ticket transfer independently and mark that row
changed_context: true. - State the tested environment and one residual limitation.
- Run
python run_checks.pyfor bounded completed-submission structure/evidence validation. Passing it is not browser or assistive-technology correctness.
run_checks.py never substitutes static checks for direct browser/AT evidence. Browser/AT statements belong only in learner-recorded evidence from a named environment.
Lesson media & slides
Teaching files (download)