Tailwind CSS Grid 响应式布局实战:从 3 列基础栅格到 16 列自定义扩展(refine 项目实践)
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
导读:本文以 Tailwind CSS 的 Grid 工具类为核心,从 Block Formatting Context(BFC)出发,逐步引入grid、grid-cols-{n}、gap-{size}、col-span-{n}、grid-rows-{n}等类,最终实现一个包含导航栏、侧栏、主内容区和页脚的响应式页面骨架。读完本文,你将掌握 Tailwind Grid 的完整用法(含移动优先断点语义、跨列跨行、断点重排),并能在tailwind.config.js中把默认 12 列栅格扩展为 16 列。文章同时结合 refine 仓库中真实示例(examples/store、examples/blog-refine-daisyui、examples/blog-refine-shadcn)给出可复制的实战佐证。
Tailwind 的 Grid 系统:为什么不用 Flexbox 切分宽度
Tailwind CSS(简称 Tailwind)是一个用于快速构建网站的 CSS 框架,内置了一批开箱即用的工具类(utility classes),可以按需组合,并能在后续通过配置文件轻易定制,以适配任意设计在响应式、布局与主题上的需求。
在 Tailwind 1.2 之前的版本里,构建多列布局通常依赖 Flexbox 工具类,用w-{fraction}(如w-1/3)手工把容器的宽度切分成若干列。而从 1.2 版本起,Tailwind 提供了一整套基于原生 CSS Grid 的类,可以构建更精细的多行多列布局。
与 Twitter Bootstrap、Foundation 这类底层仍用 Flexbox 实现 12 列栅格的框架不同,Tailwind 直接使用 CSS Grid,让我们可以构建任意列数与行数、高度可定制的布局。它提供的 Grid 容器类包括:
grid:启用 Grid Formatting Contextgrid-cols-{n}:定义列数grid-rows-{n}:定义行数
以及用于控制 Grid 子元素行为的类,例如col-span-{n}、row-span-{n}、col-start-{n}、col-end-{n}、row-start-{n}、row-end-{n}等。
Tailwind 默认配置最多支持单屏 12 列,可以通过tailwind.config.js自定义;与 Grid 相关的许多选项都可以按需调整。本文后面会演示如何把列数上限扩展到 16。
建议进一步查阅 Tailwind CSS 官方文档中 grid-template-columns 及其后续的 grid-column-start / grid-column-end / grid-template-rows 等章节,作为工具类的完整参考。
项目目标:一个 3 列 5 行的页面骨架
我们从一个最简单的 HTML 文档开始,逐步实现如下目标布局:
- 顶部导航栏(navbar)
- 左侧内容区(side content)
- 主内容区(main content)
- 底部页脚(footer)
这个布局由 3 列、5 行组成。我们会先用一组遵循 Block Formatting Context(BFC)的div起步,再按需引入 Grid 相关类,逐步过渡到 Grid Formatting Context(GFC),在过程中理解每一类的作用。
项目初始化与开发环境准备
动手前先准备好环境:
- 克隆配套的演示仓库(包含上述目标布局的起步代码),在代码编辑器中打开。
- 确保编辑器安装了 Live Server 插件并处于启用状态。
- 安装依赖并编译样式,使用 Tailwind CLI 监视模式:
npx tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch- 启动 Live Server,点击编辑器右下角的
Go Live按钮,或在浏览器中直接访问 Live Server 提供的端口号。
index.html通过<link>引入编译产物/dist/styles.css,改动源码后 Tailwind 会在监视模式下自动重新生成样式。
基础示例:一个 9 宫格页面
先看起步用的index.html,它由 9 个不同背景色的tile组成,目前只是普通块级元素(BFC),从上到下依次堆叠:
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link href="/dist/styles.css" rel="stylesheet" /> <title>Document</title> </head> <body> <div class="container m-auto"> <div class="tile bg-teal-500"> <h1 class="tile-marker">ONE</h1> </div> <div class="tile bg-amber-500"> <h1 class="tile-marker">TWO</h1> </div> <div class="tile bg-yellow-500"> <h1 class="tile-marker">THREE</h1> </div> <div class="tile bg-lime-600"> <h1 class="tile-marker">FOUR</h1> </div> <div class="tile bg-green-600"> <h1 class="tile-marker">FIVE</h1> </div> <div class="tile bg-emerald-500"> <h1 class="tile-marker">SIX</h1> </div> <div class="tile bg-teal-500"> <h1 class="tile-marker">SEVEN</h1> </div> <div class="tile bg-purple-500"> <h1 class="tile-marker">EIGHT</h1> </div> <div class="tile bg-pink-500"> <h1 class="tile-marker">NINE</h1> </div> </div> </body> </html>建立 Grid Formatting Context
grid类:看似什么都不发生
给容器加上grid类:
<div class="container m-auto grid"></div>此时页面看不出任何变化——因为默认的grid只有一列,子元素仍然按一列堆叠。但布局上下文已经从 BFC 切换为 Grid Formatting Context(GFC)。
grid-cols-{n}:显式声明列数
添加三列:
<div class="container m-auto grid grid-cols-3"></div>现在 9 个div按 Grid Formatting Context 流入了 3 列。
需要注意的是,grid-cols-3并不是简单地“把屏幕切成 3 等份”,其底层 CSS 等价于:
grid-template-columns: repeat(3, minmax(0, 1fr));即每列都是1fr弹性轨道,且用minmax(0, 1fr)防止内容的最小内容尺寸(min-content)把轨道撑爆——这是 Tailwind Grid 列宽行为的关键细节,也是它比手动写1fr更稳妥的原因。
gap-{size}:设置栅格间距
间距需要设置在 Grid 容器上,而不是子元素上:
<div class="container m-auto grid grid-cols-3 gap-4"></div>gap-4会为子元素之间添加统一的间距(同时作用于行与列)。Tailwind 也提供gap-x-{size}与gap-y-{size}分别控制水平、垂直方向的间距。
响应式列数:md:与lg:前缀
在更大断点下增加列数:
<div class="container m-auto grid grid-cols-3 gap-4 md:grid-cols-5 lg:grid-cols-8" ></div>这里用到 Tailwind 默认断点体系中的md(768px)和lg(1024px)。观察页面缩放可以看到列数在 768px、1024px 两处递增。
这里有一个非常重要的语义:Tailwind 的响应式前缀(md、lg等)不是“屏幕尺寸”而是移动优先(mobile-first)断点。所以:
grid-cols-3对所有屏幕尺寸都生效(3 列是基线);- 屏幕宽度超过
md(≥768px)后,列数变为 5; - 屏幕宽度超过
lg(≥1024px)后,列数变为 8; - 之后没有再次改变,所以即使到了
xl、2xl,仍然是 8 列。
这意味着一套类即可覆盖从小屏到大屏的完整响应式曲线,前提是理解“前缀代表 min-width 阈值,而非某个具体设备”。
控制尺寸与位置:span 与 start/end
Grid 的尺寸与放置类给了我们更多控制:某个区块跨几列、从哪一列开始、到哪一列结束。
用col-span-{n}设置跨列尺寸
把第一个div当作导航栏,让它从栅格起点开始、在每个屏幕尺寸下都横跨整行:
<div class="tile col-span-3 bg-teal-500 md:col-span-5 lg:col-span-8"> <h1 class="tile-marker">ONE</h1> </div>注意这里在三个断点分别写col-span-3、md:col-span-5、lg:col-span-8,恰好等于对应断点下的总列数,即“占满整行”。
当需要“始终占满整行”时,可以简化为col-span-full:
<div class="tile col-span-full bg-teal-500"> <h1 class="tile-marker">ONE</h1> </div>也可以用col-start-{n}与col-end-{n}组合达到同样效果:
<div class="tile col-start-1 col-end-4 bg-teal-500 md:col-start-1 md:col-end-6 lg:col-start-1 lg:col-end-9" > <h1 class="tile-marker">ONE</h1> </div>但请看这里每个断点都要同时写 start 和 end 两组响应式类:
md:col-start-1 md:col-end-6 lg:col-start-1 lg:col-end-9既不 DRY,也容易出错。col-span-{n}更适合表达“尺寸”,而 start/end 更擅长表达“位置”。
继续设置第二、第三个div的宽度:
<div class="tile col-span-1 bg-amber-500 md:col-span-2 lg:col-span-3"> <h1 class="tile-marker">TWO</h1> </div> <div class="tile col-span-2 bg-yellow-500 md:col-span-3 lg:col-span-5"> <h1 class="tile-marker">THREE</h1> </div>用col-start-{n}放置元素:start 负责位置、span 负责尺寸
单独使用col-span-{n}时,对元素在栅格中的放置自由度有限;而单独使用col-start-{n}/col-end-{n}又会在响应式场景下带来大量重复。最佳实践是组合使用:start 类定位,span 类定宽高。
以第四个div为例,让它在lg断点从第 4 列开始、向右跨 2 列:
<div class="tile bg-lime-600 lg:col-span-2 lg:col-start-4"> <h1 class="tile-marker">FOUR</h1> </div>可以看到,从lg起第四个div从第 4 列开始向右跨两列,代码比“start + end 双写”精简得多。
但此时如果仔细观察整体布局,会发现它仍是一盘散沙:最后两个div游离在整体之外。原因是我们只完成了“一半的方程式”——只用了 Grid 的列相关类,还没有显式管理 Grid 的行。
显式定义行:grid-rows-{n}
要对栅格有更精细的控制,需要像声明列一样显式声明行数。目前行数是隐式(implicit)生成的,现在我们显式声明 5 行:
<div class="container m-auto grid grid-cols-3 grid-rows-5 gap-4 md:grid-cols-5 lg:grid-cols-8" ></div>grid-rows-5对应grid-template-rows: repeat(5, minmax(0, 1fr)),等价地创建 5 个等高的行轨道。
用row-start-{n}把页脚放到最底行
现在把最后一个div设置为页脚:小屏时放到最底行的左侧,md之后横跨整行:
<div class="tile row-start-5 bg-pink-500 md:col-span-full"> <h1 class="tile-marker">NINE</h1> </div>row-start-5告诉页脚从第 5 行(最底行)开始放置;配合md:col-span-full,页脚在md之后铺满整行。
跨行:row-start-{n}+row-end-{n}
让第二个div占据导航栏与页脚之间的左侧整列——起点选在第 2 行、终点为第 5 行:
<div class="tile col-span-1 row-start-2 row-end-5 bg-amber-500 md:col-span-2 lg:col-span-3" > <h1 class="tile-marker">TWO</h1> </div>此时页面开始成型:顶部导航、左侧侧栏、右侧内容、底部页脚,一个接近真实网站的布局已经出现。
在不同断点重排区块(Reorder Regions)
改变row-start-{n}/col-start-{n}中n的值,就可以在不同断点重新排列区块顺序。
例如把第三个div当作一个广告位:小屏时展示在底部,md之后移到顶部。先设置小屏顺序,再在md覆盖:
<div class="tile col-span-2 row-start-4 row-end-5 bg-yellow-500 md:col-span-3 md:row-start-2 md:row-end-3 lg:col-span-5" > <h1 class="tile-marker">THREE</h1> </div>在小屏上,它位于第 4 行(接近底部);一旦超过md,md:row-start-2 md:row-end-3把它提升到第 2 行(靠近顶部),列宽也随之调整为md:col-span-3。这种“同一个 DOM 节点在不同断点出现在不同区域”的能力,是 CSS Grid 相对于传统 float/Flexbox 布局的重要优势,且无需改动 HTML 结构顺序。
完整的最终代码
整合以上所有步骤后,最终的index.html如下:
<!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link href="/dist/styles.css" rel="stylesheet" /> <title>Document</title> </head> <body> <div class="container m-auto grid grid-cols-3 grid-rows-5 gap-4 md:grid-cols-5 lg:grid-cols-8" > <div class="tile col-span-full bg-teal-500"> <h1 class="tile-marker">ONE</h1> </div> <div class="tile col-span-1 row-start-2 row-end-5 bg-amber-500 md:col-span-2 lg:col-span-3" > <h1 class="tile-marker">TWO</h1> </div> <div class="tile col-span-2 row-start-4 row-end-5 bg-yellow-500 md:col-span-3 md:row-start-2 md:row-end-3 lg:col-span-5" > <h1 class="tile-marker">THREE</h1> </div> <div class="tile bg-lime-600 lg:col-span-2 lg:col-start-4"> <h1 class="tile-marker">FOUR</h1> </div> <div class="tile bg-green-600"> <h1 class="tile-marker">FIVE</h1> </div> <div class="tile bg-emerald-500"> <h1 class="tile-marker">SIX</h1> </div> <div class="tile bg-teal-500"> <h1 class="tile-marker">SEVEN</h1> </div> <div class="tile bg-purple-500"> <h1 class="tile-marker">EIGHT</h1> </div> <div class="tile row-start-5 bg-pink-500 md:col-span-full"> <h1 class="tile-marker">NINE</h1> </div> </div> </body> </html>Tailwind CSS Grid 类速查表
| 类 | 作用 | 示例 |
|---|---|---|
grid | 启用 Grid 布局 | <div class="grid"></div> |
grid-cols-{n} | 设置列数 | grid-cols-3 |
gap-{size} | 在栅格项之间添加间距 | gap-4 |
col-span-{n} | 让栅格项跨越多列 | col-span-2 |
row-start-{n} | 设置栅格项的起始行 | row-start-2 |
grid-rows-{n} | 设置行数 | grid-rows-5 |
在此基础上,还有对称的row-span-{n}、row-end-{n}、col-start-{n}、col-end-{n},以及独立的gap-x-{size}/gap-y-{size},可以按需组合出任意二维栅格结构。
自定义 Tailwind 主题:把列数扩展到 16
Tailwind 的 Grid 相关配置都可以按需修改。假设业务设计需要最多16 列,那么必须能生成从 0 到 16 的所有grid-cols-{n}类,同时还要生成对应的col-start-{n}与col-end-{n}类。
在tailwind.config.js中通过extend扩展gridTemplateColumns、gridColumnStart、gridColumnEnd三个属性即可:
// tailwind.config.js module.exports = { theme: { extend: { gridTemplateColumns: { // Simple 16 column grid "16": "repeat(16, minmax(0, 1fr))", }, gridColumnStart: { "13": "13", "14": "14", "15": "15", "16": "16", "17": "17", }, gridColumnEnd: { "13": "13", "14": "14", "15": "15", "16": "16", "17": "17", }, }, }, };说明几点:
gridTemplateColumns: { "16": ... }生成grid-cols-16类(n从 1 到 12 的类由默认主题提供,extend不会覆盖它们)。- 16 列栅格中,元素的起始/结束行号理论上最多到 17(因为列线数量 = 列数 + 1),所以
gridColumnStart与gridColumnEnd需要补上 13 到 17 的取值。 - 配置完成后,重新构建样式(
npx tailwindcss -i ./src/styles.css -o ./dist/styles.css --watch会自动触发),grid-cols-16、col-start-13等类即可直接使用。
这种“扩展生成规则”的思路也适用于其他维度。在 refine 仓库中可以看到大量同类实践:
- examples/store/tailwind.config.js 通过
theme.extend.screens自定义了整套断点(2xsmall: 320px、small: 1024px、medium: 1280px、large: 1440px、xlarge: 1680px等),说明断点体系本身也可以按项目需求重定义; - examples/blog-refine-shadcn/tailwind.config.js 通过
content指定了pages/、components/、app/、src/等目录,Tailwind 只从这些路径扫描类名并生成用到的工具类; - examples/blog-refine-daisyui/tailwind.config.js 同样声明了
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],并挂载daisyui插件——这说明 Grid 类与其他工具类遵循同一套 JIT 扫描机制,只有出现在源码里的类才会被编译进产物。
refine 仓库中的真实 Grid 用法佐证
本文的布局思路在 refine 仓库的 Tailwind 示例中有大量同款实践,可以作为进阶参考:
电商示例:响应式商品栅格
examples/store/pages/index.tsx 中的商品列表容器就是标准的“移动优先列数递增”写法:
className={clsx( "flex-1", "grid", "grid-cols-1", "md:grid-cols-2", "lg:grid-cols-3", "gap-x-8", "gap-y-16", )}- 基线 1 列 →
md起 2 列 →lg起 3 列; - 用
gap-x-8/gap-y-16分别控制列间距与行间距(比gap-{size}更精细)。
这与本文“grid-cols-{n}定列数 + 断点前缀定切换时机”的方法完全一致,只是把{n}换成了 1/2/3,并叠加了商品卡片组件。
账号概览:@apply组合 Grid 类
examples/store/src/components/account/OverviewTemplate/OverviewTemplate.module.css 展示了在 CSS 中通过@apply复用 Grid 工具类:
.overviewWapper { @apply col-span-1 row-span-2 flex h-full flex-1 flex-col gap-y-4; }以及同文件中的grid grid-cols-3 grid-rows-2 gap-x-4(第 78 行),用一个 3 列 2 行的显式栅格组织数据展示区域。这印证了grid-rows-{n}与col-span-{n}在真实后台/电商界面中的典型用法。
集成方式:CLI 与 PostCSS 两条路线
本文开头的npx tailwindcss ... --watch是 Tailwind CLI 路线;refine 的 Tailwind 示例则普遍采用 PostCSS 集成(每个示例都同时包含 postcss.config.js 与 tailwind.config.js),在 Vite/Next.js 构建链中由 PostCSS 插件处理 Tailwind。两种方式生成的类完全一致,选择哪一种取决于项目构建工具。
常见问题(FAQ)
Q:如何在 Tailwind CSS 中创建类似 CSS Grid 的响应式布局?A:将grid与grid-cols-{n}组合用于定义列,并为md:、lg:等响应式前缀添加对应的列数变体,即可在指定断点调整布局。
Q:Flexbox 与 Grid 在 Tailwind 中的主要区别是什么?A:Flexbox 适合简单的一维布局(行或列),而 Grid 适合同时涉及行与列的复杂二维布局。本文的目标页面(导航 + 侧栏 + 主内容 + 页脚)就是典型的二维场景,应优先选择 Grid。
Q:如何在 Tailwind 中为栅格项添加间距?A:使用gap-{size}类,例如gap-4可为所有方向提供一致的间距;如需分别控制,用gap-x-{size}与gap-y-{size}。
Q:可以自定义 Tailwind Grid 可用的列数吗?A:可以。在tailwind.config.js中扩展gridTemplateColumns属性(以及配套的gridColumnStart/gridColumnEnd)即可生成自定义数量的列类,例如本文演示的 16 列栅格。
结语
本文使用 Tailwind 的 CSS Grid 类构建了一个简单的响应式布局:通过grid-cols-{n}与grid-rows-{n}显式声明行列数,通过md:、lg:等移动优先断点在不同屏幕宽度下重置布局,并用col-span-{n}、col-start-{n}、row-start-{n}、row-end-{n}精确控制每个区块的尺寸与位置,甚至可以在断点切换时重排区块顺序。
更进一步,Tailwind 允许我们通过tailwind.config.js扩展生成类的规则——无论是把列数从 12 扩展到 16,还是像 refine 仓库的 store 示例 那样重定义整套断点——从而构建完全贴合业务需求的流畅响应式布局。这套“显式行列 + 断点前缀 + 扩展主题”的方法论,同样适用于 refine 构建后台面板、仪表盘等二维密集型界面时的布局工作。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考