Visual polish
Skill fernando-bertholdo/4-successful-AI-life/plugins/ui-excellence/skills/foundations/visual-polish
Curated Claude Code plugin marketplace — opinionated, production-ready skills for AI-assisted work, focused on craft, rigor, and practical excellence.
npx -y skills add fernando-bertholdo/4-successful-AI-life --skill visual-polishAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when refining interface details (text wrapping, spacing, animations, shadows, alignment) to compound small visual improvements into polished, responsive experiences following Jakub Krehel's principles.
SKILL.md
14.5 KB, as published. Nobody here has run it
UI Visual Polish
Overview
Great interfaces rarely come from a single thing—it's usually a collection of small, thoughtful details that compound into a cohesive, polished experience. This skill documents techniques for visual refinement: text wrapping strategies, balanced border radius hierarchies, contextual animations, optical alignment, and shadow composition. Each detail is intentional and measurable.
When to Use / When NOT to Use
Use When:
- Refining typography presentation (headings, body text, dynamic numbers)
- Designing nested UI components with multiple layers
- Adding entrance/exit animations to modals, tooltips, or lists
- Replacing solid borders with depth-aware shadows
- Aligning buttons with icons or misaligned visual elements
- Optimizing animations for user interruption (mid-toggle, mid-drag)
- Adding micro-interactions (copy-to-clipboard, status toggles)
- Ensuring consistent visual hierarchy across the interface
Do NOT Use When:
- Building motion-heavy animations (use dedicated motion library docs)
- Solving structural layout issues (use CSS Grid/Flexbox guides)
- Implementing accessibility requirements (separate A11y guide)
- Addressing performance bottlenecks (use optimization guide)
- Creating brand identity from scratch (use design system docs)
Core Techniques
Surfaces & Layout
1. Text Wrapping Strategy
Balance text distribution and reading flow with CSS text-wrap:
-
text-wrap: balance— For headings- Distributes text evenly across lines
- Prevents orphaned words at line ends
- Use once per heading (computed once at render)
- Best for titles, hero text, short high-impact copy
-
text-wrap: pretty— For body text- Similar to
balancebut slower algorithm - Better for longer paragraphs
- Reduces hyphenation and ugly breaks
- May slightly reflow on resizes
- Similar to
Implementation:
h1, h2, h3 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}
2. Concentric Border Radius
Create visual hierarchy in nested elements by proportional radius scaling.
Formula: outer_radius = inner_radius + padding
Example:
- Inner element:
border-radius: 12px - Padding:
8px - Outer container:
border-radius: 20px(12 + 8)
This creates balanced visual nesting and guides the eye through component hierarchy without explicit borders.
.card {
border-radius: 20px;
padding: 8px;
}
.card-inner {
border-radius: 12px;
padding: 8px;
}
.card-content {
border-radius: 4px;
}
3. Shadows Over Borders
Replace flat border declarations with layered shadows for depth and background flexibility.
Three-Layer Shadow Composition:
box-shadow:
0px 0px 0px 1px rgba(0, 0, 0, 0.06), /* Outline */
0px 1px 2px -1px rgba(0, 0, 0, 0.06), /* Soft inner shadow */
0px 2px 4px 0px rgba(0, 0, 0, 0.04); /* Ambient shadow */
/* Dark mode: use white with opacity */
box-shadow:
0px 0px 0px 1px rgba(255, 255, 255, 0.1),
0px 1px 2px -1px rgba(255, 255, 255, 0.06),
0px 2px 4px 0px rgba(255, 255, 255, 0.04);
/* Hover state: increase opacity */
transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
Why: Shadows adapt to any background; solid borders limit color palette and feel flat.
4. Image Outlines
Apply subtle outlines to images for visual containment and depth.
img {
outline: 1px solid rgba(0, 0, 0, 0.1);
outline-offset: -1px;
}
/* Dark mode */
@media (prefers-color-scheme: dark) {
img {
outline-color: rgba(255, 255, 255, 0.1);
}
}
Creates consistent visual framing without relying on parent backgrounds.
Typography
5. Font Smoothing (macOS)
macOS applies subpixel antialiasing by default, making text appear heavier than intended on light backgrounds.
Solution: Apply -webkit-font-smoothing: antialiased (or Tailwind's antialiased class) at layout root.
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
Effect: Produces thinner, crisper rendering of light text on macOS; no effect on other OS.
6. Tabular Numbers
Prevent number shifting when values update by using fixed-width numerals.
.counter, .price, .metric {
font-variant-numeric: tabular-nums;
/* or Tailwind: class="tabular-nums" */
}
When to use:
- Dashboards with live-updating metrics
- Price displays with dynamic values
- Counters and timers
- Financial tables
Caveat: Some fonts (e.g., Inter) alter numeral appearance with tabular-nums. Test in your font.
Animation Details
7. Contextual Icon Animations
Animate icon transitions (copy→check, eye→eye-off) with opacity, scale, and blur.
Technique:
- Opacity: 0 → 1 (fade in)
- Scale: 0.25 → 1 (grow from center)
- Blur: 4px → 0px (sharpen as it enters)
- Duration: 300ms–400ms
Why motion library preferred: CSS transitions alone lack spring easing for natural deceleration.
Example (Framer Motion syntax):
<AnimatePresence mode="wait">
{isCopied ? (
<motion.div
key="check"
initial={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
exit={{ opacity: 0, scale: 0.75 }}
transition={{ type: "spring", stiffness: 200, damping: 10 }}
>
<CheckIcon />
</motion.div>
) : (
<motion.div
key="copy"
initial={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
exit={{ opacity: 0, scale: 0.75 }}
transition={{ type: "spring", stiffness: 200, damping: 10 }}
>
<CopyIcon />
</motion.div>
)}
</AnimatePresence>
8. Interruptible vs Fixed Animations
CSS Transitions (for interactions):
- Interpolate toward latest state
- Support interruption (user changes intent mid-animation)
- Best for: toggles, hovers, drag interactions
- User interrupts frequently; animations must retarget
Keyframe Animations (for sequences):
- Run fixed, predetermined timelines
- Do not retarget mid-sequence
- Best for: page-load sequences, one-time reveals
- Non-interruptible animations feel broken
Decision Rule: Use transitions for user interactions; keyframes for deterministic sequences.
/* ✅ Transition: interruptible */
.toggle {
transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.toggle:hover {
transform: scale(1.05);
}
/* ✅ Keyframe: deterministic */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
animation: slideIn 800ms cubic-bezier(0.4, 0, 0.2, 1);
}
9. Enter Animations with Stagger
Combine opacity, blur, and vertical translation for smooth entrances.
Single Block (headings, descriptions):
@keyframes enterBlock {
from {
opacity: 0;
filter: blur(5px);
transform: translateY(8px);
}
to {
opacity: 1;
filter: blur(0);
transform: translateY(0);
}
}
.enter-block {
animation: enterBlock 800ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
Staggered (buttons, list items):
- Sectional stagger: ~100ms delays between groups
- Word-level stagger: ~80ms delays between individual items
{buttons.map((btn, i) => (
<motion.button
key={i}
initial={{ opacity: 0, filter: "blur(5px)", y: 8 }}
animate={{ opacity: 1, filter: "blur(0px)", y: 0 }}
transition={{
duration: 0.8,
delay: i * 0.1, // Stagger by 100ms
ease: "easeOut"
}}
>
{btn.label}
</motion.button>
))}
10. Exit Animations (Subtle)
Exits should be less prominent than entrances—they're farewell states.
Options:
- Full exit:
calc(-100% - 4px)(slide out past container edge + padding) - Subtle exit: Fixed
-12pxoffset (gentle departure)
Combine with opacity fade and maintain directional motion:
@keyframes exitSubtle {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-12px);
}
}
.exit-subtle {
animation: exitSubtle 400ms ease-in forwards;
}
Interaction Details
11. Scale-on-Press Feedback
Provide tactile feedback when buttons are pressed.
Rule: Scale exactly 0.96 (never below 0.95).
button {
transition: transform 100ms cubic-bezier(0.4, 0, 0.2, 1);
}
button:active {
transform: scale(0.96);
}
Values below 0.95 feel exaggerated; 0.96 is subtle and satisfying.
12. Optical vs Geometric Alignment
Geometric alignment doesn't always look correct visually. Adjust spacing for optical correctness.
Common case: Buttons with text + icon
- Icon is lighter visually, needs less padding
- Text carries weight, needs more padding
.button-with-icon {
display: flex;
align-items: center;
padding-left: 12px; /* Icon side: less padding */
padding-right: 16px; /* Text side: more padding */
}
Best practice: Fix alignment in the SVG itself to avoid container adjustments.
13. Minimum Interactive Hit Area
Ensure all interactive elements are at least 40×40 pixels (mobile accessibility standard).
button, a, input[type="checkbox"] {
min-width: 40px;
min-height: 40px;
}
Performance & Best Practices
14. Skip Page-Load Animations
Avoid jarring animations on first paint.
<AnimatePresence initial={false}>
{/* Animations only on state changes, not mount */}
</AnimatePresence>
15. Transition Property Specificity
Never use transition: all—specify exact properties.
/* ❌ Avoid */
.element {
transition: all 200ms ease;
}
/* ✅ Specific */
.element {
transition: background-color 200ms ease, transform 200ms ease;
}
Reduces unnecessary repaints and makes intent clearer.
16. will-change Usage
Reserve will-change for GPU-compositable properties only:
transformopacityfilter
.animated-element {
will-change: transform, opacity;
}
Do NOT use on box-shadow, background-color, or layout properties—they cannot be GPU-accelerated.
Quick Reference Table
| Technique | Property/Value | Use Case | Duration |
|---|---|---|---|
| Text Balance | text-wrap: balance | Headings | N/A (layout) |
| Text Pretty | text-wrap: pretty | Body paragraphs | N/A (layout) |
| Border Radius | outer = inner + padding | Nested components | N/A (layout) |
| Shadow Depth | 3-layer composition | Borders, cards | N/A (static) |
| Font Smoothing | -webkit-font-smoothing: antialiased | macOS rendering | N/A (static) |
| Tabular Nums | font-variant-numeric: tabular-nums | Counters, prices | N/A (static) |
| Icon Animate | Opacity 0→1, Scale 0.25→1, Blur 4→0px | Toggle icons | 300–400ms |
| Enter Animation | translateY(8px), blur(5px), opacity(0) | Staggered reveals | 800ms + stagger |
| Exit Animation | translateY(-12px), opacity(0) | Dismissals | 400ms |
| Scale Press | scale(0.96) | Button press feedback | 100ms |
| Hit Area | Min 40×40px | All interactive | N/A (layout) |
Review Checklist
Before shipping a refined UI component, validate:
-
Typography
- Headings use
text-wrap: balance - Body text uses
text-wrap: pretty - Font smoothing applied at layout root (
-webkit-font-smoothing: antialiased) - Dynamic numbers use
tabular-numsif value changes
- Headings use
-
Spacing & Hierarchy
- Concentric border radius follows formula (
outer = inner + padding) - Shadows replace flat borders (3-layer composition)
- Image outlines applied consistently (
outline: 1px solid rgba(0, 0, 0, 0.1)) - Optical alignment verified (text + icon buttons checked)
- Concentric border radius follows formula (
-
Animation
- Enter animations use stagger (button delays ~100ms)
- Exit animations are subtle and directional
- Icon toggles animate: opacity, scale, blur
- Animations are interruptible (CSS transitions for interactions)
-
initial={false}on AnimatePresence (skip page-load)
-
Interaction
- Buttons scale to exactly
0.96on press - All interactive elements ≥40×40px
-
transitionproperty is specific (notall) -
will-changeused only fortransform,opacity,filter
- Buttons scale to exactly
-
Accessibility & Performance
- Color contrast meets WCAG AA
- Motion respects
prefers-reduced-motion - Animations don't block critical rendering path
- No jank on 60fps target devices
Common Mistakes
❌ Flat Borders Instead of Shadows
/* ❌ Feels flat, limits backgrounds */
border: 1px solid #ccc;
/* ✅ Depth-aware, adapts to any background */
box-shadow:
0px 0px 0px 1px rgba(0, 0, 0, 0.06),
0px 1px 2px -1px rgba(0, 0, 0, 0.06),
0px 2px 4px 0px rgba(0, 0, 0, 0.04);
❌ Animation Without Interruption
/* ❌ Feels broken if user changes intent mid-animation */
animation: spin 2s linear infinite;
/* ✅ Retargets on toggle; smooth interruption */
transition: transform 200ms ease;
❌ Over-Scaled Press Feedback
/* ❌ Feels exaggerated */
button:active { transform: scale(0.90); }
/* ✅ Subtle and satisfying */
button:active { transform: scale(0.96); }
❌ Text Clipping Due to Missing Wrapping
/* ❌ Creates orphans and awkward breaks */
h1 { /* no text-wrap */ }
/* ✅ Balanced distribution */
h1 { text-wrap: balance; }
❌ Hardcoded Icon Padding
/* ❌ Looks misaligned */
button { padding: 12px 12px 12px 12px; }
/* ✅ Optically correct */
button { padding-left: 10px; padding-right: 14px; }
❌ No Stagger on List Reveals
/* ❌ All items enter at once; feels abrupt */
{items.map(item => <Item />)}
/* ✅ Staggered entrance; feels intentional */
{items.map((item, i) => (
<Item delay={i * 0.08} />
))}
❌ Numbers Shifting in Tables
/* ❌ Metrics jump as values update */
.metric { font-family: "Inter"; }
/* ✅ Numbers stay aligned */
.metric { font-variant-numeric: tabular-nums; }
References & Inspiration
- Jakub Krehel's "Details that make interfaces feel better" — Core philosophy
- Framer Motion docs for spring easing and AnimatePresence patterns
- WCAG accessibility guidelines for color contrast and motion preferences
- CSS spec:
text-wrap,box-shadow,font-variant-numeric
Version: 1.0.0 Last Updated: 2026-03-30 Status: Active