tailwindcss-mobile-first by josiahsiegel/claude-plugin-marketplace
npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill tailwindcss-mobile-first移动优先设计是 2025/2026 年的行业标准。随着移动流量持续占据全球网络流量的 60% 以上,以及谷歌的移动优先索引策略,从移动端开始设计能确保最佳用户体验和 SEO 表现。
<!-- 正确:移动优先(渐进增强) -->
<div class="text-sm md:text-base lg:text-lg">
从小开始,向上增强
</div>
<!-- 错误:桌面优先(优雅降级) -->
<div class="lg:text-lg md:text-base text-sm">
从大开始,向下缩减(更多代码,更多错误)
</div>
关键原则:无前缀的实用类适用于所有屏幕尺寸。断点前缀从该尺寸开始应用,并向上生效。
| 前缀 | 最小宽度 | 目标设备 |
|---|---|---|
| (无) | 0px | 所有手机(基础) |
sm: | 640px (40rem) | 大屏手机、小平板 |
md: |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 768px (48rem) |
| 平板(竖屏) |
lg: | 1024px (64rem) | 平板(横屏)、笔记本电脑 |
xl: | 1280px (80rem) | 台式机 |
2xl: | 1536px (96rem) | 大屏台式机 |
2025/2026 最佳实践:让内容决定断点,而不是设备尺寸。
@theme {
/* 根据你的内容需求覆盖默认值 */
--breakpoint-sm: 36rem; /* 576px - 当你的内容需要更多空间时 */
--breakpoint-md: 48rem; /* 768px */
--breakpoint-lg: 62rem; /* 992px - 常见内容宽度 */
--breakpoint-xl: 75rem; /* 1200px */
--breakpoint-2xl: 90rem; /* 1440px */
/* 为特定内容需求添加自定义断点 */
--breakpoint-xs: 20rem; /* 320px - 非常小的设备 */
--breakpoint-3xl: 120rem; /* 1920px - 超宽屏 */
}
<!-- 覆盖最常见的设备范围(2025/2026 数据) -->
<!-- 375px-430px:约 50% 的移动设备(iPhone、现代 Android) -->
<div class="px-4">移动端基础</div>
<!-- 768px+:平板和小型笔记本电脑 -->
<div class="px-4 md:px-6">平板增强</div>
<!-- 1024px+:桌面体验 -->
<div class="px-4 md:px-6 lg:px-8">桌面增强</div>
<!-- 1440px+:宽屏桌面体验 -->
<div class="px-4 md:px-6 lg:px-8 xl:px-12">宽屏桌面</div>
流体排版消除了断点之间突兀的尺寸跳跃:
@theme {
/* 流体排版比例 */
--text-fluid-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-fluid-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
--text-fluid-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--text-fluid-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem);
--text-fluid-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem);
--text-fluid-2xl: clamp(1.5rem, 1.1rem + 2vw, 2rem);
--text-fluid-3xl: clamp(1.875rem, 1.2rem + 3.375vw, 2.5rem);
--text-fluid-4xl: clamp(2.25rem, 1rem + 6.25vw, 3.5rem);
--text-fluid-5xl: clamp(3rem, 1rem + 10vw, 5rem);
}
无障碍访问重要性:始终将 vw 与 rem 结合使用,以尊重用户的缩放偏好(符合 WCAG 标准)。
<!-- 平滑缩放的流体标题 -->
<h1 class="text-fluid-4xl font-bold leading-tight">
响应式标题
</h1>
<!-- 流体正文文本 -->
<p class="text-fluid-base leading-relaxed max-w-prose">
根据视口按比例缩放的主体文本,
同时尊重用户的字体大小偏好。
</p>
<!-- 流体文本配合断点覆盖以实现精细控制 -->
<h2 class="text-fluid-2xl lg:text-fluid-3xl font-semibold">
章节标题
</h2>
@theme {
/* 流体间距比例 */
--spacing-fluid-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem);
--spacing-fluid-sm: clamp(0.5rem, 0.4rem + 0.5vw, 1rem);
--spacing-fluid-md: clamp(1rem, 0.75rem + 1.25vw, 2rem);
--spacing-fluid-lg: clamp(1.5rem, 1rem + 2.5vw, 3rem);
--spacing-fluid-xl: clamp(2rem, 1.25rem + 3.75vw, 4rem);
--spacing-fluid-2xl: clamp(3rem, 1.5rem + 7.5vw, 6rem);
--spacing-fluid-section: clamp(4rem, 2rem + 10vw, 8rem);
}
<!-- 流体章节内边距 -->
<section class="py-fluid-section px-fluid-md">
<div class="max-w-7xl mx-auto">
<h2 class="mb-fluid-lg">章节标题</h2>
<div class="grid gap-fluid-md grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
<!-- 卡片 -->
</div>
</div>
</section>
<!-- 带有流体间距的英雄区域 -->
<header class="min-h-screen flex items-center py-fluid-2xl px-fluid-md">
<div class="max-w-4xl">
<h1 class="text-fluid-5xl mb-fluid-md">英雄标题</h1>
<p class="text-fluid-lg mb-fluid-lg">英雄描述</p>
<button class="px-fluid-md py-fluid-sm">开始使用</button>
</div>
</header>
最小尺寸(2025 年标准):
WCAG 2.2 AA 级:最小 24x24 CSS 像素
推荐:44x44 CSS 像素(苹果、谷歌、微软指南)
最佳:关键操作使用 48x48 CSS 像素
<!-- 符合 WCAG 2.2 的触摸目标 --> <!-- 最小 AA 级合规(24px) --> <button class="min-h-6 min-w-6 p-1"> <svg class="h-4 w-4">...</svg> </button> <!-- 推荐尺寸(44px) --> <button class="min-h-11 min-w-11 p-2.5"> <svg class="h-6 w-6">...</svg> <span class="sr-only">操作</span> </button> <!-- 主要操作最佳尺寸(48px) --> <button class="min-h-12 min-w-12 px-6 py-3 text-base"> 主要操作 </button><!-- 将触摸目标扩展到可见元素之外 -->
<a href="/link" class="relative inline-block text-sm">
小号可见链接
<!-- 不可见的扩展触摸区域 -->
<span class="absolute -inset-3" aria-hidden="true"></span>
</a>
<!-- 带有扩展目标的图标按钮 -->
<button class="relative p-2 -m-2 rounded-lg hover:bg-gray-100">
<svg class="h-5 w-5" aria-hidden="true">...</svg>
<span class="sr-only">关闭</span>
</button>
<!-- 触摸目标之间有足够的间距(最小 8px 间隙) -->
<nav class="flex gap-3">
<a href="#" class="min-h-11 px-4 py-2.5">首页</a>
<a href="#" class="min-h-11 px-4 py-2.5">关于</a>
<a href="#" class="min-h-11 px-4 py-2.5">联系</a>
</nav>
<!-- 堆叠导航,带有足够间距 -->
<nav class="flex flex-col">
<a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">链接 1</a>
<a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">链接 2</a>
<a href="#" class="py-3 px-4 min-h-11">链接 3</a>
</nav>
<!-- 带有安全间距的按钮组 -->
<div class="flex flex-wrap gap-3">
<button class="min-h-11 px-4 py-2">取消</button>
<button class="min-h-11 px-4 py-2 bg-blue-600 text-white">确认</button>
</div>
容器查询实现了 组件级响应式,独立于视口尺寸。
@import "tailwindcss";
@plugin "@tailwindcss/container-queries";
| 类 | 最小宽度 |
|---|---|
@xs | 20rem (320px) |
@sm | 24rem (384px) |
@md | 28rem (448px) |
@lg | 32rem (512px) |
@xl | 36rem (576px) |
@2xl | 42rem (672px) |
@3xl | 48rem (768px) |
@4xl | 56rem (896px) |
@5xl | 64rem (1024px) |
<!-- 响应其容器而非视口的卡片 -->
<article class="@container">
<div class="
flex flex-col @sm:flex-row
gap-4 p-4
bg-white rounded-xl shadow-sm
">
<img
src="..."
class="
w-full @sm:w-32 @lg:w-48
aspect-video @sm:aspect-square
object-cover rounded-lg
"
/>
<div class="flex-1 min-w-0">
<h3 class="text-base @md:text-lg @lg:text-xl font-semibold truncate">
卡片标题
</h3>
<p class="
text-sm @md:text-base
text-gray-600
line-clamp-2 @lg:line-clamp-3
mt-2
">
根据可用空间自适应的描述...
</p>
<div class="mt-4 flex flex-wrap gap-2 @md:gap-3">
<span class="text-xs @md:text-sm px-2 py-1 bg-gray-100 rounded">标签</span>
</div>
</div>
</div>
</article>
<div class="@container/sidebar w-64">
<nav class="
flex flex-col @lg/sidebar:flex-row
@lg/sidebar:items-center
gap-2 @lg/sidebar:gap-4
">
<a href="#">链接 1</a>
<a href="#">链接 2</a>
</nav>
</div>
<main class="@container/main flex-1">
<div class="
grid grid-cols-1
@md/main:grid-cols-2
@xl/main:grid-cols-3
gap-6
">
<!-- 网格项响应主容器 -->
</div>
</main>
| 使用容器查询 | 使用视口查询 |
|---|---|
| 可复用组件 | 页面级布局 |
| 侧边栏小部件 | 导航栏 |
| 卡片网格 | 英雄区域 |
| 嵌入式内容 | 全宽区域 |
| CMS/动态内容 | 固定应用外壳 |
<!-- 具有最小卡片宽度的自动响应网格 -->
<div class="
grid gap-6
grid-cols-1
sm:grid-cols-2
lg:grid-cols-3
xl:grid-cols-4
">
<!-- 卡片自动流动 -->
</div>
<!-- CSS Grid auto-fit(无需断点) -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-6">
<!-- 卡片自动适应,最小 280px -->
</div>
<!-- CSS Grid auto-fill 用于固定尺寸项 -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
<!-- 固定最小尺寸,增长以填充 -->
</div>
<!-- 堆叠到行 -->
<div class="flex flex-col md:flex-row gap-4">
<div class="flex-1">内容 1</div>
<div class="flex-1">内容 2</div>
</div>
<!-- 换行并居中项目 -->
<div class="flex flex-wrap justify-center gap-4">
<div class="w-full sm:w-auto">项目 1</div>
<div class="w-full sm:w-auto">项目 2</div>
<div class="w-full sm:w-auto">项目 3</div>
</div>
<!-- 移动端堆叠,桌面端内联并推挤 -->
<div class="flex flex-col sm:flex-row sm:items-center gap-4">
<div class="flex-1">
<h3>标题</h3>
<p class="text-sm text-gray-600">描述</p>
</div>
<button class="sm:ml-auto min-h-11 px-4 py-2">操作</button>
</div>
<!-- 可折叠侧边栏 -->
<div class="flex min-h-screen">
<!-- 侧边栏:移动端隐藏,桌面端显示 -->
<aside class="
hidden lg:flex lg:flex-col
w-64 border-r bg-gray-50
">
<nav class="flex-1 p-4">...</nav>
</aside>
<!-- 主要内容 -->
<main class="flex-1 p-4 lg:p-8">
内容
</main>
</div>
<!-- 移动端抽屉 + 桌面端侧边栏 -->
<div class="relative flex min-h-screen">
<!-- 移动端抽屉遮罩 -->
<div class="
fixed inset-0 z-40 lg:hidden
bg-black/50
data-[open=false]:hidden
" data-open="false">
<aside class="w-64 h-full bg-white">
移动端导航
</aside>
</div>
<!-- 桌面端侧边栏 -->
<aside class="hidden lg:block w-64 border-r">
桌面端导航
</aside>
<main class="flex-1">内容</main>
</div>
<div class="min-h-screen grid grid-rows-[auto_1fr_auto]">
<!-- 页眉 -->
<header class="sticky top-0 z-50 h-16 bg-white border-b shadow-sm">
<nav class="h-full max-w-7xl mx-auto px-4 flex items-center justify-between">
<Logo />
<ul class="hidden md:flex gap-6">...</ul>
<button class="md:hidden min-h-11 min-w-11">菜单</button>
</nav>
</header>
<!-- 主要内容区域,可选侧边栏 -->
<div class="
grid
grid-cols-1
md:grid-cols-[240px_1fr]
lg:grid-cols-[240px_1fr_280px]
gap-0
">
<nav class="hidden md:block border-r p-4">左侧导航</nav>
<main class="p-4 md:p-6 lg:p-8">主要内容</main>
<aside class="hidden lg:block border-l p-4">右侧边栏</aside>
</div>
<!-- 页脚 -->
<footer class="bg-gray-900 text-white py-8 md:py-12">
页脚内容
</footer>
</div>
<!-- 在所有尺寸下保持宽高比 -->
<div class="aspect-video overflow-hidden rounded-lg">
<img src="..." class="w-full h-full object-cover" loading="lazy" />
</div>
<!-- 响应式宽高比 -->
<div class="aspect-square sm:aspect-video lg:aspect-[4/3] overflow-hidden">
<img src="..." class="w-full h-full object-cover" />
</div>
<!-- 使用 picture 元素进行艺术指导 -->
<picture>
<source media="(min-width: 1024px)" srcset="large.jpg" />
<source media="(min-width: 640px)" srcset="medium.jpg" />
<img
src="small.jpg"
alt="描述"
class="w-full h-auto rounded-lg"
loading="lazy"
/>
</picture>
<!-- 响应式 srcset -->
<img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w,
image-1600.jpg 1600w
"
sizes="
(min-width: 1280px) 1200px,
(min-width: 768px) 80vw,
100vw
"
alt="响应式图片"
class="w-full h-auto"
loading="lazy"
/>
<!-- 移动优先标题比例 -->
<h1 class="
text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl
font-bold leading-tight tracking-tight
">
主标题
</h1>
<h2 class="
text-xl sm:text-2xl md:text-3xl lg:text-4xl
font-semibold leading-snug
">
章节标题
</h2>
<h3 class="
text-lg sm:text-xl md:text-2xl
font-medium
">
子章节标题
</h3>
<!-- 最佳行长和间距 -->
<article class="max-w-prose mx-auto">
<p class="
text-base md:text-lg
leading-relaxed md:leading-loose
text-gray-700 dark:text-gray-300
">
为可读性优化的正文文本,每行 45-75 个字符。
</p>
</article>
<!-- 跨行平衡的文本 -->
<h2 class="text-balance text-2xl md:text-3xl font-bold max-w-2xl">
此标题将跨行平衡文本以避免孤行
</h2>
<nav class="relative">
<!-- 桌面端导航 -->
<ul class="hidden md:flex items-center gap-6">
<li><a href="#" class="py-2 hover:text-blue-600">首页</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">产品</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">关于</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">联系</a></li>
</ul>
<!-- 移动端菜单按钮 -->
<button
class="md:hidden min-h-11 min-w-11 p-2"
aria-expanded="false"
aria-controls="mobile-menu"
aria-label="切换导航菜单"
>
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<!-- 移动端菜单(通过 JS 控制) -->
<div
id="mobile-menu"
class="
md:hidden
absolute top-full left-0 right-0
bg-white shadow-lg border-t
hidden
"
>
<ul class="py-2">
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">首页</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">产品</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">关于</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">联系</a></li>
</ul>
</div>
</nav>
<!-- 移动端固定底部导航 -->
<nav class="
fixed bottom-0 inset-x-0 z-50
md:hidden
bg-white border-t shadow-lg
safe-area-pb
">
<ul class="flex justify-around">
<li>
<a href="#" class="
flex flex-col items-center
min-h-14 min-w-14 px-3 py-2
text-xs
text-gray-600 hover:text-blue-600
aria-current:text-blue-600
">
<svg class="h-6 w-6 mb-1">...</svg>
首页
</a>
</li>
<li>
<a href="#" class="flex flex-col items-center min-h-14 min-w-14 px-3 py-2 text-xs">
<svg class="h-6 w-6 mb-1">...</svg>
搜索
</a>
</li>
<li>
<a href="#" class="flex flex-col items-center min-h-14 min-w-14 px-3 py-2 text-xs">
<svg class="h-6 w-6 mb-1">...</svg>
个人资料
</a>
</li>
</ul>
</nav>
<!-- 为主内容添加内边距以防止重叠 -->
<main class="pb-20 md:pb-0">
内容
</main>
@utility safe-area-pt {
padding-top: env(safe-area-inset-top);
}
@utility safe-area-pb {
padding-bottom: env(safe-area-inset-bottom);
}
@utility safe-area-pl {
padding-left: env(safe-area-inset-left);
}
@utility safe-area-pr {
padding-right: env(safe-area-inset-right);
}
@utility safe-area-p {
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
<!-- 尊重刘海的页眉 -->
<header class="sticky top-0 safe-area-pt bg-white">
<div class="h-16 flex items-center px-4">
导航
</div>
</header>
<!-- 带有安全区域的底部导航 -->
<nav class="fixed bottom-0 inset-x-0 safe-area-pb bg-white border-t">
底部导航
</nav>
<!-- 图片的原生懒加载 -->
<img src="image.jpg" alt="..." loading="lazy" class="w-full h-auto" />
<!-- 懒加载首屏以下内容 -->
<div class="contents" data-lazy-load>
按需加载的重型组件
</div>
@utility content-auto {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
<!-- 跳过渲染屏幕外内容 -->
<section class="content-auto">
可能位于屏幕外的大区域
</section>
<!-- 在慢速连接上显示简化版本 -->
<picture>
<source
srcset="video-poster.jpg"
media="(prefers-reduced-data: reduce)"
/>
<img src="animated.gif" alt="..." />
</picture>
使用 Tailwind 的 aspect-* 实用类在所有屏幕尺寸下保持正确的视频比例,防止累积布局偏移(CLS):
<!-- 标准 16:9 视频容器 -->
<div class="aspect-video overflow-hidden rounded-lg bg-black">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
</div>
<!-- 移动优先社交内容的竖屏视频(9:16) -->
<div class="aspect-[9/16] overflow-hidden rounded-lg bg-black max-w-sm mx-auto">
<video class="w-full h-full object-cover" playsinline muted loop>
<source src="reel.mp4" type="video/mp4" />
</video>
</div>
<!-- 社交动态常见的方形视频(1:1) -->
<div class="aspect-square overflow-hidden rounded-lg bg-black">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="square.mp4" type="video/mp4" />
</video>
</div>
<!-- 响应式宽高比 - 移动端竖屏,桌面端宽屏 -->
<div class="aspect-[9/16] sm:aspect-video overflow-hidden rounded-lg bg-black">
<video class="w-full h-full object-contain" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
</div>
未定义尺寸的视频在加载时会导致布局偏移。使用宽高比容器来预留空间:
<!-- 带有海报和骨架加载器的 CLS 安全视频 -->
<div class="relative aspect-video overflow-hidden rounded-lg bg-gray-900">
<!-- 视频加载时的骨架占位符 -->
<div class="absolute inset-0 animate-pulse bg-gray-800" aria-hidden="true"></div>
<video
class="absolute inset-0 w-full h-full object-cover"
poster="/thumbnails/video-poster.jpg"
playsinline
preload="metadata"
>
<source src="video.mp4" type="video/mp4" />
</video>
</div>
<!-- 零 CLS 的响应式视频缩略图网格 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="aspect-video overflow-hidden rounded-lg bg-gray-900">
<video class="w-full h-full object-cover" poster="/thumb1.jpg" preload="none" playsinline></video>
</div>
<div class="aspect-video overflow-hidden rounded-lg bg-gray-900">
<video class="w-full h-full object-cover" poster="/thumb2.jpg" preload="none" playsinline></video>
</div>
<div class="aspect-video overflow-hidden rounded-lg bg-gray-900">
<video class="w-full h-full object-cover" poster="/thumb3.jpg" preload="none" playsinline></video>
</div>
</div>
<!-- 移动端全宽,桌面端居中并限制最大宽度 -->
<div class="w-full lg:max-w-4xl lg:mx-auto">
<div class="aspect-video overflow-hidden lg:rounded-xl bg-black">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="hero.mp4" type="video/mp4" />
</video>
</div>
</div>
<!-- 全出血英雄视频背景 -->
<section class="relative w-full h-screen overflow-hidden">
<video
class="absolute inset-0 w-full h-full object-cover"
autoplay muted loop playsinline
preload="auto"
>
<source src="hero-bg.mp4" type="video/mp4" />
</video>
<!-- 带有安全内边距的内容覆盖层 -->
<div class="relative z-10 flex items-center justify-center h-full px-4 sm:px-8">
<div class="text-center text-white max-w-2xl">
<h1 class="text-3xl sm:text-4xl lg:text-6xl font-bold">英雄标题</h1>
<p class="mt-4 text-lg sm:text-xl">副标题文本</p>
</div>
</div>
<!-- 用于文本可读性的深色覆盖层 -->
<div class="absolute inset-0 bg-black/40" aria-hidden="true"></div>
</section>
视频播放器控件和 UI 元素需要触摸友好的尺寸和适当的放置位置:
<!-- 带有触摸安全控件的自定义视频播放器 -->
<div class="@container relative aspect-video overflow-hidden rounded-lg bg-black group">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
<!-- 播放/暂停覆盖层 - 居中,触摸友好 -->
<button class="
absolute inset-0 flex items-center justify-center
bg-black/0 group-hover:bg-black/20 transition-colors
">
<span class="
w-16 h-16 sm:w-20 sm:h-20
flex items-center justify-center
rounded-full bg-white/90
shadow-lg
">
<svg class="w-8 h-8 sm:w-10 sm:h-10 text-gray-900 ml-1" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z"/>
</svg>
</span>
</button>
<!-- 底部控制栏 - 尊重触摸目标 -->
<div class="
absolute bottom-0 inset-x-0
bg-gradient-to-t from-black/80 to-transparent
p-3 sm:p-4
flex items-center gap-3
opacity-0 group-hover:opacity-100 transition-opacity
">
<!-- 进度条 - 足够高以支持触摸 -->
<div class="flex-1 h-1 bg-white/30 rounded-full cursor-pointer">
<div class="h-full w-1/3 bg-white rounded-full relative">
<span class="absolute right-0 top-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full -mr-2"></span>
</div>
</div>
<!-- 时间显示 -->
<span class="text-white text-xs sm:text-sm tabular-nums shrink-0">1:23 / 4:56</span>
<!-- 全屏按钮 - 触摸友好的最小尺寸 -->
<button class="min-h-11 min-w-11 flex items-center justify-center text-white">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
</svg>
</button>
</div>
</div>
根据容器大小调整布局的视频播放器组件——当同一个播放器出现在侧边栏、主要内容区域或模态框中时非常有用:
<!-- 根据其容器自适应的视频播放器 -->
<div class="@container">
<div class="
flex flex-col @lg:flex-row
gap-4 @lg:gap-6
bg-gray-950 rounded-xl overflow-hidden
">
<!-- 视频区域 -->
<div class="
w-full @lg:flex-1
aspect-video
overflow-hidden
">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
</div>
<!-- 信息面板 - 窄容器中在视频下方,宽容器中在旁边 -->
<div class="
p-
Mobile-first design is the industry standard for 2025/2026. With mobile traffic consistently exceeding 60% of global web traffic and Google's mobile-first indexing, starting with mobile ensures optimal user experience and SEO performance.
<!-- CORRECT: Mobile-first (progressive enhancement) -->
<div class="text-sm md:text-base lg:text-lg">
Start small, enhance upward
</div>
<!-- INCORRECT: Desktop-first (graceful degradation) -->
<div class="lg:text-lg md:text-base text-sm">
Starts large, reduces down (more code, more bugs)
</div>
Key Principle : Unprefixed utilities apply to ALL screen sizes. Breakpoint prefixes apply at that size AND ABOVE.
| Prefix | Min-width | Target Devices |
|---|---|---|
| (none) | 0px | All mobile phones (base) |
sm: | 640px (40rem) | Large phones, small tablets |
md: | 768px (48rem) | Tablets (portrait) |
lg: | 1024px (64rem) | Tablets (landscape), laptops |
xl: | 1280px (80rem) | Desktops |
2xl: | 1536px (96rem) | Large desktops |
Best Practice 2025/2026 : Let content determine breakpoints, not device dimensions.
@theme {
/* Override defaults based on YOUR content needs */
--breakpoint-sm: 36rem; /* 576px - when your content needs more space */
--breakpoint-md: 48rem; /* 768px */
--breakpoint-lg: 62rem; /* 992px - common content width */
--breakpoint-xl: 75rem; /* 1200px */
--breakpoint-2xl: 90rem; /* 1440px */
/* Add custom breakpoints for specific content needs */
--breakpoint-xs: 20rem; /* 320px - very small devices */
--breakpoint-3xl: 120rem; /* 1920px - ultra-wide */
}
<!-- Cover the most common device ranges (2025/2026 data) -->
<!-- 375px-430px: ~50% of mobile devices (iPhone, modern Android) -->
<div class="px-4">Mobile base</div>
<!-- 768px+: Tablets and small laptops -->
<div class="px-4 md:px-6">Tablet enhancement</div>
<!-- 1024px+: Desktop experience -->
<div class="px-4 md:px-6 lg:px-8">Desktop enhancement</div>
<!-- 1440px+: Wide desktop experience -->
<div class="px-4 md:px-6 lg:px-8 xl:px-12">Wide desktop</div>
Fluid typography eliminates jarring size jumps between breakpoints:
@theme {
/* Fluid typography scale */
--text-fluid-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-fluid-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
--text-fluid-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--text-fluid-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem);
--text-fluid-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem);
--text-fluid-2xl: clamp(1.5rem, 1.1rem + 2vw, 2rem);
--text-fluid-3xl: clamp(1.875rem, 1.2rem + 3.375vw, 2.5rem);
--text-fluid-4xl: clamp(2.25rem, 1rem + 6.25vw, 3.5rem);
--text-fluid-5xl: clamp(3rem, 1rem + 10vw, 5rem);
}
Important for Accessibility : Always combine vw with rem to respect user zoom preferences (WCAG compliance).
<!-- Fluid heading that scales smoothly -->
<h1 class="text-fluid-4xl font-bold leading-tight">
Responsive Heading
</h1>
<!-- Fluid body text -->
<p class="text-fluid-base leading-relaxed max-w-prose">
Body text that scales proportionally with the viewport
while respecting user's font size preferences.
</p>
<!-- Fluid with breakpoint overrides for fine control -->
<h2 class="text-fluid-2xl lg:text-fluid-3xl font-semibold">
Section Title
</h2>
@theme {
/* Fluid spacing scale */
--spacing-fluid-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem);
--spacing-fluid-sm: clamp(0.5rem, 0.4rem + 0.5vw, 1rem);
--spacing-fluid-md: clamp(1rem, 0.75rem + 1.25vw, 2rem);
--spacing-fluid-lg: clamp(1.5rem, 1rem + 2.5vw, 3rem);
--spacing-fluid-xl: clamp(2rem, 1.25rem + 3.75vw, 4rem);
--spacing-fluid-2xl: clamp(3rem, 1.5rem + 7.5vw, 6rem);
--spacing-fluid-section: clamp(4rem, 2rem + 10vw, 8rem);
}
<!-- Fluid section padding -->
<section class="py-fluid-section px-fluid-md">
<div class="max-w-7xl mx-auto">
<h2 class="mb-fluid-lg">Section Title</h2>
<div class="grid gap-fluid-md grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
<!-- Cards -->
</div>
</div>
</section>
<!-- Hero section with fluid spacing -->
<header class="min-h-screen flex items-center py-fluid-2xl px-fluid-md">
<div class="max-w-4xl">
<h1 class="text-fluid-5xl mb-fluid-md">Hero Title</h1>
<p class="text-fluid-lg mb-fluid-lg">Hero description</p>
<button class="px-fluid-md py-fluid-sm">Get Started</button>
</div>
</header>
Minimum sizes (2025 standards) :
WCAG 2.2 Level AA : 24x24 CSS pixels minimum
Recommended : 44x44 CSS pixels (Apple, Google, Microsoft guidelines)
Optimal : 48x48 CSS pixels for critical actions
<!-- WCAG 2.2 compliant touch targets --> <!-- Minimum AA compliant (24px) --> <button class="min-h-6 min-w-6 p-1"> <svg class="h-4 w-4">...</svg> </button> <!-- Recommended size (44px) --> <button class="min-h-11 min-w-11 p-2.5"> <svg class="h-6 w-6">...</svg> <span class="sr-only">Action</span> </button> <!-- Optimal for primary actions (48px) --> <button class="min-h-12 min-w-12 px-6 py-3 text-base"> Primary Action </button><!-- Extend touch target beyond visible element -->
<a href="/link" class="relative inline-block text-sm">
Small visible link
<!-- Invisible extended touch area -->
<span class="absolute -inset-3" aria-hidden="true"></span>
</a>
<!-- Icon button with extended target -->
<button class="relative p-2 -m-2 rounded-lg hover:bg-gray-100">
<svg class="h-5 w-5" aria-hidden="true">...</svg>
<span class="sr-only">Close</span>
</button>
<!-- Adequate spacing between touch targets (minimum 8px gap) -->
<nav class="flex gap-3">
<a href="#" class="min-h-11 px-4 py-2.5">Home</a>
<a href="#" class="min-h-11 px-4 py-2.5">About</a>
<a href="#" class="min-h-11 px-4 py-2.5">Contact</a>
</nav>
<!-- Stacked navigation with adequate spacing -->
<nav class="flex flex-col">
<a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">Link 1</a>
<a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">Link 2</a>
<a href="#" class="py-3 px-4 min-h-11">Link 3</a>
</nav>
<!-- Button group with safe spacing -->
<div class="flex flex-wrap gap-3">
<button class="min-h-11 px-4 py-2">Cancel</button>
<button class="min-h-11 px-4 py-2 bg-blue-600 text-white">Confirm</button>
</div>
Container queries enable component-level responsiveness , independent of viewport size.
@import "tailwindcss";
@plugin "@tailwindcss/container-queries";
| Class | Min-width |
|---|---|
@xs | 20rem (320px) |
@sm | 24rem (384px) |
@md | 28rem (448px) |
@lg | 32rem (512px) |
@xl | 36rem (576px) |
@2xl | 42rem (672px) |
@3xl |
<!-- Card that responds to its container, not viewport -->
<article class="@container">
<div class="
flex flex-col @sm:flex-row
gap-4 p-4
bg-white rounded-xl shadow-sm
">
<img
src="..."
class="
w-full @sm:w-32 @lg:w-48
aspect-video @sm:aspect-square
object-cover rounded-lg
"
/>
<div class="flex-1 min-w-0">
<h3 class="text-base @md:text-lg @lg:text-xl font-semibold truncate">
Card Title
</h3>
<p class="
text-sm @md:text-base
text-gray-600
line-clamp-2 @lg:line-clamp-3
mt-2
">
Description that adapts to available space...
</p>
<div class="mt-4 flex flex-wrap gap-2 @md:gap-3">
<span class="text-xs @md:text-sm px-2 py-1 bg-gray-100 rounded">Tag</span>
</div>
</div>
</div>
</article>
<div class="@container/sidebar w-64">
<nav class="
flex flex-col @lg/sidebar:flex-row
@lg/sidebar:items-center
gap-2 @lg/sidebar:gap-4
">
<a href="#">Link 1</a>
<a href="#">Link 2</a>
</nav>
</div>
<main class="@container/main flex-1">
<div class="
grid grid-cols-1
@md/main:grid-cols-2
@xl/main:grid-cols-3
gap-6
">
<!-- Grid items respond to main container -->
</div>
</main>
| Use Container Queries | Use Viewport Queries |
|---|---|
| Reusable components | Page-level layouts |
| Sidebar widgets | Navigation bars |
| Card grids | Hero sections |
| Embedded content | Full-width sections |
| CMS/dynamic content | Fixed app shells |
<!-- Auto-responsive grid with minimum card width -->
<div class="
grid gap-6
grid-cols-1
sm:grid-cols-2
lg:grid-cols-3
xl:grid-cols-4
">
<!-- Cards automatically flow -->
</div>
<!-- CSS Grid auto-fit (no breakpoints needed) -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-6">
<!-- Cards auto-fit with 280px minimum -->
</div>
<!-- CSS Grid auto-fill for fixed-size items -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
<!-- Fixed minimum size, grows to fill -->
</div>
<!-- Stack to row -->
<div class="flex flex-col md:flex-row gap-4">
<div class="flex-1">Content 1</div>
<div class="flex-1">Content 2</div>
</div>
<!-- Wrap with centered items -->
<div class="flex flex-wrap justify-center gap-4">
<div class="w-full sm:w-auto">Item 1</div>
<div class="w-full sm:w-auto">Item 2</div>
<div class="w-full sm:w-auto">Item 3</div>
</div>
<!-- Mobile stack, desktop inline with push -->
<div class="flex flex-col sm:flex-row sm:items-center gap-4">
<div class="flex-1">
<h3>Title</h3>
<p class="text-sm text-gray-600">Description</p>
</div>
<button class="sm:ml-auto min-h-11 px-4 py-2">Action</button>
</div>
<!-- Collapsible sidebar -->
<div class="flex min-h-screen">
<!-- Sidebar: hidden on mobile, visible on desktop -->
<aside class="
hidden lg:flex lg:flex-col
w-64 border-r bg-gray-50
">
<nav class="flex-1 p-4">...</nav>
</aside>
<!-- Main content -->
<main class="flex-1 p-4 lg:p-8">
Content
</main>
</div>
<!-- Mobile drawer + desktop sidebar -->
<div class="relative flex min-h-screen">
<!-- Mobile drawer overlay -->
<div class="
fixed inset-0 z-40 lg:hidden
bg-black/50
data-[open=false]:hidden
" data-open="false">
<aside class="w-64 h-full bg-white">
Mobile navigation
</aside>
</div>
<!-- Desktop sidebar -->
<aside class="hidden lg:block w-64 border-r">
Desktop navigation
</aside>
<main class="flex-1">Content</main>
</div>
<div class="min-h-screen grid grid-rows-[auto_1fr_auto]">
<!-- Header -->
<header class="sticky top-0 z-50 h-16 bg-white border-b shadow-sm">
<nav class="h-full max-w-7xl mx-auto px-4 flex items-center justify-between">
<Logo />
<ul class="hidden md:flex gap-6">...</ul>
<button class="md:hidden min-h-11 min-w-11">Menu</button>
</nav>
</header>
<!-- Main content area with optional sidebars -->
<div class="
grid
grid-cols-1
md:grid-cols-[240px_1fr]
lg:grid-cols-[240px_1fr_280px]
gap-0
">
<nav class="hidden md:block border-r p-4">Left Nav</nav>
<main class="p-4 md:p-6 lg:p-8">Main Content</main>
<aside class="hidden lg:block border-l p-4">Right Sidebar</aside>
</div>
<!-- Footer -->
<footer class="bg-gray-900 text-white py-8 md:py-12">
Footer content
</footer>
</div>
<!-- Maintain aspect ratio across all sizes -->
<div class="aspect-video overflow-hidden rounded-lg">
<img src="..." class="w-full h-full object-cover" loading="lazy" />
</div>
<!-- Responsive aspect ratio -->
<div class="aspect-square sm:aspect-video lg:aspect-[4/3] overflow-hidden">
<img src="..." class="w-full h-full object-cover" />
</div>
<!-- Art direction with picture element -->
<picture>
<source media="(min-width: 1024px)" srcset="large.jpg" />
<source media="(min-width: 640px)" srcset="medium.jpg" />
<img
src="small.jpg"
alt="Description"
class="w-full h-auto rounded-lg"
loading="lazy"
/>
</picture>
<!-- Responsive srcset -->
<img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w,
image-1600.jpg 1600w
"
sizes="
(min-width: 1280px) 1200px,
(min-width: 768px) 80vw,
100vw
"
alt="Responsive image"
class="w-full h-auto"
loading="lazy"
/>
<!-- Mobile-first heading scale -->
<h1 class="
text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl
font-bold leading-tight tracking-tight
">
Main Heading
</h1>
<h2 class="
text-xl sm:text-2xl md:text-3xl lg:text-4xl
font-semibold leading-snug
">
Section Heading
</h2>
<h3 class="
text-lg sm:text-xl md:text-2xl
font-medium
">
Subsection Heading
</h3>
<!-- Optimal line length and spacing -->
<article class="max-w-prose mx-auto">
<p class="
text-base md:text-lg
leading-relaxed md:leading-loose
text-gray-700 dark:text-gray-300
">
Body text optimized for readability with 45-75 characters per line.
</p>
</article>
<!-- Text that balances across lines -->
<h2 class="text-balance text-2xl md:text-3xl font-bold max-w-2xl">
This heading will balance text across lines to avoid orphans
</h2>
<nav class="relative">
<!-- Desktop navigation -->
<ul class="hidden md:flex items-center gap-6">
<li><a href="#" class="py-2 hover:text-blue-600">Home</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">Products</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">About</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">Contact</a></li>
</ul>
<!-- Mobile menu button -->
<button
class="md:hidden min-h-11 min-w-11 p-2"
aria-expanded="false"
aria-controls="mobile-menu"
aria-label="Toggle navigation menu"
>
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<!-- Mobile menu (controlled via JS) -->
<div
id="mobile-menu"
class="
md:hidden
absolute top-full left-0 right-0
bg-white shadow-lg border-t
hidden
"
>
<ul class="py-2">
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">Home</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">Products</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">About</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">Contact</a></li>
</ul>
</div>
</nav>
<!-- Fixed bottom navigation for mobile -->
<nav class="
fixed bottom-0 inset-x-0 z-50
md:hidden
bg-white border-t shadow-lg
safe-area-pb
">
<ul class="flex justify-around">
<li>
<a href="#" class="
flex flex-col items-center
min-h-14 min-w-14 px-3 py-2
text-xs
text-gray-600 hover:text-blue-600
aria-current:text-blue-600
">
<svg class="h-6 w-6 mb-1">...</svg>
Home
</a>
</li>
<li>
<a href="#" class="flex flex-col items-center min-h-14 min-w-14 px-3 py-2 text-xs">
<svg class="h-6 w-6 mb-1">...</svg>
Search
</a>
</li>
<li>
<a href="#" class="flex flex-col items-center min-h-14 min-w-14 px-3 py-2 text-xs">
<svg class="h-6 w-6 mb-1">...</svg>
Profile
</a>
</li>
</ul>
</nav>
<!-- Add padding to main content to prevent overlap -->
<main class="pb-20 md:pb-0">
Content
</main>
@utility safe-area-pt {
padding-top: env(safe-area-inset-top);
}
@utility safe-area-pb {
padding-bottom: env(safe-area-inset-bottom);
}
@utility safe-area-pl {
padding-left: env(safe-area-inset-left);
}
@utility safe-area-pr {
padding-right: env(safe-area-inset-right);
}
@utility safe-area-p {
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
<!-- Header that respects notch -->
<header class="sticky top-0 safe-area-pt bg-white">
<div class="h-16 flex items-center px-4">
Navigation
</div>
</header>
<!-- Bottom navigation with safe area -->
<nav class="fixed bottom-0 inset-x-0 safe-area-pb bg-white border-t">
Bottom nav
</nav>
<!-- Native lazy loading for images -->
<img src="image.jpg" alt="..." loading="lazy" class="w-full h-auto" />
<!-- Lazy load below-fold content -->
<div class="contents" data-lazy-load>
Heavy component loaded on demand
</div>
@utility content-auto {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
<!-- Skip rendering off-screen content -->
<section class="content-auto">
Large section that may be off-screen
</section>
<!-- Show simpler version on slow connections -->
<picture>
<source
srcset="video-poster.jpg"
media="(prefers-reduced-data: reduce)"
/>
<img src="animated.gif" alt="..." />
</picture>
Use Tailwind's aspect-* utilities to maintain correct video proportions across all screen sizes, preventing Cumulative Layout Shift (CLS):
<!-- Standard 16:9 video container -->
<div class="aspect-video overflow-hidden rounded-lg bg-black">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
</div>
<!-- Vertical video (9:16) for mobile-first social content -->
<div class="aspect-[9/16] overflow-hidden rounded-lg bg-black max-w-sm mx-auto">
<video class="w-full h-full object-cover" playsinline muted loop>
<source src="reel.mp4" type="video/mp4" />
</video>
</div>
<!-- Square video (1:1) common for social feeds -->
<div class="aspect-square overflow-hidden rounded-lg bg-black">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="square.mp4" type="video/mp4" />
</video>
</div>
<!-- Responsive aspect ratio - vertical on mobile, widescreen on desktop -->
<div class="aspect-[9/16] sm:aspect-video overflow-hidden rounded-lg bg-black">
<video class="w-full h-full object-contain" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
</div>
Videos without defined dimensions cause layout shift when they load. Use aspect ratio containers to reserve space:
<!-- CLS-safe video with poster and skeleton loader -->
<div class="relative aspect-video overflow-hidden rounded-lg bg-gray-900">
<!-- Skeleton placeholder while video loads -->
<div class="absolute inset-0 animate-pulse bg-gray-800" aria-hidden="true"></div>
<video
class="absolute inset-0 w-full h-full object-cover"
poster="/thumbnails/video-poster.jpg"
playsinline
preload="metadata"
>
<source src="video.mp4" type="video/mp4" />
</video>
</div>
<!-- Responsive grid of video thumbnails with zero CLS -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="aspect-video overflow-hidden rounded-lg bg-gray-900">
<video class="w-full h-full object-cover" poster="/thumb1.jpg" preload="none" playsinline></video>
</div>
<div class="aspect-video overflow-hidden rounded-lg bg-gray-900">
<video class="w-full h-full object-cover" poster="/thumb2.jpg" preload="none" playsinline></video>
</div>
<div class="aspect-video overflow-hidden rounded-lg bg-gray-900">
<video class="w-full h-full object-cover" poster="/thumb3.jpg" preload="none" playsinline></video>
</div>
</div>
<!-- Full-width on mobile, centered with max-width on desktop -->
<div class="w-full lg:max-w-4xl lg:mx-auto">
<div class="aspect-video overflow-hidden lg:rounded-xl bg-black">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="hero.mp4" type="video/mp4" />
</video>
</div>
</div>
<!-- Full-bleed hero video background -->
<section class="relative w-full h-screen overflow-hidden">
<video
class="absolute inset-0 w-full h-full object-cover"
autoplay muted loop playsinline
preload="auto"
>
<source src="hero-bg.mp4" type="video/mp4" />
</video>
<!-- Content overlay with safe padding -->
<div class="relative z-10 flex items-center justify-center h-full px-4 sm:px-8">
<div class="text-center text-white max-w-2xl">
<h1 class="text-3xl sm:text-4xl lg:text-6xl font-bold">Hero Title</h1>
<p class="mt-4 text-lg sm:text-xl">Subtitle text</p>
</div>
</div>
<!-- Dark overlay for text readability -->
<div class="absolute inset-0 bg-black/40" aria-hidden="true"></div>
</section>
Video player controls and UI elements need touch-friendly sizing and proper placement:
<!-- Custom video player with touch-safe controls -->
<div class="@container relative aspect-video overflow-hidden rounded-lg bg-black group">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
<!-- Play/Pause overlay - centered, touch-friendly -->
<button class="
absolute inset-0 flex items-center justify-center
bg-black/0 group-hover:bg-black/20 transition-colors
">
<span class="
w-16 h-16 sm:w-20 sm:h-20
flex items-center justify-center
rounded-full bg-white/90
shadow-lg
">
<svg class="w-8 h-8 sm:w-10 sm:h-10 text-gray-900 ml-1" viewBox="0 0 24 24" fill="currentColor">
<path d="M8 5v14l11-7z"/>
</svg>
</span>
</button>
<!-- Bottom controls bar - respects touch targets -->
<div class="
absolute bottom-0 inset-x-0
bg-gradient-to-t from-black/80 to-transparent
p-3 sm:p-4
flex items-center gap-3
opacity-0 group-hover:opacity-100 transition-opacity
">
<!-- Progress bar - tall enough for touch -->
<div class="flex-1 h-1 bg-white/30 rounded-full cursor-pointer">
<div class="h-full w-1/3 bg-white rounded-full relative">
<span class="absolute right-0 top-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full -mr-2"></span>
</div>
</div>
<!-- Time display -->
<span class="text-white text-xs sm:text-sm tabular-nums shrink-0">1:23 / 4:56</span>
<!-- Fullscreen button - touch-friendly minimum size -->
<button class="min-h-11 min-w-11 flex items-center justify-center text-white">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
</svg>
</button>
</div>
</div>
Video player components that adapt their layout based on container size --- useful when the same player appears in a sidebar, main content area, or modal:
<!-- Video player that adapts to its container -->
<div class="@container">
<div class="
flex flex-col @lg:flex-row
gap-4 @lg:gap-6
bg-gray-950 rounded-xl overflow-hidden
">
<!-- Video area -->
<div class="
w-full @lg:flex-1
aspect-video
overflow-hidden
">
<video class="w-full h-full object-cover" playsinline preload="metadata">
<source src="video.mp4" type="video/mp4" />
</video>
</div>
<!-- Info panel - below video on narrow, beside on wide -->
<div class="
p-4 @lg:p-6
@lg:w-80 @lg:overflow-y-auto
text-white
">
<h3 class="text-base @lg:text-lg font-semibold">Video Title</h3>
<p class="text-sm text-gray-400 mt-2 line-clamp-2 @lg:line-clamp-none">
Video description that shows more text when the container is wider...
</p>
<!-- Related videos - hidden in narrow containers -->
<div class="hidden @xl:block mt-6 space-y-3">
<h4 class="text-sm font-medium text-gray-300">Related</h4>
<div class="space-y-2">
<div class="flex gap-3">
<div class="w-28 aspect-video bg-gray-800 rounded shrink-0"></div>
<div class="text-xs text-gray-400">Related Video 1</div>
</div>
</div>
</div>
</div>
</div>
</div>
| Practice | Implementation |
|---|---|
| Mobile-first utilities | Unprefixed first, then sm:, md:, lg: |
| Touch targets | min-h-11 min-w-11 (44px minimum) |
| Fluid typography | clamp(min, preferred, max) with rem + vw |
| Fluid spacing | clamp() for padding and margins |
| Container queries |
Weekly Installs
977
Repository
GitHub Stars
21
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode895
gemini-cli861
codex844
github-copilot809
cursor756
amp721
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
Grimoire CLI 使用指南:区块链法术编写、验证与执行全流程
940 周安装
Grimoire Uniswap 技能:查询 Uniswap 元数据与生成代币/资金池快照的 CLI 工具
940 周安装
Grimoire Aave 技能:查询 Aave V3 元数据和储备快照的 CLI 工具
941 周安装
Railway CLI 部署指南:使用 railway up 命令快速部署代码到 Railway 平台
942 周安装
n8n Python 代码节点使用指南:在自动化工作流中编写 Python 脚本
943 周安装
Flutter Platform Views 实现指南:Android/iOS/macOS原生视图与Web嵌入教程
943 周安装
| 48rem (768px) |
@4xl | 56rem (896px) |
@5xl | 64rem (1024px) |
@container for component responsiveness |
| Safe areas | env(safe-area-inset-*) for notched devices |
| Readable text | max-w-prose (65ch) and leading-relaxed |
| Lazy loading | loading="lazy" on below-fold images |
| Touch spacing | gap-3 (12px) minimum between targets |
| Viewport meta | width=device-width, initial-scale=1 |