Supercharge the Defaults
Don't settle for browser defaults:
- Checkboxes, radios, selects can be styled
- Custom controls feel more polished
- Maintain usability while improving aesthetics
Upgrade:
- Checkboxes: Custom design with brand colors
- Radio buttons: Better styling
- Dropdowns: Custom styling instead of native select
- File inputs: Hide and style label instead
Native Feel Is Behavior First
An interface feels native when it responds like it is under the user's hand. Visual styling helps only after the behavior is right. If feedback is late, gestures jump, layers teleport, or actions feel over-animated, no amount of blur, radius, or shadow will make the product feel polished.
Before adding visual finish, check:
- Press feedback appears immediately on pointer-down / active state
- Dragged objects stay attached to the pointer and preserve grab offset
- Moving surfaces can be interrupted and redirected
- Layers appear from the object or area that opened them
- High-frequency actions stay instant or nearly instant
- Reduced-motion users keep the same state clarity without large travel or bounce
Add Color with Accent Borders
- Short, colorful border adds pop to bland elements
- Top border on cards
- Left border on alerts/notifications
- Side border on active navigation items
- Adds visual interest without overwhelming
Decorate Your Backgrounds
Make backgrounds interesting:
- Subtle patterns
- Gradients
- Abstract shapes
- Geometric designs
Keep it subtle:
- Low contrast
- Doesn't compete with content
- Adds polish without distraction
Don't Overlook Empty States
Empty is not one state. The UI should explain why the surface is empty before it asks the user to do something. Five types need different treatment:
1. First-use: No data exists yet. Name the missing thing, explain why it's empty, give the primary next action. CTA should move setup forward: "Create task", "Import contacts", "Connect Stripe". Don't use the same generic empty state everywhere — the right CTA depends on what unlocks the screen.
2. Filtered-out: Data exists but current filters removed all visible results. Explain the active cause: "No orders match these filters", "No teammates found for 'design' in London." CTA undoes the cause: clear filters, change date range, remove search term. Never say "No orders yet" when 184 orders exist in the system.
3. Cleared-out: User had data, now doesn't (archived all cards, completed every task, deleted every saved view). Acknowledge the change: "All tasks completed", "No saved views left." Don't use first-use language — it sounds like the product forgot what just happened. Sometimes no CTA needed — acknowledgment is the point.
4. Permission-based: User lacks access. Say so plainly: "You don't have access to billing invoices." CTA: "Ask an admin to grant access." Retrying is useless here — don't hide this behind soft neutral language.
5. Error-adjacent: Request failed, app fell back to empty array. Don't pretend emptiness is normal. Say "We couldn't load invoices" with a retry action. The next action changes completely vs permissions — retrying makes sense here, requesting access doesn't.
Every empty state answers three questions:
- What is empty?
- Why is it empty?
- What can I do next?
Hide chrome that does nothing without data. On first-use, cleared-out, and zero-data states, strip the controls that only make sense once content exists: tab bars, filter dropdowns, sort menus, search inputs, bulk-action toolbars, pagination. Dead controls add clutter and imply the screen is broken. Leave only the empty-state message and its CTA. Exception: the filtered-out state keeps its filters/search, since the next action is to change or clear them.
Layout:
- Full-page empty states for first-use setup or major zero-data moments (can teach)
- Inline empty states for tables, panels, cards, filtered results (should mostly clarify)
Optical Alignment Over Geometric
Centering by the math (geometric alignment) is not the same as looking centered (optical alignment). The eye judges balance by visual mass, not by the bounding box, so shapes that are mathematically centered often look off. Trust your eye and nudge.
The usual offenders:
- Play / triangle icons in a circular button: a triangle's mass sits toward its flat left edge, so a geometrically centered triangle looks shifted left. Nudge it a few pixels right (
transform: translateX(1px)) so it reads as centered. - Glyphs and icons next to text: an icon's optical center rarely matches its box center. Align by how it looks beside the label, not by
vertical-alignalone. - Letters in a circular avatar/badge: a single initial usually needs a tiny upward nudge because letter baselines leave extra space below.
- Quotation marks, asterisks, and superscripts: hang them slightly outside the text block so the paragraph edge stays straight (optical margin).
/* Triangle play icon in a round button: shift right toward true center */
.play-button svg {
transform: translateX(1px);
}
How to do it: center geometrically first, then look. If it feels off, move it 1-2px toward where the visual weight is missing and check again. These nudges are small and per-component, there is no global rule, only "does it look centered." A measured-equal value that looks wrong is wrong.
Use Fewer Borders
Borders add visual noise:
- Use sparingly
- Often other methods work better
Alternatives to borders:
- Box shadow: Subtle outline without harsh line
- Different background color: Separates sections cleanly
- Extra spacing: More space between elements defines groups
When borders make sense:
- Defining boundaries of clickable areas (buttons)
- Dividing distinct sections (not just spacing)
Debugging Layout Overflow
Random horizontal scrollbar and no idea what's causing it? The culprit is often deeply nested — an image without max-width, a fixed-width container, a table, or invisible padding.
/* Add to stylesheet temporarily */
* {
outline: 1px solid red !important;
}
outline does not affect layout. Unlike border, it won't shift elements around or change box sizes. Every element gets a visible boundary, so you can immediately spot which one sticks out past the viewport.
// From DevTools console (goes away on refresh)
document.querySelectorAll("*").forEach((el) => {
el.style.outline = "1px solid red";
});
// Random colors to tell overlapping elements apart
document.querySelectorAll("*").forEach((el) => {
const hue = Math.floor(Math.random() * 360);
el.style.outline = `1px solid hsl(${hue}, 100%, 50%)`;
});
Common fixes once you find the culprit:
overflow: hiddenon the parent containermax-width: 100%on images or media elements- Remove hardcoded widths that exceed the viewport
- Check for negative margins or absolute positioning pushing elements off-screen
Reserve the Scrollbar Gutter
When a page toggles between having and not having a vertical scrollbar, the content area changes width by the scrollbar's thickness (~15px). That shift re-wraps and re-centers text, producing a visible horizontal jump, common when an async list finishes loading, a modal opens, or you navigate between a short page and a tall one.
Reserve the gutter on the root element so the layout width stays constant whether or not a scrollbar is present:
html {
scrollbar-gutter: stable;
}
The browser always leaves room for the scrollbar, so text never re-wraps when it appears. Use stable both-edges if you want symmetric padding on both sides of centered content. Apply it on the scroll container (html, or any element with overflow: auto/scroll), not on individual text blocks.
Think Outside the Box
Challenge your preconceptions about components:
Most people have fixed ideas about how components "should" look. Don't let conditioning limit your creativity.
Dropdowns:
- Don't default to boring list of links
- Don't hide everything in dropdowns - show popular options directly
- A dropdown is just a floating box — be creative with it
- Break it into sections with headings
- Use multiple columns for better organization
- Add supporting text or descriptions
- Include colorful icons for visual interest
Tables:
- Not just for data grids
- Don't force every table into sortable columns
- Combine related columns to introduce hierarchy
- Data + description in same cell works great
- Add images when it makes sense
- Use color to enrich data (status badges, priority indicators)
Radio Buttons:
- Don't settle for boring stack of labels with circles
- For important choices, use selectable cards instead
- Add icons, images, or illustrations
- Include descriptions or additional context
- Make the whole card clickable, not just the radio button
Forms:
- Multi-step instead of one long form
- Inline editing instead of edit forms
- Progressive disclosure
- Break long forms into manageable sections
General approach:
- Question every "this is how it's done" assumption
- Try unconventional layouts
- Ask: "Does this pattern serve my users, or is it just convention?"
Live Tuning
Expose important parameters so they can be changed while the result is visible. A control panel for duration, easing, spacing, blur, shadow, position, scale, or frequency creates a tight loop between action and perception.
Typing a value, rebuilding, and navigating back interrupts learning. Direct manipulation lets you sweep through a range, find thresholds, compare combinations, and develop intuition about what each parameter actually does.
Useful live-tuning rules:
- expose the smallest set of parameters that meaningfully changes the result;
- choose ranges that prevent accidental nonsense;
- show current values so discoveries can be reproduced;
- tune related parameters together, then test the final values without the panel;
- keep the playground disposable so exploration remains cheap.
Style Details
Consistency is evidence that the interface was considered as a system. Repeated parts should share a construction language even when their exact values differ.
Check these families together:
- icon fill, stroke, line cap, corner character, and optical size;
- control height, padding, focus treatment, and disabled treatment;
- surface radius, border contrast, shadow softness, and elevation;
- material qualities such as opacity, blur, noise, and reflection;
- active, hover, pressed, selected, and unavailable states.
Transparent or inset borders often adapt to changing backgrounds more naturally than a fixed opaque stroke. Use them deliberately: a hairline should clarify an edge, not compensate for weak spacing or contrast. Fewer border strategies generally produce a more settled interface.
Refining Today
The task interface begins with several individually reasonable decisions that do not form one visual language. Icons mix styles and stroke weights. The toolbar feels like a heavy container. Dividers follow different rules. Labels lack a useful hierarchy, and nearby elements align to too many invisible spines.
The refinement process:
- standardize icon construction, stroke weight, color, and optical size;
- reduce the number of vertical alignment rules;
- simplify the toolbar and remove unnecessary container weight;
- place the primary action where its importance is unmistakable;
- tighten padding while preserving comfortable targets;
- use a small semantic token set for task categories and states;
- make separators consistent or remove them when spacing is sufficient.
None of these changes is dramatic alone. Together they reduce visual drag and make the product feel closer to a native, dependable tool.
Refining Presscut
The dashboard carries redundant explanation, generous spacing that weakens relationships, and decorative typography in places where data needs clarity. Metrics and labels compete instead of forming compact units.
The strongest changes remove the subtitle when the heading already provides the context, tighten metric groups, limit the expressive typeface to moments where it has a clear role, and standardize spacing between repeated structures. Data should not be decorated into importance; hierarchy, comparison, and good numeric typography should let it speak.
Interface Kit Overview
Interface Kit is a browser-based visual design tool for editing a live application. A designer selects an element, adjusts its visual properties, sees the result immediately, and copies a structured description of the changes for a coding agent.
The Style controls cover surface qualities such as background, border, radius, shadow, blur, and opacity. Typography controls cover family, size, weight, line height, letter spacing, style, alignment, and color. The useful model is not “design replaces code”; it is “direct manipulation discovers values, then a precise handoff carries them into code.”
Interface Kit Critique
The critique chapter is presented as a walkthrough of the alpha version. Its role in the larger collection is methodological: put the tool itself through the same noticing and refinement process used on product interfaces. Evaluate whether the controls expose the right concerns, whether the hierarchy supports fast tuning, and whether the output closes the loop with implementation.
Interface Kit Refinements
The follow-up walkthrough revisits issues identified in the critique and refines the tool. This demonstrates that a design instrument also needs conceptual depth: the panel’s organization, control density, interaction speed, and generated handoff all affect the quality of work people can produce with it.
Interface Kit Details
The implementation supports both utility-class workflows and raw CSS. That matters because the interface should express design intent without forcing every codebase into one styling model. The broader direction is a bridge between visual tuning and agent-assisted implementation, with live feedback on one side and usable implementation instructions on the other.
Checklist
- Style default form elements
- Optically align icons/glyphs (nudge play triangles right, etc.) instead of trusting geometric center
- Add accent borders for interest
- Empty states match their type (first-use, filtered, cleared, permission, error)
- Hide non-functional chrome (tabs, filters, sort, search, pagination) on first-use/cleared/zero-data states; keep filters only on the filtered-out state
- Use fewer borders (try alternatives first)
- Add subtle background decoration
- Use
overscroll-behavior: noneto avoid page bounce
Icons
Icon polish is weight, state, sizing, and direction: the details that make an icon sit naturally next to text instead of floating beside it.
Match Icon Stroke to Adjacent Text Weight
An icon next to a label should carry the same optical weight as the text. A hairline icon beside semibold text reads as broken, a heavy icon beside regular text shouts. Tune the stroke to the label it sits with (values assume a 24px grid):
| Adjacent text | Icon stroke width |
|---|---|
| Regular (400), 14-16px | 1.5px |
| Medium / Semibold (500-600) | 2px |
| Bold (700), or emphasized standalone | 2.5px |
<!-- Good: stroke tuned to the label weight -->
<button class="flex items-center gap-2 font-semibold">
<PlusIcon stroke-width="2" class="size-4" />
New project
</button>
<!-- Bad: default 1.5px stroke against a bold label -->
<button class="flex items-center gap-2 font-bold">
<PlusIcon stroke-width="1.5" class="size-4" />
New project
</button>
One library, one optical strategy per surface. Do not mix icon libraries with incompatible stroke conventions on one toolbar, the mismatch is instantly visible. Pick a single set. If it intentionally supports stroke variants, match them to adjacent text as above; otherwise keep the set's native stroke and use size or color for emphasis.
One SVG, Recolored per State
Never ship separate icon assets for default / hover / selected / disabled. Draw one SVG with currentColor, strip any hardcoded fill, and drive every state with CSS color and opacity:
<!-- Good: one asset, states are CSS -->
<svg fill="none" stroke="currentColor" stroke-width="2">…</svg>
.icon-button { color: oklch(0.552 0.016 285.938); }
.icon-button:hover { color: oklch(0.21 0.006 285.885); }
.icon-button[aria-pressed="true"] { color: oklch(0.623 0.188 259.815); }
.icon-button:disabled { opacity: 0.4; }
A hardcoded fill inside the SVG (fill="oklch(0.55 0 0)", and worse a raw hex) pins the color and defeats state-driven recoloring. Strip fills to currentColor when importing icons.
Outline Default, Fill Active
When a set offers outline and filled variants, use them as a state pair, not interchangeably. Outline is the resting state, fill signals active:
| Variant | Use for |
|---|---|
| Outline | Default: toolbars, list rows, inline with text |
| Fill | Selected / active: the active tab, a toggled bookmark, a liked heart |
// Good: variant communicates state
<TabIcon variant={isActive ? "solid" : "outline"} />
// Bad: filled everywhere, so the active tab has no state signal
<TabIcon variant="solid" />
Filled icons everywhere is the same mistake as color everywhere: with no resting state, the active one has nothing to contrast against.
Icons in RTL
Under dir="rtl", mirror only the icons whose meaning is tied to reading direction, and leave the rest alone. Flipping a clock or a checkmark looks broken:
| Flip | Don't flip |
|---|---|
| Back / forward arrows, chevrons in navigation | Logos and brand marks |
| "Send" style directional glyphs | Checkmarks |
| Text-block glyphs (alignment, lists, indent) | Physical objects: clocks, cups, pencils |
| Speaker / volume waves (emanate in reading direction) | Media playback (play / rewind refer to tape direction) |
/* Good: mirror only direction-dependent icons */
[dir="rtl"] .icon-directional {
scale: -1 1;
}
<!-- Tailwind -->
<ChevronRightIcon class="icon-directional rtl:-scale-x-100" />
Analyze composite icons part by part: a badge, slash, or notification dot overlaid on a base glyph may need to keep its position even when the base flips.
Design at Render Size
An icon that looks great at 48px can collapse into mush at 16px. Thin interior lines, tight counters, and fine texture blur or alias when small.
- Test every icon at the smallest size it renders, often
16px, and confirm it stays recognizable there. Prefer a simplified glyph for small contexts over scaling detailed artwork down. - Keep icons on the set's native grid (
16,20,24). A 16px icon drawn on a 24px grid with fractional scaling renders soft. - Size inline icons to the text's cap height, typically
1emto1.25em, so the pair scales together. - Always SVG, never raster, so the same asset stays crisp at every density.
Icon Checklist
- Icon stroke weight matches adjacent text weight (1.5px at 400, 2px at 500-600, 2.5px at 700)
- One icon library and one optical strategy per surface
- Single
currentColorSVG per icon, hardcoded fills stripped, states driven by CSS color / opacity - Outline for default, filled for active / selected / toggled
- Direction-dependent icons flip under
dir="rtl"; logos, checkmarks, clocks, media playback do not - Every icon tested at its smallest render size, kept on the native grid (16/20/24), sized to cap height