Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Diagnose and fix CJK (Korean, Japanese, Chinese) text-wrapping issues in web UIs. Systematically traces mid-syllable breaks, orphaned glyphs, and awkward line splits through a layered fix strategy covering global CSS cascade, component-library overrides, headline balancing, and translation-level phrase binding. Produces an evidence-based diagnosis and verification report before any code change is applied.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 244% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 71% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 170% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 176% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 164% | 0% |
Type: Execution
Turn ad-hoc CJK (Korean, Japanese, Chinese) line-break firefighting into a layered, evidence-first audit. CJK wrap bugs are a class of cross-cutting cascade defects: a missing locale-scoped word-break rule, an Ant Design / MUI / Mantine cssinjs override that wins on specificity, an over-eager text-wrap algorithm, and a translation string with no phrase boundary can each independently cause text to shatter mid-syllable or strand a single glyph at line edge.
This skill prescribes a fixed sequence of diagnostic gates so the audit produces an evidence-backed report — codepoint dump, computed-style trace, and verification matrix — before any code is changed.
right".
invisible at 100%.
<br>, hardcoded widths, or !important andthe bug came back.
glyphs, fallback fonts).
pre blocks (wrap is a terminalconcern, not CSS).
white-space: nowrap,not by wrapping behavior.
Do not run this skill without:
Mantine, Chakra, plain CSS, …).
Optional but recommended:
Without asking the user (unless unavailable), gather these inputs directly from the repository and the running app.
Produce a structured 7-section report. Do not produce code changes until Section 7 is approved.
reference, exact observed break (quote the broken line).
of word-break / overflow-wrap / text-wrap / hyphens / line-break, and the cascade trace identifying the winning declaration with its specificity.
library injector, headline balancing algorithm, or translation string itself.
order, and why additional layers are or are not needed.
after). No speculative refactors.
breakpoints × locales × theme modes to be re-checked.
applying patch." Do not edit code until the user confirms.
Collect screenshot, URL, locale code, breakpoint width, browser, and zoom level. If any item is missing, request it before proceeding. Quote the offending broken line verbatim from the screenshot so later gates have an unambiguous target.
Halt if no screenshot or repro is available — visual bugs cannot be audited from descriptions alone.
Read the translation file containing the offending key. Dump the target string as Unicode codepoints (e.g., U+C774 U+B3D9 U+D558 U+C138 U+C694) and annotate:
U+00A0), zero-width joiner (U+200D), zero-widthspace (U+200B), or soft hyphen (U+00AD) — any of these may already be influencing the wrap.
semantic unit that should not split (e.g., Korean 어절 / Japanese 文節 / Chinese 词组).
inside a CJK string.
Output: a labeled codepoint table and a phrase-boundary map.
Read the affected component file. Then trace the computed CSS for the broken element. Document the winning declaration (with selector and specificity) for each of:
word-breakoverflow-wrap (and legacy word-wrap)text-wrap (wrap / nowrap / balance / pretty)hyphensline-breakNote any white-space value other than normal — it changes wrap semantics entirely.
If the component is wrapped by a UI library (Ant Design Typography, MUI Typography, Mantine Text, Chakra Text, …), record the library's injected class and where its rule comes from.
For each library identified in Gate 2, locate the injected stylesheet (usually via cssinjs / emotion / stitches) and capture the rule that is in effect. Calculate its specificity.
Common offenders:
Typography: injects .ant-typography { word-break:break-word } (specificity 0,1,0).
Typography: injects .MuiTypography-root { … } similarly.body defaults can be overridden by anycomponent class.
Identify a safe override location and selector. Prefer:
.ant-typography.ant-typography { … }(specificity 0,2,0) — beats the library's single-class rule without !important.
:lang(ko) on the element — a pseudo-classadds 0,1,0 specificity (unlike :where(), which erases it) and keeps wrap overrides away from ja / zh text.
Avoid:
:where(.ant-typography) — :where() collapses specificity to0,0,0. The override will lose every cascade contest.
!important — wins, but pollutes future overrides and is hard toaudit.
Propose the minimum number of layers needed. Stop at the first layer that fully resolves the symptom; only escalate if the verification matrix in Gate 5 still has failing cells.
Layer 1 — Global cascade. Add a locale-scoped base rule:
css/* Korean only. ja/zh have no inter-word spaces — keep-all would remove every break opportunity there and cause overflow. */ :lang(ko) { word-break: keep-all; overflow-wrap: break-word; } /* Always exempt monospace contexts. */ code, pre, kbd, samp { word-break: normal; overflow-wrap: normal; }
The UA default (word-break: normal) allows a break between any two CJK syllables — that is what shatters Korean mid-word. keep-all forbids those intra-word breaks; Korean still wraps at spaces (어절 boundaries). Japanese and Chinese are written without spaces, so keep-all would leave them no break opportunity at all — never apply it to ja / zh. For ja, keep default breaking (optionally line-break: strict for stricter kinsoku punctuation rules) and bind phrases at Layer 4 or with a phrase segmenter such as BudouX. For zh, the default break-anywhere behavior is correct. overflow-wrap: break-word is the safety net for very long Latin tokens. :lang(ko) relies on a correct lang attribute (<html lang="ko"> or on the locale subtree) — most i18n frameworks set it; verify during Gate 2.
Layer 2 — Component-library override. If a UI library injects a stronger rule, neutralize it at the same scope:
css.ant-typography.ant-typography:lang(ko) { word-break: keep-all; overflow-wrap: break-word; }
(Adjust the selector for whichever library is in use. Use the doubled-class trick rather than !important, and keep the :lang(ko) scope so the override never reaches ja / zh text.)
Layer 3 — Headline-specific balancing. For hero / headline text where line-length aesthetics matter, add a balanced wrap on the specific element:
html<h1 class="text-balance">…</h1>
text-wrap: balance (Tailwind utility text-balance, or raw CSS) distributes characters evenly across lines and prevents single-word orphans on the last line. Use text-pretty only if balance is too expensive (it's a hint, not a guarantee).
Layer 4 — Translation-level phrase binding. When a specific phrase must move as a unit (e.g., a verb + its object), insert NBSP (\u00a0) between the words in the translation file, not in JSX:
json{ "hero.title2": "더 스마트하게 구축하고, 더\u00a0빠르게\u00a0이동하세요" }
Use NBSP only for short, semantically tight phrases (≤4 words). Long NBSP runs prevent any wrapping at all and will overflow on narrow screens.
Build an explicit matrix and mark pass/fail for every cell:
| Axis | Values | | ----------- | ----------------------------------------------- | | Zoom | 100%, 125%, 150%, 200% (WCAG 1.4.4) | | Breakpoint | sm, md, lg, xl (whatever the project ships) | | Locale | Every CJK locale + at least one Latin control | | Theme | Light, Dark (only if rendering depends on it) |
Re-test all cells after every layer added in Gate 4. The fix is only complete when every cell passes. Latin-locale regressions count as failures even if the CJK bug is resolved.
Present the full report (Sections 1–6 above) plus the proposed patch. State explicitly: "Awaiting approval before applying patch." Do not edit any file until the user confirms.
After approval, apply the patch exactly as proposed and re-run Gate 5 on the live build. Report the matrix again.
!important to win a specificity contest. Use thedoubled-class trick (.foo.foo) instead.
:where(...). It collapsesspecificity to 0 and the override will silently lose.
word-break: keep-all to code, pre, kbd, orsamp. Always carve out monospace contexts.
word-break: keep-all to Japanese or Chinese text.They have no inter-word spaces, so keep-all removes every break opportunity and causes overflow. Scope the rule with :lang(ko).
string as Unicode codepoints. Hidden characters (NBSP, ZWJ, soft hyphens) are common and easy to miss.
Long NBSP runs prevent wrapping entirely and cause overflow on narrow screens.
max-width or width to "fix" wrapping. Itmasks the cascade bug and breaks at other breakpoints.
<br> for wrap control. It does not survive translationlength variance and breaks the moment another locale is added.
edits. DevTools changes don't capture cssinjs injection order.
remain usable at 200%, and CJK breaks often only appear when zoomed.
reported one. The same string can wrap differently across ko, ja, and zh.
en, de, fr,es, …). German is ~30% longer and is the canonical regression case for headline wrapping.
part of this audit. Stay scoped to the bug.
uses Tailwind utilities, prefer text-balance over inline style={{ textWrap: 'balance' }}.
Common bad outputs:
word-break: keep-all globally without exemptingcode / pre — breaks code blocks across the entire app.
word-break: keep-all unscoped (e.g., on body) in aproduct that also ships ja / zh — those locales have no spaces, so whole clauses become unbreakable and overflow narrow screens.
:where(.ant-typography) — specificitydrops to 0, the library rule wins, and the "fix" silently has no effect.
text-wrap: balance without fixing the underlying cascade— balances the broken text into a different broken layout.
wrap on narrow screens, causing horizontal overflow.
English one) — the bug persists in the actual locale that has the problem.
200%.
<br> to force a break — works once, breaks the moment copychanges or another locale is added.
max-width to dodge the issue — masks the cascadebug and reintroduces overflow at unrelated breakpoints.
checking the cascade for a missing rule.
or ZWJ that would have explained the symptom in seconds.
Input:
A single Korean <h1> in a plain Tailwind project breaks as 이동하세 / 요 at the md breakpoint. No UI library is in use. Project has no global word-break rule.
Output:
ko, breakpoint md (768px), Chrome123, zoom 100%, line broken as 이동하세 / 요.
U+C774 U+B3D9 U+D558 U+C138 U+C694(no hidden characters). Computed word-break: normal (UA default). Cascade winner: user-agent stylesheet.
word-break: normal allows breaks between any two CJK syllables.
needed.
src/styles/globals.css:css :lang(ko) { word-break: keep-all; overflow-wrap: break-word; } code, pre, kbd, samp { word-break: normal; overflow-wrap: normal; }
ko and en.
Input:
A SaaS admin portal's Korean login hero shows three rotating slogans built with Ant Design <Typography.Title>. Slogan 2 reads "더 스마트하게 구축하고, 더 빠르게 이동하세요" and breaks as "이동하세" / "요" at lg. Project uses Tailwind 3 + Ant Design 5. Hero element is src/pages/login/index.tsx. Translation key login.hero.title2 lives in public/locales/ko/common.json.
Output:
ko, breakpoint lg (1024px), Chrome123, zoom 100% and 150%, line broken as 이동하세 / 요. Bug reproduces only in ko; en renders fine.
NBSP / ZWJ / soft hyphens. Computed style on the <h1> shows word-break: break-word winning from .ant-typography (specificity 0,1,0), overriding the project's body { word-break: keep-all } (specificity 0,0,1). text-wrap is wrap (default).
body) and missing the monospaceexemption; it must move to :lang(ko).
cssinjs rule wins over body.text-wrap: wrap will orphan 더 onthe previous line because the natural break lands one word short.
더 빠르게 이동하세요 is semantically a single unitand must move together.
each.
src/index.css (replaces the existing unscoped body rule): css /* Korean-scoped text wrapping */ :lang(ko) { word-break: keep-all; overflow-wrap: break-word; } code, pre, kbd, samp { word-break: normal; overflow-wrap: normal; } /* Ant Design override — doubled-class beats single-class without !important */ .ant-typography.ant-typography:lang(ko) { word-break: keep-all; overflow-wrap: break-word; }
src/pages/login/index.tsx (hero <Title>, three places): tsx <Title level={1} className="!mb-3 text-4xl md:text-5xl lg:text-6xl text-balance" style={{ color: 'var(--color-heading)' }} >
public/locales/ko/common.json: diff
+ "title2": "더 스마트하게 구축하고, 더\u00a0빠르게\u00a0이동하세요"
| Cell | Result | | ----------------------------- | --------- | | 100% / lg / ko / dark | pass | | 125% / lg / ko / dark | pass | | 150% / lg / ko / dark | pass | | 200% / lg / ko / dark | pass | | 100% / md / ko / dark | pass | | 100% / xl / ko / light | pass | | 100% / lg / en / dark (control) | pass |
FAST MODE (only if explicitly requested):
breakpoint, plus 150% as a WCAG sanity check.
acceptable in fast mode; fixes without diagnosis are not.
Other measured skills in the registry, with their headline benchmark lift.