Skip to content

DOL Design System - Practice Patterns

active v0.3Updated

Extends CORE.md. Dành cho Exercise, Exercise with AI, Online Test. Đọc CORE.md trước - file này chỉ chứa patterns đặc thù cho Practice. Product logic như score band, attempt rule, leaderboard metric, unlock/deadline, hoặc thứ tự material phải được product/spec xác nhận; file này chỉ ghi reusable UI implications để designer/dev không phải suy luận lại từ đầu.


Extends CORE.md §4 Spacing + §6 Surface + §9 Components. Playground refs: components/Practice/AttemptView.tsx, components/Playground/Exercise2026Idea.tsx.

One question, one focus - exercise page strip chrome, center user attention on active question.

Three-region shell:

┌─────────────────────────────┐
│ Header (skill + progress) │ ← sticky top, thin
├─────────────────────────────┤
│ │
│ Body (prompt + answers) │ ← scrollable, main focus
│ │
├─────────────────────────────┤
│ Footer (submit / next) │ ← sticky bottom OR inline
└─────────────────────────────┘
  • Header: h-14 sticky top-0 bg-white border-b border-slate-100 với skill icon + progress indicator. No page nav (user đã in exercise mode).
  • Body: px-4 py-6 md:px-8 md:py-10 max-w-3xl mx-auto - centered, readable width (~700-800px). Prevents reading marathon trên wide screens (DIRECTION §10 Layout).
  • Footer: sticky bottom-0 bg-white border-t border-slate-100 cho long exercises; inline dưới body cho short exercises.

Question region: prompt + optional media (audio, image) + instructions.

ElementClassNotes
Prompt texttext-lg font-medium text-slate-900 hoặc text-xl font-semiboldlg cho most cases; xl khi prompt quan trọng hơn answer
Media (audio/image)Below prompt, max-w-full rounded-xlGiữ aspect ratio; media không lấn prompt readability
Instructionstext-sm text-slate-600 trước answer areaOptional; only khi exercise type cần explain
Prompt → Answer gapmt-6 (C-organism per CORE §4.1)Separates question context from action area

Layout depends on answer type:

Answer TypeLayoutSizing
Multiple choice (2-4 options)Stacked verticalFull-width cards space-y-3
Multiple choice (5-8 options)2-column gridgrid grid-cols-1 md:grid-cols-2 gap-3
True/FalseHorizontal pairgrid grid-cols-2 gap-3
Fill-in-blankInline with promptInput w-32 (short) hoặc w-full (long)
Free text / essayFull-width textareaw-full min-h-[120px] max-h-[400px]
Speaking / recordingRecord button centeredButton h-14 centered, mt-6

Khi prompt dài (reading passage, listening transcript) → split-panel ≥ lg:

<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="lg:sticky lg:top-20 lg:max-h-[calc(100vh-8rem)] lg:overflow-y-auto">
{/* prompt (reading passage / transcript) */}
</div>
<div>
{/* answers */}
</div>
</div>
  • Breakpoint: lg (≥1024px). Below: stack (prompt trước, answers sau).
  • Sticky prompt while user scrolls answers - user reference passage while answering.
  • max-h-[calc(100vh-8rem)] + overflow-y-auto trong prompt panel - scroll independently.
  • Không bao giờ 3-column split (reading flow gãy).
BreakpointBehavior
< md (mobile)Always stack vertical. Full-width actions.
md-lg (tablet)Stack for split-panel; side-by-side for short prompts
≥ lg (desktop)Split-panel activates for long-form
  • Header giữ sticky mọi breakpoint (progress context always visible)
  • Footer: full-width button mobile, inline tablet+
  • Không force 2-column split mobile (reading flow broken)

Extends CORE.md §9.1 Card + §2.3 Status Colors. Answer cards = core interactive primitive của Practice - phải consistent across exercise types.

Touchable, not flashy - answer card mời chạm nhưng không overwhelm prompt. Extends CORE §9.1 Card với interaction layer.

<button
type="button"
className="w-full text-left p-4 rounded-xl border border-slate-200 bg-white
hover:border-slate-300 hover:bg-slate-50
focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500
transition-colors duration-200
text-slate-900"
>
{answer.text}
</button>
  • Padding: p-4 (inset-4 per CORE §4.1) - breathable, không loose
  • Radius: rounded-xl (12px) - soft-large per DIRECTION §8
  • Border: border-slate-200 default, border-slate-300 hover (subtle, không flash)
  • Focus ring: ring-2 ring-blue-500/20 - CORE focus pattern
  • Transition: duration-200 color-only (no layout animation per R11 slop-09)

User clicks answer → selected, chưa submit.

className={cn(
base,
isSelected && "border-blue-500 bg-blue-50 ring-2 ring-blue-500/10"
)}
  • Border: border-blue-500 (info-primary = active state)
  • Background tint: bg-blue-50 (info dim bg per CORE §2.3)
  • Optional subtle ring: ring-2 ring-blue-500/10

Không dùng brand red cho selected - brand reserved cho CTA/logo (DIRECTION §4). Info color = active state per DOL convention.

After submit, correct answer gets success treatment.

className={cn(
base,
isCorrect && "border-green-500 bg-green-50 text-slate-900"
)}
  • Border: border-green-500
  • Background: bg-green-50
  • Optional <Check /> icon right-aligned text-green-600
  • Extends CORE §2.3 Status Colors (success dim bg + primary border)
  • Text giữ text-slate-900 - không đổi sang green (maintains readability)

After submit, user’s incorrect answer gets danger treatment - visible nhưng không hostile.

className={cn(
base,
isIncorrect && "border-red-500 bg-red-50 text-slate-900"
)}
  • Border: border-red-500
  • Background: bg-red-50
  • Optional <X /> icon right-aligned text-red-600
  • Không shake/bounce animation (R11 slop-08); subtle color transition 200ms là đủ
  • Tone: informative, không punitive. DOL = education, wrong answer là learning moment, không là failure.

Viewing past attempts / completed exercise → cards non-interactive.

// button → div when review mode
className={cn(
base,
isReviewMode && "cursor-default hover:border-slate-200 hover:bg-white"
)}
  • Remove hover state (non-interactive)
  • Giữ correct/incorrect color treatment nếu reviewing attempts
  • Disable click handler; button → div với role="group"
  • Wrap với label “Đáp án đúng” / “Bạn chọn” above mỗi card để clarify context

🛑 Match-and-refuse (per DIRECTION §10): nếu bạn sắp generate code khớp một row dưới, STOP - rewrite approach từ đầu, không chỉ swap value lẻ.

<dol_anti_pattern scope=“practice-2.6-answer-cards”>

ID❌ PATTERNVì sao✅ REWRITE
01Shake / bounce animation on incorrectR11 slop-08 bounce feel; feels punitive, wrong DOL education toneSubtle color transition 200ms ease-out
02Brand red for “selected” stateBrand reserved cho CTA/logo (DIRECTION §4); confuses với dangerBlue for selected (info = active per DOL convention)
03Full border + bg + shadow change on hoverR11 slop-07 over-treatmentBorder color change only, hoặc bg tint only, not all three
04Icon badge above answer textIcon-tile-above slop (R11 slop-06); wastes visual weightInline icon right-aligned, hoặc skip entirely
05Gradient bg on correct/incorrectR11 slop-02 decoration without meaningFlat bg-{color}-50 tint (CORE §2.3)
06Huge letter prefix “A” / “B” / “C” as badgePulls weight from answer content (user reads letter, not text)Letter inline: text-slate-500 font-medium mr-3, or skip if answers text alone is clear
07”Try again” text on incorrectPunitive phrasing; DOL tone is calmExplanation + next-action button inline, không text reprimand
08Confetti / celebration animation on correctMarketing-gamification, wrong for learning contextQuiet success indicator; reserve celebration cho milestone (chapter complete)

</dol_anti_pattern>

Rules cho AI-generated content rendering trên Practice surfaces.

AI content chỉ highlight 3 nhóm:

NhómVí dụStyle
Core skill namesReading, Listening, Speaking, Writing, Vocabulary, Grammar, PronunciationTint inline nhẹ, đọc được trong paragraph flow
Lesson target / subskillRelative Clauses, Paraphrasing, IntonationTint inline, chỉ khi thật sự quan trọng
Study metrics / data77%, Band 6.5, 15 phút, 3/5 bàiNeutral data-pill, tabular-nums
  • Mỗi paragraph chỉ vài semantic highlights đáng chú ý.
  • Nếu content đã dày → **bold** cho emphasis phụ thay vì tô màu.
  • KHÔNG biến highlight thành badge nặng trong body copy.
  • Oversized value chỉ cho metric-like values: %, band, số lượng, thời lượng, count.
  • Topic/status text (Relative Clauses, Đang ổn, Cần sửa) giữ ở 14px body scale.
  • Signal card không được phồng typography.

AI diagnostic rows (label:value format):

Rủi ro: Relative Clauses
Completion: 77%
Điểm mạnh: Vocabulary in Context
  • Label ngắn, value là phần chính.
  • Đi qua shared fact row formatting.
  • KHÔNG biến thành card con hoặc dashboard nặng.

AI content phải đi qua shared markdown renderer:

Inline semantics (baseline): bold, italic, code, link, strikethrough, highlight. Block semantics (baseline): heading, list, task list, blockquote, divider, code block, labeled callout.

  • KHÔNG để raw markers (**bold**, *italic*, ~~strike~~, `code`) lọt ra production.
  • Mở rộng semantics → thêm vào shared renderer trước, không patch riêng từng lane.
  • Checklist, list, callout, paragraph, signal value → cùng markdown preview behavior.
  • Utility copy tiếng Việt nhất quán: Xem thêm, Thu gọn, Tạo lại.
  • KHÔNG mixed-language drift trên AI surfaces.
  • Không kéo marketing narrative vào logged-in surfaces.

Extends CORE.md §2.3 Status Colors + §3 Typography + §10 Motion. Practice time + progress tracking UX.

Two modes: countdown (timed tests - IELTS simulation) + elapsed (untimed practice).

Countdown (shows time remaining):

<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full
bg-white border border-slate-200 text-slate-900 tabular-nums">
<Clock className="w-4 h-4 text-slate-500" />
<span className="font-semibold">{formatTime(timeRemaining)}</span>
</div>
  • Layout: pill shape, sticky top near skill indicator
  • Font: tabular-nums - prevents width shift between 00:4501:30
  • Color progression (urgency without panic):
    • 50% remaining: neutral text-slate-900

    • 25-50%: text-amber-600 + border-amber-200 bg-amber-50
    • <25%: text-red-600 + border-red-200 bg-red-50
    • =0: see §4.4 Time-up state

Elapsed (untimed): same structure, skip color progression - stays neutral throughout.

Question-by-question / skill-section progress:

<div className="w-full h-1.5 bg-slate-100 rounded-full overflow-hidden">
<div
className="h-full bg-brand-600 transition-all duration-500 ease-out rounded-full"
style={{ width: `${(current / total) * 100}%` }}
/>
</div>
  • Height: h-1.5 (6px) - subtle, không compete với content
  • Track: bg-slate-100; fill: bg-brand-600 default or bg-{skill}-600 khi có skill context
  • Transition: duration-500 ease-out - smooth update (no bounce per R11 slop-08)

Horizontal dots showing question statuses:

<div className="flex items-center gap-1.5 flex-wrap">
{questions.map((q, i) => (
<button
key={q.id}
className={cn(
"w-8 h-8 rounded-full text-xs font-semibold transition-colors",
q.answered && !q.current && "bg-slate-100 text-slate-600",
q.current && "bg-brand-600 text-on-inverse-primary",
!q.answered && !q.current && "bg-white border border-slate-200 text-slate-400"
)}
aria-label={`Câu ${i + 1}${q.current ? ' (hiện tại)' : q.answered ? ' (đã trả lời)' : ' (chưa trả lời)'}`}
>
{i + 1}
</button>
))}
</div>
  • Answered (not current): bg-slate-100 text-slate-600 (done, neutral)
  • Current: bg-brand-600 text-on-inverse-primary (active focus)
  • Pending: bg-white border-slate-200 text-slate-400 (not yet reached)
  • Clickable cho review/jump; horizontal scroll wrapper khi >10 questions

Khi countdown = 0: calm VN alert + UI transition:

{isTimeUp && (
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full
bg-red-50 border border-red-200 text-red-700 font-semibold">
<AlertCircle className="w-4 h-4" />
Hết giờ
</div>
)}
  • Clear VN label “Hết giờ”
  • Auto-submit form if configured, else freeze input + show result option
  • Không blink/flash effect - DOL education tone stays calm (R11 slop-08 bounce territory)
  • References CORE §2.3 Status Colors danger dim bg

🛑 Match-and-refuse (per DIRECTION §10): nếu bạn sắp generate code khớp một row dưới, STOP - rewrite approach từ đầu, không chỉ swap value lẻ.

<dol_anti_pattern scope=“practice-4.5-timer-progress”>

ID❌ PATTERNVì sao✅ REWRITE
01Red flash / blink when time <1 minStress-inducing, punitive - wrong DOL toneColor progression (neutral → amber → red) via static colors
02Timer font không tabular-numsWidth shifts mỗi second; visual jitterAlways tabular-nums for time displays
03Progress bar bounce/elastic easingR11 slop-08; feels playful trong wrong contextease-out duration-500 linear smooth
04Circular progress với huge % text centerHero-metric slop; competes với questionSlim horizontal bar + small % text nếu cần
05Step indicator có label “Câu 3 of 10” + dots đồng thờiRedundant - dot count = totalDots only OR “3/10” text only, không both
06Auto-submit không warningUser mất answers chưa commit; surprise UX30s warning toast trước time-up

</dol_anti_pattern>


§5 Skill Color Usage in Practice status:done

Section titled “§5 Skill Color Usage in Practice status:done”

Skill colors (from CORE.md §2.4) apply trong Practice context:

ElementÁp dụng skill color
Question section headerTint background theo skill
Score/result badgeSkill color badge
Progress per skillSkill color cho progress bar
Navigation tabSubtle tint khi active

Xem CORE.md §2.4 cho bảng skill → TW family mapping.


Extends CORE.md §9.2 Button + §10 Motion. UI-side patterns cho audio controls. Audio engine / TTS pipeline detail: see audio-tts skill (workspace .claude/skills/).

Standard playback controls: play/pause + progress + duration.

<div className="flex items-center gap-3 p-3 rounded-xl bg-slate-50 border border-slate-100">
<button
onClick={togglePlay}
aria-label={isPlaying ? "Tạm dừng" : "Phát"}
className="w-10 h-10 rounded-full bg-brand-600 text-on-inverse-primary
hover:bg-brand-700 flex items-center justify-center
transition-colors duration-200"
>
{isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4 ml-0.5" />}
</button>
<div className="flex-1">
<div className="h-1 bg-slate-200 rounded-full overflow-hidden">
<div className="h-full bg-slate-600 transition-all"
style={{ width: `${progress * 100}%` }} />
</div>
</div>
<span className="text-xs text-slate-500 tabular-nums">
{formatTime(current)} / {formatTime(duration)}
</span>
</div>
  • Container: rounded-xl bg-slate-50 border border-slate-100 subtle surface per CORE §6
  • Play button: w-10 h-10 circular brand color - core action
  • Progress: h-1 thin track (support role, không focal)
  • Time display: tabular-nums giữ digit width stable

Persistent mute state across practice session:

<button
onClick={toggleMute}
aria-label={isMuted ? "Bật âm thanh" : "Tắt âm thanh"}
aria-pressed={isMuted}
className="p-2 rounded-lg hover:bg-slate-100 text-slate-600 transition-colors"
>
{isMuted ? <VolumeX className="w-5 h-5" /> : <Volume2 className="w-5 h-5" />}
</button>
  • Placement: header area, near skill/progress indicators
  • State persistence: store preference trong localStorage - maintain across sessions
  • Icon swap: Volume2 / VolumeX from Lucide
  • Không auto-unmute khi user explicitly muted - respect choice

Correct/incorrect/complete notification sounds - optional, accessible:

SoundCharacteristicVolumeDuration
CorrectSoft chime - pleasant, not celebratory30-40%<500ms
IncorrectGentle low tone - informative, not punitive30-40%<500ms
Section completeShort rising melody - achievement cue40-50%~800ms

A11y mandate:

  • All feedback sounds HAVE visual counterpart (check icon / x icon per §2 Answer Cards)
  • Respect prefers-reduced-motion: nếu user có preference → auto-mute feedback sounds
  • User can disable via settings - preference persists per §6.2 pattern
  • Never rely on sound alone cho critical state feedback

6.4 Narration Indicator (Speaking Animation)

Section titled “6.4 Narration Indicator (Speaking Animation)”

Visual cue khi AI TTS narration is playing:

<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full
bg-sky-50 border border-sky-200 text-sky-700">
<div className="flex items-end gap-0.5 h-3">
<span className="w-0.5 bg-sky-600 animate-pulse-bar-1" />
<span className="w-0.5 bg-sky-600 animate-pulse-bar-2" />
<span className="w-0.5 bg-sky-600 animate-pulse-bar-3" />
</div>
<span className="text-xs font-medium">Đang nói...</span>
</div>
  • Color: sky-* (AI features per CORE §2.4 skill colors)
  • Animation: 3 pulsing bars với staggered delay (0s / 0.2s / 0.4s) - audio-level indicator pattern
  • VN label: “Đang nói…” / “Đang giải thích…”
  • Auto-dismiss khi narration ends

🛑 Match-and-refuse (per DIRECTION §10): nếu bạn sắp generate code khớp một row dưới, STOP - rewrite approach từ đầu, không chỉ swap value lẻ.

<dol_anti_pattern scope=“practice-6.5-audio-tts”>

ID❌ PATTERNVì sao✅ REWRITE
01Loud feedback sounds (100% volume)Jarring, disrupts focus30-40% volume, <500ms duration
02Harsh buzzer for incorrectPunitive tone, wrong DOL education feelGentle low tone - informative, not scolding
03Auto-play narration without user initiateSurprises user trong public places; consent violationRequire explicit play button tap
04Narration indicator using brand colorBrand reserved cho CTA/logo; confuses semanticssky-* (AI identity per DOL skill colors)
05No visual counterpart cho audio feedbackA11y fail cho deaf users / muted contextPair audio với visual (check icon, toast)
06Progress bar bounce/elastic during playbackR11 slop-08 bounce; dated feelLinear smooth transition; no overshoot
07Vague labels (“Play”, “Pause” in English)Mixed-language drift trong VN UIFull VN: “Phát”, “Tạm dừng”, “Đang nói…”

</dol_anti_pattern>


Synthesized from current Design Hub product-pattern pages: Online Test logic, Status test, UI Score, Score level, Leaderboard, Attempt history, and exercise-ordering reference. DS-Token keeps the reusable UI implications here; EduDoc/product owner remains source of truth for scoring, data model, permission, and business rules.

Default mental model:

[optional] Skill -> Section -> [optional] Group -> Question

Use this model when designing flow, progress, review, scoring, or result screens:

LevelUI implicationCheck before reuse
SkillTop-level tab/segment or result breakdown for Reading, Listening, Writing, Speaking, TestDoes product actually split by skill?
SectionMajor test part, passage, task type, or content blockDoes navigation/progress follow section or question?
GroupShared passage/media/context for >=2 related questionsDoes the group need split-panel, sticky prompt, or shared audio/image?
QuestionSmallest answerable unitAre type, input, scoring, explanation, and review state clear?

If the product has no skill split, start from Section. If questions share passage/media, prefer Group rather than repeating context per question.

Practice/test cards, rows, and navigation should distinguish lifecycle state from visual decoration.

StateMeaningUI requirement
LockedUser cannot access yetDisabled/locked affordance + reason/unlock condition if available
Waiting / Not openedAvailable later or not startedNeutral state + clear start/open timing
In progressCurrent attempt existsPrimary/current affordance + progress context
Done / ClosedCompleted or closed for submissionResult/review affordance; do not imply editable if closed
WarningNeeds attention before becoming errorWarning label + action or explanation
ErrorFailed/invalid/blocking problemError label + recovery path

Rules:

  • State must affect CTA/navigation when relevant, not only badge color.
  • Do not rely on color alone; include label/icon/context.
  • If a state is both skill-related and status-related, status color wins; skill color stays in surrounding context.

Result UI should help learners understand what happened and what to do next. A useful result screen usually includes:

  • Score/result summary: score, band, percent, pass/fail, or main outcome.
  • Context: attempt, total questions, skill/section, time, benchmark, or previous result if useful.
  • Breakdown: correct/incorrect/skipped, skill/topic, section, or group-level performance.
  • Feedback: short, specific, non-punitive explanation.
  • Next action: review mistakes, practice again, continue lesson, retake, view explanation, or ask AI.

Score rules:

  • Product/spec must confirm score band, threshold, and meaning.
  • Low score is not automatically danger; it may be learning feedback.
  • High score is not automatically success if no completion/achievement has occurred.
  • Score color must not conflict with error/warning/success states on the same screen.

Use leaderboard only when there is a real comparison mechanic.

Before designing leaderboard UI, confirm:

  • Metric: score, time, accuracy, completion, streak, or composite score.
  • Scope: class, cohort, course, practice set, assignment, or global board.
  • Rank logic: top 3/top 4/top 8/top 10, tie handling, and hidden/private users.
  • Attempt rule: latest, best, first N attempts, or product-specific counted attempts.
  • Current user visibility and empty/loading/error states.

Visual rules:

  • Ranking emphasis is not generic accent color and not status color.
  • Top 1/2/3/other may use distinct treatments, but they must stay subordinate to task CTA and result clarity.
  • If leaderboard coexists with personal score feedback, separate ranking from learning feedback in layout and copy.

Attempt history supports review, comparison, and explaining why score/rank changed.

Required states:

  • No attempt yet.
  • Current/latest attempt.
  • Past attempt.
  • Attempt not counted in leaderboard.
  • AI attempt limit reached, if AI practice has a separate quota.
  • Loading/error while fetching history.

Rules:

  • Opening an old attempt should enter review/read-only mode unless it is the current active attempt.
  • UI must say which attempt affects score summary or leaderboard when ranking is present.
  • GV/HV visibility may differ; confirm permissions with product/spec before exposing attempt detail.

7.6 Exercise / Material Ordering Reference

Section titled “7.6 Exercise / Material Ordering Reference”

Current product reference order for Course materials / Bài tập trong khóa:

  1. Assignments
  2. Online tests
  3. Dictation
  4. Vocabulary
  5. Exercises
  6. AI mock test
  7. Mental model
  8. Sample W/S
  9. Blog, Video, PDF
  10. Roadmap cá nhân hóa

Before reusing this order:

  • Confirm role: GV, HV, or operations role.
  • Confirm surface: course material, homework list, learning roadmap, or another product lane.
  • Check permission/lock/deadline conditions that may hide or reorder items.
  • Ask product owner if applying this order to a new product, because this is a product reference, not universal DS hierarchy.

7.7 Product-pattern Verification Checklist

Section titled “7.7 Product-pattern Verification Checklist”

Before shipping a Practice/Test surface:

  • Does the UI model match skill/section/group/question structure?
  • Are lifecycle states explicit in label/icon/context and CTA behavior?
  • Are score bands, thresholds, and ranking rules confirmed by product/spec?
  • Is old attempt review read-only?
  • Does leaderboard state clearly say what metric and attempt rule are counted?
  • Are status color, skill color, and ranking color separated by meaning?
  • Is the next learner action clear after result/feedback?