ui-polish
The Last Ten Percent
When a UI works and still feels unfinished, assume the missing thing is a state, not a value. The overwhelmingly common cause is that some interactive element has a hover but no focus, a focus but no pressed state, no disabled treatment, or no answer for the second between the click and the response. Run the state sweep before you touch a single pixel value. Only once every interactive element has a complete row do you go looking for the optical nudges, the hairline greys, and the cursor that lies — and those are what this skill owns, because nobody else does.
This skill is the residue and nothing more. If you can name the dimension — shadow, radius, elevation, blur, gap, density, breakpoint — it is out by its own description, and surfaces owns the plane while spacing owns the gaps. Two more edges are worth naming, because they are the ones people reach for here first: the construction of a focus ring is a11y's, and the colour of a text selection is color's.
Find where the project keeps its interaction states before adding one. A :hover written at a call site in a codebase that has a Button component with variants is not polish, it is a fork. Look for the component layer, a data-state convention (Radix, Base UI), a cva/tv variant map, or a global stylesheet of element defaults, and put the missing state where its siblings already live. Corrections belong at the lowest layer that covers every instance.
Quick Reference
| You need | Open |
|---|---|
| The full sweep, in order, to run over a finished component or page | references/checklist.md — open it when the request is "go over this" and nothing specific is named. |
| The nudge for a specific shape, glyph, or label pair | references/optical-corrections.md — open it when something is mathematically centred and still looks wrong. |
Core Principles
- Fill the state matrix before anything else. Every interactive element owes five states — rest,
:hover,:focus-visible,:active,:disabled— plus a loading treatment wherever the action is async. Missing states are why a UI feels dead while every value in it is defensible. The exception is a link inside prose, which does not need an:activestate distinct from its hover.