
Mobile-First Web Development in 2025 — How We Actually Build It | TiltStack
Mobile-First Web Development in 2025: How We Actually Build It
"Mobile-first" is one of those phrases that's been repeated so often it's started to lose meaning. Every web developer says they build mobile-first. But when you audit the CSS, what you often find is a desktop layout with media queries that shrink it down — which is the opposite of mobile-first.
This post is about what mobile-first actually means at the code level, why it matters for both performance and Google ranking, and exactly how we implement it on every TiltStack build.
What "Mobile-First" Means in the CSS
Responsive design says: build a desktop layout, then write media queries to handle smaller screens.
Mobile-first says: build the mobile layout first, then write media queries to scale up.
This is not just a philosophical preference — it has direct performance consequences.
When you build desktop-first and use @media (max-width: 768px) to shrink things for mobile, the browser loads the desktop CSS first, then loads and applies the media query override. Mobile users are downloading CSS they don't need before getting the CSS they do need.
When you build mobile-first and use @media (min-width: 768px) to add desktop styles, mobile users receive exactly the CSS they need and nothing extra. Desktop users load slightly more CSS (mobile base + desktop enhancement), but they're on faster connections and it's significantly less CSS than the desktop-first approach produces.
Google crawls and indexes your site on mobile by default — this is Google's "mobile-first index." The mobile experience is the canonical experience from Google's perspective.
Our Actual SASS Breakpoint System
Every TiltStack project uses this exact breakpoint structure. We write it the same way every time so new projects have a consistent pattern and any developer can read the CSS intuitively.
/* =============================================
MOBILE (BASE) — 360px minimum
All styles here are mobile defaults.
Always start here. Never assume desktop.
============================================= */
@media only screen and (min-width: 0rem) {
#section-name {
padding: var(--sectionPadding);
.cs-container {
width: 100%;
max-width: calc(550 / 16 * 1rem);
margin: auto;
padding: 0 1rem;
}
.cs-title {
font-size: clamp(1.9375rem, 3.9vw, 3.0625rem);
line-height: 1.2em;
}
/* Mobile-specific: stack elements vertically */
.cs-content {
display: flex;
flex-direction: column;
gap: 1rem;
}
}
}
/* =============================================
TABLET — 48rem (768px)
============================================= */
@media only screen and (min-width: 48rem) {
#section-name {
.cs-container {
max-width: calc(738 / 16 * 1rem);
}
/* Tablet: side-by-side layout begins */
.cs-content {
flex-direction: row;
gap: 2rem;
}
}
}
/* =============================================
DESKTOP — 64rem (1024px)
============================================= */
@media only screen and (min-width: 64rem) {
#section-name {
.cs-container {
max-width: calc(1280 / 16 * 1rem);
}
}
}
A few things worth noting about this structure:
min-width: 0rem as the base. Not min-width: 360px or just no media query. We wrap mobile styles in @media only screen and (min-width: 0rem) explicitly. This makes the pattern consistent — you can always scan a CSS file and know exactly which breakpoint a rule belongs to.
only screen prevents print stylesheets from inheriting. The only keyword filters out non-screen media types, preventing your mobile layout rules from affecting print output.
rem units for breakpoints, not px. This means the breakpoint responds to user font-size preferences. A user who has increased their browser's base font to 20px (users with vision difficulties often do this) will hit the tablet breakpoint at 960px instead of 768px — which is typically correct behavior for their context.
Touch Target Sizing — The Specific Numbers
One of the most common mobile UX failures we see on audited sites: interactive elements too small to tap reliably.
Google's Lighthouse audit flags touch targets smaller than 48px as a usability issue. Apple's HIG recommends at least 44×44 points. We use this as our minimum for any clickable element:
/* Navigation links — minimum touch target */
.cs-li-link {
padding: calc(8 / 16 * 1rem) calc(16 / 16 * 1rem);
min-height: calc(44 / 16 * 1rem);
display: flex;
align-items: center;
}
/* Mobile nav toggle button */
.cs-toggle {
width: calc(48 / 16 * 1rem);
height: calc(48 / 16 * 1rem);
display: flex;
align-items: center;
justify-content: center;
}
/* Form inputs — large enough to tap without zooming */
input, textarea, select {
min-height: calc(48 / 16 * 1rem);
font-size: 1rem; /* Prevents iOS auto-zoom on input focus */
}
That last rule is critical: font-size: 1rem on inputs. iOS Safari triggers a zoom-in animation on input focus if the input's font size is below 16px. This causes an instant CLS (Cumulative Layout Shift) event and feels terrible. Setting font-size: 1rem on input, textarea, and select elements fixes this across all iOS devices.
Image Pipeline for Mobile Performance
Images are the #1 cause of slow mobile load times. Here's our exact implementation:
Every image is WebP. We don't serve JPEG or PNG for photographs. WebP is 25–34% smaller than JPEG at equivalent visual quality. On mobile connections, that matters significantly.
LCP images get fetchpriority="high":
<!-- Hero image — this is always the LCP element -->
<img
src="/assets/images/hero.webp"
alt="..."
width="1200"
height="600"
fetchpriority="high"
decoding="async"
/>
Non-LCP images get loading="lazy" and decoding="async":
<!-- Below-fold image -->
<img
src="/assets/images/team.webp"
alt="..."
width="600"
height="400"
loading="lazy"
decoding="async"
/>
Explicit width and height on every image. This is the single biggest CLS fix available. Without explicit dimensions, the browser doesn't allocate space for the image until it loads — everything below shifts. With explicit dimensions, space is pre-allocated correctly. CLS goes from 0.15+ to 0.0.
Responsive <picture> for images with different crops at different sizes:
<picture>
<source
media="(max-width: 600px)"
srcset="/assets/images/hero-mobile.webp"
type="image/webp"
/>
<source
media="(min-width: 601px)"
srcset="/assets/images/hero-desktop.webp"
type="image/webp"
/>
<img
src="/assets/images/hero-desktop.webp"
alt="..."
width="1200"
height="600"
fetchpriority="high"
/>
</picture>
CSS Loading Strategy for Mobile Performance
The way CSS loads dramatically affects Largest Contentful Paint (LCP) on mobile.
Critical CSS loads synchronously (blocks rendering, by design):
<!-- Loads before any render — keeps header from flashing unstyled -->
<link rel="stylesheet" href="/assets/css/header.css" />
Non-critical CSS loads async:
<!-- Below-fold CSS — doesn't block rendering -->
<link
rel="stylesheet"
href="/assets/css/root.css"
media="print"
onload="this.media='all'"
/>
<noscript>
<link rel="stylesheet" href="/assets/css/root.css" />
</noscript>
The media="print" trick makes the browser load the stylesheet at low priority (since print stylesheets don't apply to screen rendering). When it finishes loading, onload switches media to 'all', applying the styles. The <noscript> fallback handles browsers with JavaScript disabled.
This pattern typically reduces LCP by 200–400ms on mobile by removing CSS from the critical render path.
Navigation Pattern: The Most Mobile-Critical Component
Mobile navigation is where many sites fail under real-world use. Our standard pattern:
<!-- Skip link for keyboard/screen reader users -->
<a class="skip" aria-label="skip to main content" href="#main">Skip to Main Content</a>
<header id="cs-navigation">
<div class="cs-container">
<a href="/" class="cs-logo" aria-label="back to home">
<img src="/assets/logo/logo.svg" alt="TiltStack logo" width="120" height="40" />
</a>
<!-- Mobile toggle button — proper ARIA -->
<button
class="cs-toggle"
aria-label="mobile menu toggle"
aria-expanded="false"
aria-controls="cs-expanded"
>
<!-- hamburger icon SVG -->
</button>
<nav id="cs-expanded" class="cs-ul-wrapper" aria-label="primary navigation">
<ul class="cs-ul" role="list">
<!-- nav items -->
</ul>
</nav>
</div>
</header>
Key decisions here:
aria-expandedon the toggle button, updated by JavaScript when the menu opens/closesaria-controlslinking the button to the nav it controlsrole="list"on the<ul>(some CSS resets remove the implicit list role)- Skip link first in the DOM so keyboard users can jump past navigation
The JavaScript that runs the toggle is 15 lines of vanilla JS, deferred, and doesn't load any framework.
Running Your Own Mobile Audit
First check: open PageSpeed Insights, paste your URL, and read the mobile report carefully. Beyond the overall score:
- LCP element — the audit shows you exactly which element is the LCP and how long it takes. That's your primary target.
- CLS — the audit shows which elements are shifting. Almost always images without dimensions.
- Opportunities — the specific recommendations with estimated savings.
Second check: Chrome DevTools → Device Mode (Ctrl+Shift+M). Set the network throttle to "Slow 4G" (under the network dropdown). This simulates what your mobile users on a real connection experience. If it feels slow to you, it's slow to them.
Third check: Lighthouse audit from DevTools (under the "Lighthouse" tab). Run it in incognito mode to prevent browser extensions from influencing results.
FAQs
Q1: What's the difference between responsive design and mobile-first?
A: Responsive design is any technique that makes a site work across screen sizes. Mobile-first is a specific responsive approach — you write your base CSS for mobile and use min-width media queries to add desktop styles. The difference matters for performance (mobile users download less CSS) and forces better structural decisions (simple mobile layout first, then complexity added for larger screens).
Q2: Does font-size really affect iOS zoom behavior on input focus?
A: Yes. Any input with a computed font-size under 16px triggers iOS Safari's automatic zoom on focus. This is a platform behavior, not a bug you can patch. The fix is always setting font-size to 16px (or 1rem) on input elements. This affects every iOS device and Safari browser.
Q3: Should I build a Progressive Web App (PWA) instead of a standard website?
A: For most small business sites, a well-optimized static site outperforms a PWA on the metrics that matter for conversion and SEO. PWAs are valuable when you need offline functionality, push notifications, or app-like UX patterns (dashboard, authenticated sections). For a marketing/lead generation site, the complexity overhead of PWA isn't typically worth it.
Q4: How important is touch target size for Google rankings?
A: Lighthouse includes tap target sizing in its SEO audit (not just accessibility). While Google hasn't explicitly confirmed it as a direct ranking signal, failing the Lighthouse SEO audit for tap targets is a signal your UX is poor on mobile — and Google does use UX signals as indirect ranking factors. More practically: small tap targets mean frustrated mobile users who leave.
Q5: Is AMP still worth implementing in 2025?
A: Generally no. Google removed the AMP requirement for Top Stories inclusion in 2021. For most small business sites, a well-built standard HTML page with optimized Core Web Vitals outperforms an AMP page and gives you significantly more control over design and functionality. The cases where AMP still makes sense are high-volume news publishers with very specific technical requirements.





















































