Design for Developers: Making Your UI Actually Work Everywhere (Part 2)

In Part 1, we covered the fundamentals: spacing, typography, color, hierarchy, and alignment. Those rules fix 80% of ugly developer UIs.
But there's a problem. You followed all the rules. Your spacing is consistent. Your typography has hierarchy. Your colors make sense.
Then someone opens your app on a phone. Or switches to dark mode. Or hovers over a button and nothing happens.
Your UI falls apart.
This is Part 2. We're going beyond "making it look good" to "making it work everywhere." We'll cover layout patterns, component states, dark mode, animations, and responsive design.
Same deal: no theory, just concrete patterns you can copy.
Rule 1: Layout Patterns That Actually Work
Developers often think layout is complicated. It's not. You need like three patterns, and they cover 95% of use cases.
The Three Layouts You Actually Need
1. Stack Layout - Everything vertical
.stack {
display: flex;
flex-direction: column;
gap: 16px;
}
Use for: Forms, article content, mobile views, anything that flows top to bottom.
2. Sidebar Layout - Fixed side, flexible main
.sidebar-layout {
display: grid;
grid-template-columns: 250px 1fr;
gap: 24px;
}
Use for: Dashboards, settings pages, admin panels.
3. Grid Layout - Equal cards
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
Use for: Product grids, image galleries, card lists.
That's it. Three patterns. Everything else is a variation.
Container Width Rules
Developers either make content 100% width (unreadable on big screens) or pick a random max-width.
The rule:
/* Text content - narrow */
.prose {
max-width: 65ch; /* About 65 characters per line */
margin: 0 auto;
}
/* General content - medium */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* Full-width data - wide */
.table-container {
max-width: 1400px;
margin: 0 auto;
}
Why ch units for text? Because readability is about line length, not screen size. 50-75 characters per line is optimal. 65ch gives you that regardless of font size.
The diagram above shows the three core layout patterns in action. Stack layout flows vertically with consistent gaps. Sidebar layout has a fixed navigation column and flexible content area. Grid layout auto-fills with cards that maintain minimum width. Notice how each pattern has a specific use case - these aren't just random choices.
Rule 2: Component States Aren't Optional
Here's what developers do:
.button {
background: blue;
color: white;
}
That's it. One state. No hover. No focus. No disabled. No active.
Users have no idea if the button is clickable, loading, or broken.
The Five States Every Interactive Element Needs
/* 1. Default - How it looks normally */
.button {
background: #3b82f6;
color: white;
border: 2px solid transparent;
transition: all 0.15s ease;
}
/* 2. Hover - Mouse is over it */
.button:hover {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
}
/* 3. Focus - Keyboard navigation */
.button:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
/* 4. Active - Being clicked */
.button:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
}
/* 5. Disabled - Can't be clicked */
.button:disabled {
background: #d1d5db;
color: #9ca3af;
cursor: not-allowed;
transform: none;
}
Every state tells the user something:
Hover: "I'm interactive"
Focus: "I'm selected (keyboard users need this)"
Active: "I'm responding to your click"
Disabled: "I'm not available right now"
Form Input States
Inputs need even more states because they handle validation:
/* Default */
.input {
border: 1px solid #d1d5db;
transition: all 0.15s ease;
}
/* Focus - User is typing */
.input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
outline: none;
}
/* Error - Validation failed */
.input.error {
border-color: #ef4444;
}
.input.error:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
/* Success - Validation passed */
.input.success {
border-color: #10b981;
}
/* Disabled - Can't edit */
.input:disabled {
background: #f3f4f6;
color: #9ca3af;
cursor: not-allowed;
}
This diagram shows all five states for a button and an input field. Notice how each state provides visual feedback: hover lifts slightly, focus shows a ring for keyboard users, active presses down, and disabled is grayed out. The input field adds error and success states with color-coded borders. These aren't decorative - they're functional feedback.
Rule 3: Dark Mode Is Not "Invert Colors"
Developers who add dark mode:
/* Bad - Just flip everything */
body.dark {
background: black;
color: white;
}
This creates problems:
Pure black (#000) is harsh on OLED screens
Pure white text (#fff) on pure black is too high contrast
Shadows disappear (they're designed for light backgrounds)
Colors need adjustment (bright colors hurt on dark backgrounds)
Dark Mode Color System
You need a separate palette:
:root {
/* Light mode */
--bg-primary: #ffffff;
--bg-secondary: #f3f4f6;
--text-primary: #1a1a1a;
--text-secondary: #6b7280;
--border: #e5e7eb;
}
[data-theme="dark"] {
/* Dark mode - NOT just inverted */
--bg-primary: #1a1a1a; /* Dark gray, not black */
--bg-secondary: #2d2d2d; /* Lighter for cards */
--text-primary: #e5e7eb; /* Light gray, not white */
--text-secondary: #9ca3af; /* Reduced contrast */
--border: #404040; /* Subtle borders */
}
Key principles:
Use dark gray (#1a1a1a), not black (#000)
Use light gray (#e5e7eb), not white (#fff)
Reduce contrast slightly (easier on eyes)
Make borders more subtle
Adjust brand colors to be less saturated
Elevation in Dark Mode
In light mode, you use shadows for depth. In dark mode, shadows don't work (dark on dark).
Solution: Use lighter backgrounds for elevated elements.
/* Light mode - elevation with shadows */
.card {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Dark mode - elevation with lighter backgrounds */
[data-theme="dark"] .card {
background: #2d2d2d; /* Lighter than page background */
box-shadow: none; /* No shadow needed */
}
[data-theme="dark"] .modal {
background: #353535; /* Even lighter for modals */
}
The rule: Elevated elements = lighter background in dark mode.
The diagram compares light mode and dark mode implementations. Notice light mode uses pure white backgrounds with shadows for depth. Dark mode uses a dark gray base (#1a1a1a) with lighter grays for elevated surfaces. Colors are desaturated in dark mode - the bright blue becomes a softer blue. Text contrast is slightly reduced to prevent eye strain.
Rule 4: Animation Should Feel Natural
Developers either skip animations entirely (boring, static UI) or go overboard (everything bounces and spins).
The right approach: animate state changes, keep it subtle.
What to Animate
Good candidates:
Buttons on hover/click
Modal/drawer open/close
Accordion expand/collapse
Loading states
Toast notifications appearing
Form validation feedback
Bad candidates:
Page loads (users just want content)
Scrolling (interferes with user control)
Every single thing (overwhelming)
Timing and Easing
This is where developers mess up. They use linear (looks robotic) or random timing values.
The standards:
/* Quick interactions - 150ms */
.button {
transition: all 0.15s ease-out;
}
/* Medium transitions - 250ms */
.dropdown {
transition: all 0.25s ease-in-out;
}
/* Slow transitions - 350ms */
.modal {
transition: all 0.35s ease-out;
}
Easing functions:
ease-out- Starts fast, ends slow (feels responsive)ease-in- Starts slow, ends fast (feels like falling)ease-in-out- Smooth both ends (feels polished)
Never use linear for UI animations. It looks robotic.
Practical Examples
/* Button - Quick response */
.button {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.15s ease-out;
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
/* Modal - Medium entrance */
.modal {
opacity: 0;
transform: scale(0.95);
transition: all 0.25s ease-out;
}
.modal.open {
opacity: 1;
transform: scale(1);
}
/* Loading spinner - Continuous */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 1s linear infinite;
}
Performance rule: Only animate transform and opacity. Everything else (width, height, margin, etc.) causes layout recalculation and is slow.
This diagram illustrates timing curves and animation examples. The top shows the difference between linear (robotic), ease-out (natural), and ease-in-out (smooth) timing functions. Below are three examples: a button hover with 150ms ease-out, a modal entrance with 250ms ease-out, and a toast notification sliding in. Each uses the appropriate timing for its interaction type.
Rule 5: Responsive Design Is Mobile-First
Developers design for desktop first. Then they try to "make it work on mobile" by hiding things and shrinking fonts.
This breaks everything.
The Mobile-First Approach
Start with mobile. Add complexity for larger screens.
/* Mobile first - Default styles */
.container {
padding: 16px;
}
.grid {
display: flex;
flex-direction: column;
gap: 16px;
}
.nav {
display: none; /* Hidden on mobile */
}
/* Tablet - 768px and up */
@media (min-width: 768px) {
.container {
padding: 24px;
}
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.nav {
display: flex; /* Show on tablet+ */
}
}
/* Desktop - 1024px and up */
@media (min-width: 1024px) {
.container {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
.grid {
grid-template-columns: repeat(3, 1fr);
gap: 32px;
}
}
Why this works:
Mobile loads minimal CSS (faster)
Desktop enhances the mobile base
You design for constraints first (forces simplicity)
No "fixing" mobile as an afterthought
Touch Target Sizes
Developers use 32px buttons on mobile. Users tap wrong buttons constantly.
The rule: Minimum 44px x 44px for touch targets.
/* Desktop - can be smaller */
.button {
padding: 8px 16px;
font-size: 14px;
}
/* Mobile - needs to be tappable */
@media (max-width: 767px) {
.button {
padding: 12px 24px; /* At least 44px tall */
font-size: 16px; /* Prevents zoom on iOS */
}
}
iOS zoom prevention: If input font-size is less than 16px, iOS zooms in when you tap. Always use 16px minimum on mobile inputs.
Responsive Typography
Don't just shrink fonts. Adjust the scale.
/* Mobile - smaller scale */
h1 { font-size: 24px; }
h2 { font-size: 20px; }
p { font-size: 16px; }
/* Desktop - larger scale */
@media (min-width: 768px) {
h1 { font-size: 32px; }
h2 { font-size: 24px; }
p { font-size: 16px; } /* Body stays same */
}
Body text should stay 16px (readability). Only scale headings.
The diagram shows how layouts transform across breakpoints. A mobile stack (single column) becomes a 2-column grid on tablet, then a 3-column grid on desktop. Navigation goes from hidden (hamburger menu) to visible horizontal nav. Container padding increases from 16px to 32px. Touch targets grow from 32px to 44px minimum. This is mobile-first progressive enhancement.
Common Patterns Developers Get Wrong
1. Modals on Mobile
Desktop modals are centered overlays. On mobile, they should be full-screen (or bottom sheets).
/* Desktop - Centered modal */
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 500px;
}
/* Mobile - Full screen */
@media (max-width: 767px) {
.modal {
top: 0;
left: 0;
transform: none;
width: 100%;
height: 100%;
max-width: none;
}
}
2. Tables on Mobile
Tables don't work on small screens. You have two options:
Option 1: Horizontal scroll
.table-container {
overflow-x: auto;
}
Option 2: Reformat as cards (better UX)
/* Desktop - Table */
@media (min-width: 768px) {
.data-list {
display: table;
}
}
/* Mobile - Cards */
@media (max-width: 767px) {
.data-row {
display: block;
margin-bottom: 16px;
padding: 16px;
border: 1px solid #e5e7eb;
border-radius: 8px;
}
}
3. Navigation Patterns
Desktop: horizontal nav bar. Mobile: hamburger menu or bottom nav.
/* Mobile - Bottom nav */
@media (max-width: 767px) {
.nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
background: white;
border-top: 1px solid #e5e7eb;
padding: 12px;
}
}
/* Desktop - Top nav */
@media (min-width: 768px) {
.nav {
position: static;
justify-content: flex-start;
gap: 24px;
border-top: none;
}
}
Accessibility Checklist
These are non-negotiable:
[ ] Keyboard navigation works - Tab through everything
[ ] Focus states are visible - You can see what's selected
[ ] Color isn't the only indicator - Use icons + text, not just color
[ ] Touch targets are 44px minimum - On mobile
[ ] Text is at least 16px - On mobile (prevents zoom)
[ ] Contrast ratios meet WCAG - 4.5:1 for text, 3:1 for UI
[ ] Alt text on images - Screen readers need descriptions
[ ] Forms have labels - Not just placeholders
Tools for Building Responsive UIs
Testing:
Chrome DevTools device mode
Responsively App (tests multiple devices at once)
BrowserStack (real device testing)
Design systems with good responsive patterns:
Tailwind CSS (mobile-first utilities)
Chakra UI (responsive props)
Radix UI (accessible primitives)
Animation libraries:
Framer Motion (React)
GSAP (vanilla JS)
CSS
@keyframes(built-in)
The Bottom Line
Part 1 taught you to make UIs look good. Part 2 teaches you to make them work everywhere.
Follow these rules:
Layouts - Use stack, sidebar, or grid patterns
States - Every interactive element needs 5 states
Dark mode - Use dark gray, not black; lighter surfaces = elevated
Animation - Animate transforms/opacity only; 150ms for quick, 250ms for medium
Responsive - Mobile-first; 44px touch targets; scale typography
These aren't subjective. They're patterns that work across devices, themes, and interaction methods.
Start with mobile. Add states to your components. Test in dark mode. Add subtle animations. Make it responsive.
Your UI will work everywhere, for everyone.
What responsive patterns do you find most useful? Drop them in the comments.





