Skip to content

响应式设计

现代 Web 应用需要在从手机到大屏显示器的各种设备上正常工作。TailwindCSS 采用移动优先(Mobile-First)的响应式策略,通过断点前缀让你可以精确控制不同屏幕尺寸下的样式。v4 还引入了容器查询支持,让响应式设计更加灵活。

移动优先策略#

核心概念#

移动优先意味着:

  1. 默认样式针对最小屏幕
  2. 使用断点逐步增强大屏样式
<!-- 移动优先的写法 -->
<div class="text-sm md:text-base lg:text-lg">文字大小随屏幕增大</div>

这段代码的含义:

为什么是移动优先#

移动设备的限制更多(屏幕小、带宽有限),从约束最多的场景开始设计:

// 移动优先:从简单到复杂
function ProductGrid({ products }: { products: Product[] }) {
return (
<div
className="
grid
grid-cols-1 /* 手机:1列 */
sm:grid-cols-2 /* 小平板:2列 */
lg:grid-cols-3 /* 桌面:3列 */
xl:grid-cols-4 /* 大屏:4列 */
gap-4
sm:gap-6
lg:gap-8
"
>
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}

断点系统#

默认断点#

v4 提供 5 个默认断点,采用 min-width 媒体查询:

断点最小宽度典型设备
sm640px大手机/小平板
md768px平板
lg1024px笔记本
xl1280px桌面显示器
2xl1536px大屏显示器

记忆技巧:sm/md/lg/xl 分别对应 640/768/1024/1280,每级约增加 256px

使用断点#

<!-- 显示/隐藏 -->
<div class="hidden md:block">平板及以上显示</div>
<div class="md:hidden">仅手机显示</div>
<!-- 布局切换 -->
<div class="flex flex-col md:flex-row">手机垂直排列,平板水平排列</div>
<!-- 间距调整 -->
<section class="py-8 md:py-12 lg:py-16">响应式内边距</section>
<!-- 网格列数 -->
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4">响应式网格</div>

范围断点#

v4 支持最大宽度断点:

<!-- max-* 最大宽度断点 -->
<div class="max-md:text-center">仅在 md 断点以下居中</div>
<!-- 范围断点 -->
<div class="md:max-lg:bg-blue-500">仅在 md 到 lg 之间显示蓝色背景</div>

自定义断点#

在 CSS 中添加自定义断点:

@import 'tailwindcss';
@theme {
--breakpoint-xs: 475px;
--breakpoint-3xl: 1920px;
}

使用自定义断点:

<div class="xs:text-sm 3xl:text-xl">使用自定义断点</div>

响应式设计模式#

响应式字体#

<h1 class="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold">
响应式标题
</h1>
<p class="text-sm sm:text-base lg:text-lg leading-relaxed">响应式正文</p>

实用的字体比例:

const headingStyles = {
h1: 'text-3xl sm:text-4xl lg:text-5xl xl:text-6xl',
h2: 'text-2xl sm:text-3xl lg:text-4xl',
h3: 'text-xl sm:text-2xl lg:text-3xl',
h4: 'text-lg sm:text-xl lg:text-2xl',
}

响应式间距#

<!-- 容器内边距 -->
<div class="px-4 sm:px-6 lg:px-8">内容区域</div>
<!-- 区块间距 -->
<section class="py-12 sm:py-16 lg:py-20 xl:py-24">大区块</section>
<!-- 元素间隙 -->
<div class="space-y-4 sm:space-y-6 lg:space-y-8">堆叠元素</div>

响应式布局#

从堆叠到并排:

function Feature({ icon, title, description }: FeatureProps) {
return (
<div
className="
flex
flex-col /* 手机:垂直堆叠 */
sm:flex-row /* 平板:水平排列 */
items-start
sm:items-center
gap-4
"
>
<div className="shrink-0 size-12 rounded-lg bg-blue-100 flex items-center justify-center">
{icon}
</div>
<div>
<h3 className="font-bold text-lg">{title}</h3>
<p className="text-gray-600 mt-1">{description}</p>
</div>
</div>
)
}

响应式网格#

function Dashboard() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 lg:gap-6">
{/* 指标卡片 */}
<StatsCard title="总用户" value="12,345" />
<StatsCard title="活跃用户" value="8,901" />
<StatsCard title="收入" value="¥234,567" />
<StatsCard title="订单" value="1,234" />
{/* 跨列图表 */}
<div className="md:col-span-2 xl:col-span-2">
<Chart type="line" />
</div>
<div className="md:col-span-2 xl:col-span-2">
<Chart type="bar" />
</div>
{/* 全宽表格 */}
<div className="md:col-span-2 xl:col-span-4">
<DataTable />
</div>
</div>
)
}

容器查询#

v4 引入了容器查询支持,让组件可以根据其容器(而非视口)的尺寸响应式变化。

基础用法#

<!-- 定义查询容器 -->
<div class="@container">
<!-- 根据容器宽度响应 -->
<div class="@sm:flex @lg:grid @lg:grid-cols-2">容器查询布局</div>
</div>

容器断点#

默认容器断点:

断点最小宽度
@xs320px
@sm384px
@md448px
@lg512px
@xl576px
@2xl672px
@3xl768px
@4xl896px
@5xl1024px

实战:响应式卡片组件#

function ArticleCard({ article }: { article: Article }) {
return (
// 容器
<article className="@container bg-white rounded-xl shadow-sm overflow-hidden">
<div
className="
flex
flex-col /* 默认:垂直布局 */
@md:flex-row /* 容器 ≥448px:水平布局 */
"
>
{/* 图片 */}
<div
className="
aspect-video /* 默认:16:9 */
@md:aspect-square /* 水平布局时:正方形 */
@md:w-48 /* 固定宽度 */
shrink-0
"
>
<img
src={article.image}
alt={article.title}
className="w-full h-full object-cover"
/>
</div>
{/* 内容 */}
<div className="p-4 @lg:p-6">
<h3
className="
text-lg
@lg:text-xl
font-bold
"
>
{article.title}
</h3>
<p
className="
mt-2
text-sm
@lg:text-base
text-gray-600
line-clamp-2
@md:line-clamp-3
"
>
{article.excerpt}
</p>
</div>
</div>
</article>
)
}

这个卡片组件可以放在任何宽度的容器中,并自动调整布局。

命名容器#

当需要查询特定祖先容器时:

<div class="@container/main">
<div class="@container/sidebar">
<!-- 查询 sidebar 容器 -->
<div class="@md/sidebar:hidden">...</div>
<!-- 查询 main 容器 -->
<div class="@lg/main:grid-cols-2">...</div>
</div>
</div>

响应式图片与媒体#

响应式图片#

<!-- 基础响应式图片 -->
<img src="/image.jpg" alt="描述" class="w-full max-w-md mx-auto" />
<!-- 不同尺寸的图片 -->
<img
src="/image.jpg"
alt="描述"
class="
w-full
md:w-1/2
lg:w-1/3
"
/>
<!-- 固定宽高比 -->
<div class="aspect-video">
<img src="/image.jpg" alt="描述" class="w-full h-full object-cover" />
</div>

响应式宽高比#

<!-- 手机 1:1,平板 4:3,桌面 16:9 -->
<div class="aspect-square md:aspect-[4/3] lg:aspect-video">
<img src="/image.jpg" alt="描述" class="w-full h-full object-cover" />
</div>

响应式背景图#

<div
class="
bg-cover bg-center
h-[300px]
md:h-[400px]
lg:h-[500px]
bg-[url('/hero-mobile.jpg')]
md:bg-[url('/hero-tablet.jpg')]
lg:bg-[url('/hero-desktop.jpg')]
"
>
响应式背景
</div>

响应式视频#

function VideoEmbed({ src }: { src: string }) {
return (
<div className="aspect-video w-full max-w-4xl mx-auto">
<iframe src={src} className="w-full h-full" allowFullScreen />
</div>
)
}

实战:响应式导航栏#

完整的响应式导航实现:

import { useState } from 'react'
function Navbar() {
const [isOpen, setIsOpen] = useState(false)
const navLinks = [
{ href: '/', label: '首页' },
{ href: '/products', label: '产品' },
{ href: '/about', label: '关于' },
{ href: '/contact', label: '联系' },
]
return (
<nav className="bg-white border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<div className="shrink-0">
<a href="/" className="text-xl font-bold text-gray-900">
Logo
</a>
</div>
{/* 桌面导航 */}
<div className="hidden md:flex md:items-center md:gap-8">
{navLinks.map((link) => (
<a
key={link.href}
href={link.href}
className="text-gray-600 hover:text-gray-900 transition-colors"
>
{link.label}
</a>
))}
<button className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
登录
</button>
</div>
{/* 移动端菜单按钮 */}
<button
className="md:hidden p-2 rounded-lg hover:bg-gray-100"
onClick={() => setIsOpen(!isOpen)}
>
<svg
className="size-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{isOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
</div>
{/* 移动端菜单 */}
<div className={`md:hidden ${isOpen ? 'block' : 'hidden'}`}>
<div className="py-4 space-y-2">
{navLinks.map((link) => (
<a
key={link.href}
href={link.href}
className="block py-2 px-4 text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg"
>
{link.label}
</a>
))}
<div className="pt-4 border-t border-gray-100">
<button className="w-full py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600">
登录
</button>
</div>
</div>
</div>
</div>
</nav>
)
}

响应式侧边栏#

function Layout({ children }: { children: React.ReactNode }) {
const [sidebarOpen, setSidebarOpen] = useState(false)
return (
<div className="min-h-screen">
{/* 移动端遮罩 */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
{/* 侧边栏 */}
<aside
className={`
fixed top-0 left-0 z-50
w-64 h-full
bg-white border-r border-gray-200
transform transition-transform duration-300
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
lg:translate-x-0 lg:static lg:z-0
`}
>
<div className="p-4">
<h2 className="text-lg font-bold">侧边栏</h2>
{/* 导航项 */}
</div>
</aside>
{/* 主内容区 */}
<main className="lg:ml-64">
{/* 顶栏 */}
<header className="h-16 border-b border-gray-200 flex items-center px-4 lg:px-8">
<button
className="lg:hidden p-2 rounded-lg hover:bg-gray-100"
onClick={() => setSidebarOpen(true)}
>
<svg
className="size-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
<h1 className="ml-4 lg:ml-0 text-xl font-bold">页面标题</h1>
</header>
{/* 内容 */}
<div className="p-4 lg:p-8">{children}</div>
</main>
</div>
)
}

调试响应式#

显示当前断点#

开发时可以添加一个断点指示器:

function BreakpointIndicator() {
if (process.env.NODE_ENV === 'production') return null
return (
<div className="fixed bottom-4 left-4 z-50 px-3 py-1 bg-gray-900 text-white text-sm font-mono rounded">
<span className="sm:hidden">xs</span>
<span className="hidden sm:inline md:hidden">sm</span>
<span className="hidden md:inline lg:hidden">md</span>
<span className="hidden lg:inline xl:hidden">lg</span>
<span className="hidden xl:inline 2xl:hidden">xl</span>
<span className="hidden 2xl:inline">2xl</span>
</div>
)
}

浏览器开发工具#

Chrome DevTools 提供了设备模拟功能:


响应式设计让同一套代码能够适配从手机到桌面的各种设备。下一篇文章将深入探讨交互与动画,学习如何使用状态变体和过渡效果创建流畅的用户体验。