Your target audience
We're going to start with the hardest thing to do right, which is your ideal client and how they want to be spoken to.
Small, deliberate choices separate websites that work from websites that feel crafted. None of these tips will make or break your site on their own — but stacked together, they create that elusive sense of quality users can feel but rarely articulate.
1. Balance your headings
When a heading wraps to two or more lines, the default behavior often leaves a single orphaned word dangling on the last line. text-wrap: balance distributes the text evenly across lines, making headings look intentional rather than accidental.
For body text, use text-wrap: pretty instead — it focuses on eliminating widows in the last few lines.
h1, h2, h3 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}A two-line heading that used to split unevenly now distributes words equally across lines. Much better.
2. Make your text crispy
On macOS, browsers default to subpixel antialiasing, which makes text — especially light text on dark backgrounds — appear heavier than the designer intended. A single declaration fixes this across your entire site.
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}The difference is subtle in isolation but cumulative across the page. Your font-weight: 400 will actually look like 400, not a bloated 500.
3. Give numbers a fixed width
Counters, prices, timers, table columns — anywhere numbers update dynamically, the digits should be monospaced. Without this, a value going from 111,111 to 999,999 causes the entire layout to jitter because each digit has a different natural width.
.price, .counter, .table-cell {
font-variant-numeric: tabular-nums;
}This is especially visible in data-heavy dashboards, pricing cards, and any countdown component.
4. Use layered shadows, not a single drop
A single box-shadow with a large blur looks flat and synthetic. Real-world light creates soft, layered shadows. Stack multiple shadows with increasing offsets and blur radii.
.card {
box-shadow:
0 1px 1px hsl(0 0% 0% / 0.06),
0 2px 2px hsl(0 0% 0% / 0.06),
0 4px 4px hsl(0 0% 0% / 0.06),
0 8px 8px hsl(0 0% 0% / 0.06),
0 16px 16px hsl(0 0% 0% / 0.06);
}Each layer doubles the previous offset and blur. The result is a shadow that feels physically grounded rather than painted on.
5. Match your shadow hue to your palette
Pure black shadows (rgba(0,0,0,...)) look dirty on colored backgrounds. A shadow tinted to match your page's dominant hue feels dramatically more natural.
.card {
box-shadow:
0 2px 4px hsl(220 60% 50% / 0.12),
0 4px 8px hsl(220 60% 50% / 0.12);
}On a warm, amber-toned site, shift the hue to 30-40. The shadows will stop looking like they belong to a different design system.
6. Outline your images
Images with light edges bleed into light backgrounds, making the boundary invisible. A 1px outline at low opacity creates a subtle frame that works universally — on light backgrounds, dark backgrounds, and everything in between.
img {
outline: 1px solid rgba(0, 0, 0, 0.1);
outline-offset: -1px;
}The outline-offset: -1px pulls the outline inside the image so it doesn't add to the element's visual size. It's the same technique Apple uses on product imagery across their site.
7. Use concentric border radii
When you nest rounded elements — a card inside a card, a button inside a rounded container — the outer radius must be larger than the inner one. The formula is: outer radius = inner radius + padding between them.
.outer {
border-radius: 20px; /* 12 + 8 */
padding: 8px;
}
.inner {
border-radius: 12px;
}Without this, nested corners look pinched and uneven. With it, they look like they were designed as a single cohesive shape. This is one of those details that nobody consciously notices, but everyone feels.
8. Buttons need optical padding, not equal padding
A button with perfectly equal padding on all sides looks wrong. The icon side always appears to have more space than the text side because icons have built-in optical whitespace in their bounding box.
.button-with-leading-icon {
padding: 10px 16px 10px 12px;
}
.button-with-trailing-icon {
padding: 10px 12px 10px 16px;
}Similarly, a play button inside a circle looks off-center even when it's mathematically centered. Nudge it 1-2px to the right to compensate for the triangle's visual weight.
9. Use accent-color for instant form polish
Unstyled checkboxes, radio buttons, and range sliders are one of the most common tells of an unfinished site. You don't need to rebuild them from scratch — a single CSS property recolors all native form controls to match your brand.
:root {
accent-color: #6c5ce7;
}This gives you branded form elements that keep full accessibility, keyboard support, and native behavior — zero JavaScript, zero custom components.
10. Focus rings only when they matter
:focus styles show up on every click, which clutters a clean design. :focus-visible only activates when the user is navigating with a keyboard — exactly when they need the visual indicator.
button:focus {
outline: none;
}
button:focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
}Accessibility preserved, visual noise eliminated. This is one of the highest-leverage CSS changes you can make on any existing site.
11. Give clicks a physical response
A button that does nothing visually when pressed feels dead. A subtle scale transform on :active creates a satisfying tactile response — like pressing a real button.
.button {
transition: transform 150ms ease-out;
}
.button:hover {
transform: scale(1.02);
}
.button:active {
transform: scale(0.97);
}Keep the values tiny. You're simulating a physical press, not a cartoon bounce. The ease-out curve makes the movement fast at the start and gentle at the end — mimicking how real objects respond to force.
12. Fluid sizing without breakpoints
Media queries create hard jumps between sizes. clamp() creates smooth, continuous scaling. Use it for font sizes, container widths, and spacing.
h1 {
font-size: clamp(1.75rem, 4vw, 3rem);
}
.container {
padding-inline: clamp(1rem, 5vw, 3rem);
max-width: clamp(320px, 90vw, 1200px);
}A heading that smoothly scales from 28px on a phone to 48px on a desktop, without a single breakpoint, feels inherently more considered than one that snaps between two rigid sizes.
13. Smooth scroll — but respect the user
Smooth scrolling makes anchor links and scroll-to-top buttons feel polished rather than teleporting. But some users have vestibular disorders that make animated movement nauseating. Wrap it in a preference check.
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}This is the correct pattern for all animations: start with no motion as the default, add it only for users who haven't opted out.
14. Snap your carousels
Native scroll snapping gives you buttery-smooth, physics-based carousel behavior without a single line of JavaScript. On mobile, it feels indistinguishable from a native app component.
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 1rem;
scrollbar-width: none;
}
.carousel > * {
scroll-snap-align: center;
flex-shrink: 0;
}No libraries. No IntersectionObservers. No translateX calculations. Just CSS that works.
15. Reserve space before images load
When images load without a defined aspect ratio, the content below them jumps — sometimes violently. This is called Cumulative Layout Shift (CLS), and it makes a site feel unreliable.
img {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
height: auto;
}The browser now reserves the correct vertical space before the image arrives. The rest of the page stays perfectly still. This single change can dramatically improve your Core Web Vitals score.
16. Transition timing that feels alive
Not all easings are equal. ease is the CSS default, but it's generic and forgettable. ease-out — fast start, gentle end — is best for elements entering the screen. ease-in — slow start, fast end — is best for elements leaving. ease-in-out works for elements that stay in place but change state.
And a universal truth: keep durations between 150ms and 300ms. Below 150ms, the human eye can't perceive the interpolation. Above 300ms, the interface feels sluggish.
.element {
transition: all 200ms ease-out;
}17. Exit animations should whisper
Entrance animations can afford drama — they're introducing something new. Exit animations should be fast, short, and subtle. An element leaving the screen doesn't need your attention; it needs to get out of the way.
.toast-enter {
animation: slideIn 300ms ease-out;
}
.toast-exit {
animation: fadeOut 150ms ease-in;
}
@keyframes slideIn {
from { transform: translateY(100%); opacity: 0; }
}
@keyframes fadeOut {
to { opacity: 0; transform: translateY(-8px); }
}Notice the exit moves only 8px — a whisper compared to the full-height entrance. This asymmetry feels natural because it mirrors how attention works: arrivals are events, departures are transitions.
18. Frosted glass that actually works
The glassmorphism trend is everywhere, but most implementations look muddy because they skip the crucial details: a subtle border and proper background opacity.
.glass {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
}The border catches light at the edges, just like real frosted glass. Without it, the element looks like a rectangle with a Gaussian blur — which is technically what it is, but you don't want it to feel that way.
19. Build a spacing system and stick to it
The fastest way to make a site look "designed" is consistent spacing. Define a scale based on a base unit (typically 4px or 8px), expose it as custom properties, and use nothing else.
:root {
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
}When every margin, padding, and gap snaps to this scale, elements fall into natural visual rhythms. The page starts to feel like a grid even when it isn't.
20. Stagger your entrance animations
Animating a group of elements all at once looks mechanical. Adding a small sequential delay between them — 60 to 100ms — makes the entrance feel organic, like dominoes falling.
.card {
animation: fadeUp 400ms ease-out both;
animation-delay: calc(var(--i, 0) * 80ms);
}The stagger makes each element feel like it has its own moment. It turns a static page into a choreographed experience.
None of these details are revolutionary. You won't find them in a design system's changelog or a sprint retrospective. But they're the reason some websites feel right the moment you land on them — and others feel like they were assembled rather than designed.
Ship the details.