Ground My UI
Most "ugly" UI is not a taste problem. It is code quietly lying to a
framework: a variant="danger" the badge never styled, a #header-right
the card never read, a var(--scale-size-13) that does not exist, a
display: flex on an element the framework positions absolutely. None of
it errors. The build passes, the tests pass, and the page renders as a
pile of half-applied intentions.
So this skill does not start with taste. It starts by reading the
installed component library, token file, icon set, and translation catalog
that the page actually runs against — then rebuilds on what is provably
there.
The Prime Directive (family rule)
No prop, slot, event, enum value, token, icon name, or i18n key that
you have not read in the installed source. Not remembered from
training, not inferred from a sibling component, not copied from the
library's public docs — read from the copy on this disk, at a
file:line you can cite. Design decisions come after the contract
audit, never before.
Hard rules
- Look at the rendered thing — before and after. A diagnosis from
source alone is a guess. Screenshot it, measure the real box model,
read the console. If you cannot render it, say so and mark every
finding
UNVERIFIED.
- Never enter credentials to reach the screen. Ask the user to log
in, or to hand you a reachable URL. Waiting is cheaper than a wrong
assumption about what the page looks like.
- Silence is the enemy. The defects that matter here throw nothing.
Hunt them with the catalog in
references/silent-failures.md instead
of waiting for an error.
- The host system's primitives before a new dependency. A design
system you must obey plus a library you bolted on is two design
systems. New packages need a written justification (what the host
cannot do) and a cost line (bundle size, review/audit rules, upgrade
surface). Check what already ships in the host first — it is routinely
more than the team remembers.
- Scope discipline, with one exception. Do not restyle neighbouring
screens. Do fix the destinations your screen links to — a beautiful
card whose button leads to the wrong page is not finished.
- Delete redundancy instead of restyling it. If two elements do the
same job, the fix is one element, not two prettier ones.
Progress checklist
Copy this into your response and check items off:
Ground Progress:
- [ ] Phase 0: Rendered — screenshot, box model measured, console read, defect list written
- [ ] Phase 1: Contract source located on disk (library, tokens, icons, i18n)
- [ ] Phase 2: Contract audit — every prop/slot/enum/token/icon/key verified, ledger cited
- [ ] Phase 3: Ownership audit — layout/scroll owner identified, selector can win
- [ ] Design Gate — IA + visual plan presented, dependency decision stated
- [ ] Phase 4: Rebuilt on verified contracts only
- [ ] Phase 5: Contracts locked into tests (each test names the defect it prevents)
- [ ] Phase 6: Re-verified rendered — clean console, responsive, focus, reduced motion
Phase 0 — Render it first
Open the actual screen. Then, in this order:
- Screenshot it at a realistic viewport.
- Measure, do not eyeball: dump width/left/display/gap/padding/
max-width/overflow and
scrollHeight vs clientHeight for the page
wrapper, the scroll container, and each major block. Misalignment you
can see is a symptom; the number that explains it is the finding.
- Read the console — and treat the buffer as stale until proven
otherwise. Reload, then re-read, before blaming a live error.
- Write the defect list in plain language: "first tile's icon is a
blank square", "the budget card is narrower than the row above it",
"the button shows
global.default.refresh". Each line becomes a row in
the ledger and, later, a test.
Deliverable: a numbered visual defect list. No causes yet.
Phase 1 — Locate the contract source
Find the truth on disk, not on the web — the installed version is the one
that renders. Per-ecosystem locations and enumeration commands are in
references/contract-discovery.md.
Four artifacts to pin down, each with a path:
| Artifact |
You need to be able to answer |
| Component library |
What props/slots/events does this component declare, and what enum values are actually styled? |
| Design tokens |
Does --x exist? Is it a spacing step or a type size? |
| Icon set |
Is foo-bar a real icon name in this version? |
| i18n catalog |
Does this key resolve, in every shipped locale? |
Also pin the host version (lockfile, composer.lock, Package.resolved,
pubspec.lock). A contract answer is only true for a version.
Phase 2 — Contract audit
For every component the screen uses, verify each attribute the code passes
against the declaration you located. Cite file:line. Record in the ledger
(references/audit-ledger.md) — one row per
defect: what the code says, what the contract says, what the user sees.
The high-yield checks, in cost-benefit order:
- Enum props — is that exact string in the union? Off-list values are
the single most common cause of "unstyled but present".
- Slot names — exact spelling and case.
headerRight ≠ header-right.
A wrong slot name renders nothing, silently.
- Required props — omitted required props degrade quietly in
production builds.
- Token existence and kind — a spacing token used as a font size is
both wrong and invisible.
- Icon names — verify against the installed icon directory listing.
- i18n keys — resolve every key the screen references, in all
locales; a key present in
en and missing in de ships a raw key.
- Deprecations — a component that still works today but is scheduled
for removal is a defect with a due date.
Then close the loop the other way: grep the screen for every key, icon,
and token it references and confirm each resolves. Auditing only what you
suspect leaves the rest of the file unaudited.
Phase 3 — Ownership audit
This is where the biggest silent defects live, and it is the phase most
often skipped. Method in
references/css-ownership.md. Answer three
questions:
- Who owns layout? Framework wrapper components frequently position
their root absolutely and delegate scrolling and padding to an inner
element. Layout applied to the wrapper is inert — your gaps and
max-widths do nothing and nothing warns you. Style an element you own.
- Can your selector win? Compute specificity against the library rule
you are overriding, and check load order for ties. Scoped-style
attribute selectors (
[data-v-…]), CSS layers, and shadow boundaries
all change the answer. Prefer an extra owned ancestor class over
!important.
- What does the library already impose? Default
max-width, default
margins, and content padding fight hand-written grids. Reconcile
explicitly — one owner per axis.
Design Gate
Only now, with the defect ledger in hand, decide what the screen should
be. Present in chat, briefly:
- Information architecture — what the viewer needs first; what gets
cut. Prefer deleting a redundant element over restyling it: a repeated
page title, or a "quick actions" row pointing where the tiles already
point, is noise the redesign should remove.
- Visual plan — the host system's primitives you will use, and which
ledger rows each change closes.
- Dependency decision — stated either way. If a new package is
warranted, name what the host cannot do. If not, say so plainly rather
than staying silent: "no new dependency; the accessible form for
≤5-category part-to-whole is a stacked meter, which is CSS."
- Accessibility intent — what carries meaning besides colour.
Ask once. If the user cannot respond (headless), proceed with the
lowest-risk reading and tag the design choices UNCONFIRMED.
Phase 4 — Rebuild
Build against verified contracts only. While rebuilding:
- Use the library's own escape hatches (icon slots, size props,
full-width modifiers) rather than re-implementing them with custom CSS.
- Route every number and date through the platform's formatters — raw
floats and ISO timestamps in a cell read as broken, and a dead
formatDate helper nobody called is a tell that this was never wired.
- Make the primary target the whole element, and use a real link or button
so keyboard and middle-click work.
- Never encode meaning in colour alone — put the value or share in text.
- Give hover motion a
prefers-reduced-motion opt-out and keep a visible
:focus-visible ring.
- Leave a comment where you fight a framework default, saying why — the
next person will otherwise "clean up" your specificity fix.
Phase 5 — Lock the contracts in
An audit that lives only in chat gets re-broken next sprint. Write tests
that fail on reintroduction — patterns per ecosystem in
references/contract-tests.md. Each test
names the defect it prevents in a comment. Cheap, high-value asserts:
- Every enum value used appears in the library's allowed set.
- No non-existent slot name; no deprecated component name.
- Every
font-size uses a type token, not a spacing token.
- Every i18n key the screen references resolves in every locale.
- Every route the screen navigates to is registered.
Then run the whole suite, not just yours. Distinguish failures you
caused from failures already there, and say which is which.
Phase 6 — Re-verify rendered
Repeat Phase 0 against the rebuild and compare like for like:
- Screenshot at wide and narrow; no horizontal scroll, no orphaned column.
- Re-measure: the numbers that explained each defect must have moved.
- Console clean — after a reload, so you are not reading stale errors.
- Keyboard: tab to the interactive elements, confirm the focus ring.
- Click through at least one link you claimed to fix.
Report inline: the defect list from Phase 0 with each row marked
FIXED / PARTIAL / OUT-OF-SCOPE, the root-cause classes found, the
dependency decision, and any pre-existing failures you deliberately left
alone.
When things go wrong
| Situation |
Response |
| Cannot reach the screen (auth wall) |
Ask the user to log in; never type credentials. Audit source meanwhile, mark findings UNVERIFIED until rendered |
| Library source not on disk (CDN/binary only) |
Use the version-pinned published types/docs, say the evidence is second-hand, and verify each contract in the browser instead |
| Contract is a private/internal API |
Prefer a public alternative; if none, isolate the usage in one place with a comment naming the risk |
| Override keeps losing to library CSS |
Ownership audit, not !important — check specificity ties, load order, scoped attributes, layers, shadow DOM |
| Console errors persist after the fix |
Reload and re-read; buffers keep stale entries. Then confirm the DOM actually contains the offender before chasing it |
| The defect is in the data, not the UI |
Render an explicit empty/fallback state — a blank primary cell reads as a rendering fault, not as missing data |
| Screen links to a half-built destination |
Fix the wiring so the link lands correctly; report the destination's own defects instead of silently absorbing them |
| Test deps or lockfile wiped by the host build |
Reinstall after the build, not before; note the ordering trap so the next run does not repeat it |
| Design system genuinely lacks the primitive |
Then a dependency is justified — state what is missing, the cost, and keep the host's tokens for colour and spacing |
1---2name: ground-my-ui3description: Diagnoses and rebuilds UI that looks broken, ugly, or off-system by verifying every contract the code touches against the INSTALLED source on disk — component props, slot names, enum values, design tokens, icon names, i18n keys, and which element actually owns layout — before any redesign is attempted. Built for the failure class that raises no error: an invalid variant renders unstyled, a misspelled slot renders nothing, an undefined custom property voids the whole declaration, a missing translation key prints itself. Stack-agnostic (React, Vue, Svelte, Angular, Twig/PHP, Rails, SwiftUI, Compose, Flutter, .NET) and works inside any design system, then locks the verified contracts into tests. Use when a screen looks wrong, unstyled, misaligned, or inconsistent with its design system, when components render blank or ignore their props, when raw translation keys or unformatted values appear in the UI, when asked to redesign or restyle a view, or when the user mentions ground-my-ui or /ground-my-ui.4license: MIT5---67# Ground My UI89Most "ugly" UI is not a taste problem. It is code quietly lying to a10framework: a `variant="danger"` the badge never styled, a `#header-right`11the card never read, a `var(--scale-size-13)` that does not exist, a12`display: flex` on an element the framework positions absolutely. None of13it errors. The build passes, the tests pass, and the page renders as a14pile of half-applied intentions.1516So this skill does not start with taste. It starts by reading the17installed component library, token file, icon set, and translation catalog18that the page actually runs against — then rebuilds on what is provably19there.2021## The Prime Directive (family rule)2223> **No prop, slot, event, enum value, token, icon name, or i18n key that24> you have not read in the installed source.** Not remembered from25> training, not inferred from a sibling component, not copied from the26> library's public docs — read from the copy on this disk, at a27> `file:line` you can cite. Design decisions come *after* the contract28> audit, never before.2930## Hard rules31321. **Look at the rendered thing — before and after.** A diagnosis from33 source alone is a guess. Screenshot it, measure the real box model,34 read the console. If you cannot render it, say so and mark every35 finding `UNVERIFIED`.362. **Never enter credentials to reach the screen.** Ask the user to log37 in, or to hand you a reachable URL. Waiting is cheaper than a wrong38 assumption about what the page looks like.393. **Silence is the enemy.** The defects that matter here throw nothing.40 Hunt them with the catalog in41 [references/silent-failures.md](references/silent-failures.md) instead42 of waiting for an error.434. **The host system's primitives before a new dependency.** A design44 system you must obey plus a library you bolted on is two design45 systems. New packages need a written justification (what the host46 cannot do) and a cost line (bundle size, review/audit rules, upgrade47 surface). Check what already ships in the host first — it is routinely48 more than the team remembers.495. **Scope discipline, with one exception.** Do not restyle neighbouring50 screens. Do fix the destinations *your* screen links to — a beautiful51 card whose button leads to the wrong page is not finished.526. **Delete redundancy instead of restyling it.** If two elements do the53 same job, the fix is one element, not two prettier ones.5455## Progress checklist5657Copy this into your response and check items off:5859```60Ground Progress:61- [ ] Phase 0: Rendered — screenshot, box model measured, console read, defect list written62- [ ] Phase 1: Contract source located on disk (library, tokens, icons, i18n)63- [ ] Phase 2: Contract audit — every prop/slot/enum/token/icon/key verified, ledger cited64- [ ] Phase 3: Ownership audit — layout/scroll owner identified, selector can win65- [ ] Design Gate — IA + visual plan presented, dependency decision stated66- [ ] Phase 4: Rebuilt on verified contracts only67- [ ] Phase 5: Contracts locked into tests (each test names the defect it prevents)68- [ ] Phase 6: Re-verified rendered — clean console, responsive, focus, reduced motion69```7071## Phase 0 — Render it first7273Open the actual screen. Then, in this order:74751. **Screenshot** it at a realistic viewport.762. **Measure**, do not eyeball: dump width/left/display/gap/padding/77 max-width/overflow and `scrollHeight` vs `clientHeight` for the page78 wrapper, the scroll container, and each major block. Misalignment you79 can *see* is a symptom; the number that explains it is the finding.803. **Read the console** — and treat the buffer as stale until proven81 otherwise. Reload, then re-read, before blaming a live error.824. **Write the defect list** in plain language: "first tile's icon is a83 blank square", "the budget card is narrower than the row above it",84 "the button shows `global.default.refresh`". Each line becomes a row in85 the ledger and, later, a test.8687Deliverable: a numbered visual defect list. No causes yet.8889## Phase 1 — Locate the contract source9091Find the truth on disk, not on the web — the installed version is the one92that renders. Per-ecosystem locations and enumeration commands are in93[references/contract-discovery.md](references/contract-discovery.md).9495Four artifacts to pin down, each with a path:9697| Artifact | You need to be able to answer |98|----------|-------------------------------|99| **Component library** | What props/slots/events does this component declare, and what enum values are actually styled? |100| **Design tokens** | Does `--x` exist? Is it a spacing step or a type size? |101| **Icon set** | Is `foo-bar` a real icon name in this version? |102| **i18n catalog** | Does this key resolve, in every shipped locale? |103104Also pin the **host version** (lockfile, `composer.lock`, `Package.resolved`,105`pubspec.lock`). A contract answer is only true for a version.106107## Phase 2 — Contract audit108109For every component the screen uses, verify each attribute the code passes110against the declaration you located. Cite `file:line`. Record in the ledger111([references/audit-ledger.md](references/audit-ledger.md)) — one row per112defect: what the code says, what the contract says, what the user sees.113114The high-yield checks, in cost-benefit order:115116- **Enum props** — is that exact string in the union? Off-list values are117 the single most common cause of "unstyled but present".118- **Slot names** — exact spelling and case. `headerRight` ≠ `header-right`.119 A wrong slot name renders *nothing*, silently.120- **Required props** — omitted required props degrade quietly in121 production builds.122- **Token existence and kind** — a spacing token used as a font size is123 both wrong and invisible.124- **Icon names** — verify against the installed icon directory listing.125- **i18n keys** — resolve every key the screen references, in *all*126 locales; a key present in `en` and missing in `de` ships a raw key.127- **Deprecations** — a component that still works today but is scheduled128 for removal is a defect with a due date.129130Then close the loop the other way: grep the screen for every key, icon,131and token it references and confirm each resolves. Auditing only what you132suspect leaves the rest of the file unaudited.133134## Phase 3 — Ownership audit135136This is where the biggest silent defects live, and it is the phase most137often skipped. Method in138[references/css-ownership.md](references/css-ownership.md). Answer three139questions:1401411. **Who owns layout?** Framework wrapper components frequently position142 their root absolutely and delegate scrolling and padding to an inner143 element. Layout applied to the wrapper is *inert* — your gaps and144 max-widths do nothing and nothing warns you. Style an element you own.1452. **Can your selector win?** Compute specificity against the library rule146 you are overriding, and check load order for ties. Scoped-style147 attribute selectors (`[data-v-…]`), CSS layers, and shadow boundaries148 all change the answer. Prefer an extra owned ancestor class over149 `!important`.1503. **What does the library already impose?** Default `max-width`, default151 margins, and content padding fight hand-written grids. Reconcile152 explicitly — one owner per axis.153154## Design Gate155156Only now, with the defect ledger in hand, decide what the screen should157be. Present in chat, briefly:158159- **Information architecture** — what the viewer needs first; what gets160 cut. Prefer deleting a redundant element over restyling it: a repeated161 page title, or a "quick actions" row pointing where the tiles already162 point, is noise the redesign should remove.163- **Visual plan** — the host system's primitives you will use, and which164 ledger rows each change closes.165- **Dependency decision** — stated either way. If a new package is166 warranted, name what the host cannot do. If not, say so plainly rather167 than staying silent: "no new dependency; the accessible form for168 ≤5-category part-to-whole is a stacked meter, which is CSS."169- **Accessibility intent** — what carries meaning besides colour.170171Ask once. If the user cannot respond (headless), proceed with the172lowest-risk reading and tag the design choices `UNCONFIRMED`.173174## Phase 4 — Rebuild175176Build against verified contracts only. While rebuilding:177178- Use the library's own escape hatches (icon slots, size props,179 full-width modifiers) rather than re-implementing them with custom CSS.180- Route every number and date through the platform's formatters — raw181 floats and ISO timestamps in a cell read as broken, and a dead182 `formatDate` helper nobody called is a tell that this was never wired.183- Make the primary target the whole element, and use a real link or button184 so keyboard and middle-click work.185- Never encode meaning in colour alone — put the value or share in text.186- Give hover motion a `prefers-reduced-motion` opt-out and keep a visible187 `:focus-visible` ring.188- Leave a comment where you fight a framework default, saying *why* — the189 next person will otherwise "clean up" your specificity fix.190191## Phase 5 — Lock the contracts in192193An audit that lives only in chat gets re-broken next sprint. Write tests194that fail on reintroduction — patterns per ecosystem in195[references/contract-tests.md](references/contract-tests.md). Each test196names the defect it prevents in a comment. Cheap, high-value asserts:197198- Every enum value used appears in the library's allowed set.199- No non-existent slot name; no deprecated component name.200- Every `font-size` uses a type token, not a spacing token.201- Every i18n key the screen references resolves in every locale.202- Every route the screen navigates to is registered.203204Then run the **whole** suite, not just yours. Distinguish failures you205caused from failures already there, and say which is which.206207## Phase 6 — Re-verify rendered208209Repeat Phase 0 against the rebuild and compare like for like:210211- Screenshot at wide and narrow; no horizontal scroll, no orphaned column.212- Re-measure: the numbers that explained each defect must have moved.213- Console clean — after a reload, so you are not reading stale errors.214- Keyboard: tab to the interactive elements, confirm the focus ring.215- Click through at least one link you claimed to fix.216217Report inline: the defect list from Phase 0 with each row marked218**FIXED / PARTIAL / OUT-OF-SCOPE**, the root-cause classes found, the219dependency decision, and any pre-existing failures you deliberately left220alone.221222## When things go wrong223224| Situation | Response |225|-----------|----------|226| **Cannot reach the screen (auth wall)** | Ask the user to log in; never type credentials. Audit source meanwhile, mark findings UNVERIFIED until rendered |227| **Library source not on disk (CDN/binary only)** | Use the version-pinned published types/docs, say the evidence is second-hand, and verify each contract in the browser instead |228| **Contract is a private/internal API** | Prefer a public alternative; if none, isolate the usage in one place with a comment naming the risk |229| **Override keeps losing to library CSS** | Ownership audit, not `!important` — check specificity ties, load order, scoped attributes, layers, shadow DOM |230| **Console errors persist after the fix** | Reload and re-read; buffers keep stale entries. Then confirm the DOM actually contains the offender before chasing it |231| **The defect is in the data, not the UI** | Render an explicit empty/fallback state — a blank primary cell reads as a rendering fault, not as missing data |232| **Screen links to a half-built destination** | Fix the wiring so the link lands correctly; report the destination's own defects instead of silently absorbing them |233| **Test deps or lockfile wiped by the host build** | Reinstall after the build, not before; note the ordering trap so the next run does not repeat it |234| **Design system genuinely lacks the primitive** | Then a dependency is justified — state what is missing, the cost, and keep the host's tokens for colour and spacing |