TL;DR
This tutorial builds a complete dark-mode app landing page with Tailwind CSS and Next.js — hero section, feature grid, screenshot gallery, testimonials, and CTA. All responsive, all accessible. If you want the result without the build time, AppLander generates a Tailwind CSS landing page from your App Store URL in 60 seconds.
Tailwind CSS has become the default styling choice for modern app landing pages. Its utility-first approach lets you build custom designs without writing a single CSS file, and its responsive modifiers make mobile-first design trivial. Combined with Next.js for routing, metadata, and image optimization, it is the most productive stack for app websites in 2026.
In this tutorial, we will build a complete app landing page from scratch. I will walk through each section with real Tailwind CSS code, explain the design decisions behind each utility class, and show you how to make everything responsive. By the end, you will have a production-ready page you can deploy to Vercel.
What Do You Need Before Starting?
This tutorial assumes you have Node.js 18+ installed and basic familiarity with React and JSX. You do not need prior Tailwind CSS experience — I will explain every utility class as we use it.
Let us start by setting up the project:
npx create-next-app@latest my-app-landing --typescript --tailwind --app --src-dir=false
cd my-app-landingThis command creates a Next.js 15 project with TypeScript, Tailwind CSS 4, and the App Router pre-configured. You are ready to start building immediately.
How Do You Structure the Landing Page Layout?
Open app/layout.tsx. This is the root layout that wraps every page. We need to set up the dark background, font, and basic structure:
// app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'MyApp — Your Tagline Here',
description: 'A brief description of what your app does.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className="dark">
<body className={`${inter.className} bg-[#0A0A0B] text-white antialiased`}>
{children}
</body>
</html>
)
}Key decisions here: we use Inter as the font (the most popular choice for app landing pages in 2026), set the background to near-black #0A0A0B, default text to white, and enable antialiased for smoother font rendering on all platforms.
How Do You Build the Hero Section?
The hero section is the most important part of any app landing page. It needs to communicate what your app does, show what it looks like, and provide a clear download button — all above the fold.
// components/Hero.tsx
export function Hero() {
return (
<section className="relative overflow-hidden px-4 pt-24 pb-16 sm:px-6 lg:px-8">
<div className="mx-auto max-w-5xl text-center">
{/* Badge */}
<span className="inline-block rounded-full border border-white/10
bg-white/5 px-4 py-1.5 text-xs font-medium text-white/60">
New: Version 2.0 is here
</span>
{/* Headline */}
<h1 className="mt-6 text-4xl font-bold tracking-tight
sm:text-5xl lg:text-6xl">
Track Your Habits
<span className="block text-accent">in 30 Seconds a Day</span>
</h1>
{/* Subtitle */}
<p className="mx-auto mt-6 max-w-2xl text-lg text-white/60
leading-relaxed">
The simplest habit tracker for people who hate habit trackers.
No streaks, no guilt, no complexity — just a daily check-in
that actually sticks.
</p>
{/* CTA buttons */}
<div className="mt-10 flex flex-wrap justify-center gap-4">
<a
href="https://apps.apple.com/app/your-app"
className="inline-flex items-center gap-2 rounded-xl
bg-white px-6 py-3 text-sm font-semibold text-black
transition hover:bg-white/90"
>
Download on the App Store
</a>
<a
href="#features"
className="inline-flex items-center gap-2 rounded-xl
border border-white/20 px-6 py-3 text-sm font-semibold
text-white/80 transition hover:border-white/40
hover:text-white"
>
See Features
</a>
</div>
</div>
</section>
)
}Let me break down the key Tailwind classes:
tracking-tight— Reduces letter spacing on the headline for a modern, premium feel.text-white/60— White text at 60% opacity. This creates hierarchy without introducing additional colors.sm:text-5xl lg:text-6xl— Responsive font sizes. The headline scales from 36px on mobile to 60px on desktop.max-w-2xl mx-auto— Constrains the subtitle width for comfortable reading line lengths.flex-wrap justify-center gap-4— Buttons stack vertically on small screens and sit side by side on larger screens.
How Do You Build a Feature Grid?
The feature grid showcases your app's key capabilities. We will use a responsive CSS Grid layout that adapts from one column on mobile to three columns on desktop:
// components/Features.tsx
const features = [
{
icon: '⏱️',
title: 'One-Tap Logging',
description: 'Check in with a single tap. No forms, no typing,
no friction. Your habit is logged before you can talk yourself
out of it.',
},
{
icon: '📊',
title: 'Visual Streaks',
description: 'See your consistency at a glance with a beautiful
heat map. Green squares are addictive — in a good way.',
},
{
icon: '🔔',
title: 'Smart Reminders',
description: 'Gentle nudges at exactly the right time. Based on
your past behavior, not a fixed schedule.',
},
{
icon: '🌙',
title: 'Dark Mode Native',
description: 'Designed dark-first for comfortable use at any hour.
Your eyes will thank you at 11 PM.',
},
{
icon: '🔒',
title: 'Private by Design',
description: 'All data stays on your device. No accounts required,
no cloud sync unless you want it.',
},
{
icon: '⚡',
title: 'Instant Launch',
description: 'Opens in under 200ms. We obsess over performance
so you never wait.',
},
]
export function Features() {
return (
<section id="features" className="px-4 py-20 sm:px-6 lg:px-8">
<div className="mx-auto max-w-6xl">
<h2 className="text-center text-3xl font-bold tracking-tight
sm:text-4xl">
Everything You Need,
<span className="text-accent"> Nothing You Don't</span>
</h2>
<p className="mx-auto mt-4 max-w-2xl text-center text-white/50">
Six features. Zero bloat. Built for people who value their time.
</p>
<div className="mt-16 grid grid-cols-1 gap-6 sm:grid-cols-2
lg:grid-cols-3">
{features.map((feature) => (
<div
key={feature.title}
className="group rounded-2xl border border-white/10
bg-white/[0.02] p-6 transition-all duration-300
hover:border-white/20 hover:bg-white/[0.05]"
>
<span className="text-2xl">{feature.icon}</span>
<h3 className="mt-4 text-lg font-semibold">
{feature.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-white/50">
{feature.description}
</p>
</div>
))}
</div>
</div>
</section>
)
}The grid is fully responsive with just three classes: grid-cols-1 for mobile, sm:grid-cols-2 for tablets, and lg:grid-cols-3 for desktop. The hover effect on each card (hover:border-white/20 hover:bg-white/[0.05]) provides subtle interactivity without JavaScript.
How Do You Create a Screenshot Gallery?
Screenshots are critical for converting visitors into downloaders. We will build a horizontally scrollable gallery with device frames:
// components/Screenshots.tsx
const screenshots = [
{ src: '/screenshots/home.png', alt: 'Home screen with habit list' },
{ src: '/screenshots/stats.png', alt: 'Weekly statistics view' },
{ src: '/screenshots/timer.png', alt: 'Focus timer in action' },
{ src: '/screenshots/settings.png', alt: 'Settings and preferences' },
]
export function Screenshots() {
return (
<section className="py-20 overflow-hidden">
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<h2 className="text-center text-3xl font-bold tracking-tight
sm:text-4xl">
See It in Action
</h2>
<p className="mx-auto mt-4 max-w-2xl text-center text-white/50">
Real screenshots from the app. What you see is what you get.
</p>
</div>
<div className="mt-16 flex gap-6 overflow-x-auto px-4
pb-4 snap-x snap-mandatory scrollbar-hide
sm:justify-center sm:flex-wrap sm:overflow-visible">
{screenshots.map((screenshot) => (
<div
key={screenshot.src}
className="shrink-0 snap-center"
>
{/* Device frame */}
<div className="w-[240px] rounded-[2.5rem] border-[8px]
border-white/10 bg-black p-1 shadow-2xl sm:w-[280px]">
<img
src={screenshot.src}
alt={screenshot.alt}
className="rounded-[2rem] w-full"
loading="lazy"
/>
</div>
</div>
))}
</div>
</section>
)
}On mobile, the screenshots scroll horizontally with snap points (snap-x snap-mandatory) for a native feel. On larger screens (sm:flex-wrap sm:overflow-visible), they wrap into a grid layout. The device frame is built entirely with Tailwind — rounded corners, a border, and padding simulate an iPhone frame without any images.
How Do You Build a Testimonials Section?
Social proof converts skeptics into downloaders. Here is a testimonial card component:
// components/Testimonials.tsx
const testimonials = [
{
text: 'Finally a habit tracker that respects my time. I open it,
tap once, and close it. That is exactly what I needed.',
author: 'Sarah M.',
rating: 5,
},
{
text: 'I have tried every habit app on the App Store. This is the
only one I have used for more than two weeks.',
author: 'James K.',
rating: 5,
},
{
text: 'The dark mode is gorgeous. I actually look forward to
checking in before bed. Never thought I would say that about
a habit tracker.',
author: 'Priya R.',
rating: 5,
},
]
export function Testimonials() {
return (
<section className="px-4 py-20 sm:px-6 lg:px-8">
<div className="mx-auto max-w-6xl">
<h2 className="text-center text-3xl font-bold tracking-tight
sm:text-4xl">
Loved by Real Users
</h2>
<div className="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-3">
{testimonials.map((t) => (
<div
key={t.author}
className="rounded-2xl border border-white/10
bg-white/[0.02] p-6"
>
{/* Stars */}
<div className="flex gap-1 text-yellow-400">
{Array.from({ length: t.rating }).map((_, i) => (
<span key={i}>★</span>
))}
</div>
<p className="mt-4 text-sm leading-relaxed text-white/70">
“{t.text}”
</p>
<p className="mt-4 text-xs font-medium text-white/40">
— {t.author}, App Store Review
</p>
</div>
))}
</div>
</div>
</section>
)
}How Do You Build the Final CTA Section?
The bottom CTA gives visitors who scrolled through the entire page one final nudge. It should be visually distinct from the rest of the page:
// components/CTA.tsx
export function CTA() {
return (
<section className="px-4 py-20 sm:px-6 lg:px-8">
<div className="mx-auto max-w-3xl rounded-3xl border
border-accent/20 bg-accent/5 p-10 text-center
sm:p-16">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
Ready to Build Better Habits?
</h2>
<p className="mt-4 text-lg text-white/60">
Download free on the App Store. No account required.
Start tracking in 30 seconds.
</p>
<div className="mt-8">
<a
href="https://apps.apple.com/app/your-app"
className="inline-flex items-center gap-2 rounded-xl
bg-accent px-8 py-4 text-base font-semibold text-white
transition hover:bg-accent/90"
>
Download for Free
</a>
</div>
</div>
</section>
)
}The CTA section uses an accent-tinted border and background to create a distinct container that draws the eye. The button is larger than the hero CTA (px-8 py-4) to emphasize its importance.
How Do You Make Everything Responsive?
Tailwind CSS makes responsive design almost automatic with its breakpoint prefixes. The three breakpoints we use throughout this tutorial:
- Default (no prefix): Mobile-first styles. Everything without a prefix applies to all screen sizes.
sm:640px and above. Tablet landscape and small desktops.lg:1024px and above. Standard desktop.
The key principle is to design mobile-first. Write your base styles for the smallest screen, then add sm: and lg: overrides for larger screens. This approach ensures your page works on every device because the mobile design is the foundation, not an afterthought.
Common responsive patterns used in this tutorial:
- Grid columns:
grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 - Font sizes:
text-3xl sm:text-4xl lg:text-5xl - Padding:
px-4 sm:px-6 lg:px-8 - Visibility:
hidden sm:blockto show elements only on desktop
How Do You Add Dark Mode Support?
Since we designed dark-first, our page already looks great. But if you want to support a light mode toggle, Tailwind CSS makes it straightforward with the dark: variant. Set darkMode: 'class' in your Tailwind config and toggle a dark class on the <html> element:
<div className="bg-white text-black dark:bg-[#0A0A0B] dark:text-white">
{/* Content adapts to the current mode */}
</div>For most app landing pages in 2026, a dark-only design is the safer and simpler choice. It looks premium, performs well, and eliminates the need to maintain two color schemes.
How Do You Optimize for Performance?
Tailwind CSS produces small CSS bundles because it only includes the classes you actually use. But there are additional performance optimizations for app landing pages:
- Use Next.js Image component. Replace
<img>with<Image>fromnext/imagefor automatic WebP conversion, responsive sizing, and lazy loading. - Preload your hero screenshot. The largest element above the fold should be preloaded to reduce LCP. Add
priorityto the Next.js Image component for the hero image. - Minimize JavaScript. A landing page is mostly static content. Avoid client-side JavaScript unless absolutely necessary. Every component should be a server component by default.
- Use system fonts as fallback. The Inter font is loaded from Google Fonts with
display: swap, so text is visible immediately with the system font before Inter loads.
What About SEO Metadata?
Next.js App Router has a built-in Metadata API that makes SEO straightforward. In your page.tsx:
// app/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'MyApp — Track Habits in 30 Seconds a Day',
description: 'The simplest habit tracker for people who hate
habit trackers. Free on the App Store.',
openGraph: {
title: 'MyApp — Track Habits in 30 Seconds a Day',
description: 'The simplest habit tracker.',
images: ['/og.png'],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'MyApp — Track Habits in 30 Seconds a Day',
description: 'The simplest habit tracker.',
images: ['/og.png'],
},
}For a complete SEO guide including structured data, read our tutorial on JSON-LD structured data for app landing pages. And for an overview of SEO strategy, check SEO for mobile developers.
Do You Have to Build All of This Yourself?
You can. The code in this tutorial is production-ready and fully customizable. But building a polished app landing page from scratch — even with Tailwind CSS making the styling fast — still takes hours of work: writing copy, optimizing images, configuring SEO, testing responsive breakpoints, adding animations, and deploying.
AppLander generates everything in this tutorial — and more — automatically from your App Store URL. The hero, features, screenshots, testimonials, CTA, SEO metadata, and Tailwind CSS styling are all included out of the box. You customize a single config.ts file instead of building components from scratch.
If you enjoy building things yourself, this tutorial gives you everything you need. If you would rather ship in 5 minutes and customize later, AppLander is the shortcut. Either way, Tailwind CSS is the right tool for the job.