Devlog 21: Mobile-First CSS - Building Responsive UI With Tailwind

6 min read
css mobile responsive tailwind

Devlog 21: Mobile-First CSS - Building Responsive UI With Tailwind

78% of Tuggy's traffic is mobile. Desktop is the minority. So I design for mobile first, then enhance for larger screens.

Here's my approach with Tailwind CSS.

Mobile-First Breakpoints

Start with mobile, add complexity for larger screens:

<div class="
  p-4             /* Mobile: 16px padding */
  md:p-6          /* Tablet: 24px padding */
  lg:p-8          /* Desktop: 32px padding */
">

Not the reverse (desktop-first would be p-8 md:p-6 sm:p-4).

Touch Target Sizes

Minimum 44x44px for tap targets (Apple guideline). This is crucial for making taps feel accurate, especially combined with removing the 300ms touch delay:

<button class="
  min-w-[44px]
  min-h-[44px]
  p-3
  rounded-lg
">
  Vote
</button>

Bigger than you think! But necessary for thumbs.

Responsive Typography

Scale font sizes with viewport:

<h1 class="
  text-2xl        /* Mobile: 24px */
  md:text-3xl     /* Tablet: 30px */
  lg:text-4xl     /* Desktop: 36px */
  font-bold
">
  {matchup.title}
</h1>

Tailwind's default scale works well.

Layout Patterns

Mobile: Stack vertically

<div class="flex flex-col gap-4">
  <div>Content A</div>
  <div>Content B</div>
</div>

Desktop: Side by side

<div class="flex flex-col md:flex-row gap-4">
  <div class="md:w-1/2">Content A</div>
  <div class="md:w-1/2">Content B</div>
</div>

Column on mobile, row on desktop.

Voting Area Layout

Mobile gets full width buttons:

<div class="
  grid grid-cols-2 gap-4     /* Mobile: 2 columns */
  md:grid-cols-1 md:gap-6    /* Desktop: 1 column per side */
">
  <VoteButton side="a" />
  <VoteButton side="b" />
</div>

Big tappable areas on mobile.

Safe Areas (iOS Notch)

Handle notches and home indicators:

/* In global CSS */
:root {
  --safe-area-inset-top: env(safe-area-inset-top);
  --safe-area-inset-bottom: env(safe-area-inset-bottom);
}
<header class="
  pt-[var(--safe-area-inset-top)]
  px-4
">
  <!-- Header content -->
</header>

Content doesn't hide behind notch.

Horizontal Scrolling (Swipe)

For upgrade shop on mobile:

<div class="
  overflow-x-auto
  flex gap-4
  snap-x snap-mandatory
  px-4
  -mx-4
">
  {#each upgrades as upgrade}
    <div class="
      min-w-[280px]
      snap-center
    ">
      <UpgradeCard {upgrade} />
    </div>
  {/each}
</div>

Swipeable carousel feels native.

Sticky Elements

Vote button always visible:

<button class="
  sticky bottom-4
  md:relative md:bottom-0
  z-10
">
  Cast Vote
</button>

Sticky on mobile, static on desktop.

Dark Mode

Use Tailwind's dark mode:

<div class="
  bg-white dark:bg-gray-900
  text-gray-900 dark:text-white
">

Respects system preference:

// tailwind.config.js
module.exports = {
  darkMode: 'media', // or 'class' for manual toggle
}

Performance: Purge CSS

Only include used classes:

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{html,js,svelte,ts}'],
}

Reduces CSS bundle from 3MB to ~10KB.

Custom Utilities

For Tuggy-specific patterns:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        'tuggy': {
          500: '#e94569',
          600: '#d63456',
        }
      },
      spacing: {
        'safe-top': 'env(safe-area-inset-top)',
        'safe-bottom': 'env(safe-area-inset-bottom)',
      }
    }
  }
}

Use like: bg-tuggy-500, pt-safe-top.

Responsive Images

<img
  src={imageUrl}
  srcset="
    {imageUrl}?w=400 400w,
    {imageUrl}?w=800 800w,
    {imageUrl}?w=1200 1200w
  "
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 50vw,
    33vw
  "
  alt={alt}
  class="w-full h-auto"
  loading="lazy"
/>

Serves appropriate size per device.

Testing Responsive Layouts

Chrome DevTools device emulation:

  • iPhone SE (small)
  • iPhone 13 (medium)
  • iPad (tablet)
  • Desktop 1920px

Also test on real devices. Emulation isn't perfect.

Common Mobile Issues I Hit

Issue 1: Text too small

Mobile Safari auto-zooms if text < 16px. Keep base font 16px+.

Issue 2: Buttons too close

Need 8px+ gap between tap targets to avoid misclicks.

Issue 3: Horizontal scroll

Elements wider than viewport cause annoying horizontal scroll. Use max-w-full to constrain.

Issue 4: Fixed positioning

position: fixed breaks on mobile when keyboard opens. Use position: sticky instead.

Takeaway

Mobile-first means designing for constraints first, then adding enhancements for larger screens.

Touch targets need to be way bigger than mouse targets. 44x44px minimum.

Tailwind's utility-first approach makes responsive design straightforward. Just add breakpoint prefixes.

Test on real devices. Emulators miss iOS quirks like safe areas and the notch.

Purge unused CSS. Tailwind is huge until you purge. After purging, tiny.

CSS is just one part of mobile optimization. Combine this with touch interaction optimizations like removing the 300ms delay and adding haptic feedback for a complete mobile experience.

Next: performance profiling and optimization techniques.