跳到主要内容

tailwindcss

基础

环境配置

安装
pnpm add --save-dev tailwindcss postcss autoprefixer
pnpm dlx tailwindcss init -p
配置文件
  • 根目录创建 tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
css 文件
  • 创建 ./src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

插件

  • vscode plugin;
  • prettier-plugin-tailwindcss;

状态管理

支持状态管理

  • 伪类;
  • 微元素;
  • 媒体查询;
  • 选择器;

伪类

hover/focus/active
<button class="bg-violet-500 hover:bg-violet-600 active:bg-violet-700 focus:outline-none focus:ring focus:ring-violet-300 ...">
Save changes
</button>
first/last/odd/even
<ul role="list" class="p-6 divide-y divide-slate-200">
{#each people as person}
<!-- Remove top/bottom padding when first/last child -->
<li class="flex py-4 first:pt-0 last:pb-0">
<img class="h-10 w-10 rounded-full" src="{person.imageUrl}" alt="" />
<div class="ml-3 overflow-hidden">
<p class="text-sm font-medium text-slate-900">{person.name}</p>
<p class="text-sm text-slate-500 truncate">{person.email}</p>
</div>
</li>
{/each}
</ul>

表单状态

  • required/invalid/disabled;
<form>
<label class="block">
<span class="block text-sm font-medium text-slate-700">Username</span>
<!-- Using form state modifiers, the classes can be identical for every input -->
<input type="text" value="tbone" disabled class="mt-1 block w-full px-3 py-2 bg-white border border-slate-300 rounded-md text-sm shadow-sm placeholder-slate-400
focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500
disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none
invalid:border-pink-500 invalid:text-pink-600
focus:invalid:border-pink-500 focus:invalid:ring-pink-500
"/>
</label>
<!-- ... -->
</form>

父元素状态

基础
  • 根据父元素的状态设置自身样式;
  • 使用 group 标记父元素;
  • 使用 group-* 设置状态;
<a href="#" class="group block max-w-xs mx-auto rounded-lg p-6 bg-white ring-1 ring-slate-900/5 shadow-lg space-y-3 hover:bg-sky-500 hover:ring-sky-500">
<div class="flex items-center space-x-3">
<svg class="h-6 w-6 stroke-sky-500 group-hover:stroke-white" fill="none" viewBox="0 0 24 24"><!-- ... --></svg>
<h3 class="text-slate-900 group-hover:text-white text-sm font-semibold">New project</h3>
</div>
<p class="text-slate-500 group-hover:text-white text-sm">Create a new project from a variety of starting templates.</p>
</a>
嵌套父元素
  • 使用 group/{name} 标记父元素;
  • 使用 group-*/{name} 设置状态;
<ul role="list">
{#each people as person}
<li class="group/item hover:bg-slate-100 ...">
<img src="{person.imageUrl}" alt="" />
<div>
<a href="{person.url}">{person.name}</a>
<p>{person.title}</p>
</div>
<a class="group/edit invisible hover:bg-slate-200 group-hover/item:visible ..." href="tel:{person.phone}">
<span class="group-hover/edit:text-gray-700 ...">Call</span>
<svg class="group-hover/edit:translate-x-0.5 group-hover/edit:text-slate-500 ...">
<!-- ... -->
</svg>
</a>
</li>
{/each}
</ul>
选择器
  • 使用 group-[*] 操作符表示 * 选择器对应标签的状态;
<div class="group is-published">
<div class="hidden group-[.is-published]:block">Published</div>
</div>

同级元素状态

  • 父元素基础上将 group 替换为 peer;
<form>
<label for="email">Email:</label>
<input id="email" name="email" type="email" class="is-dirty peer" required />
<div class="peer-[.is-dirty]:peer-required:block hidden">This field is required.</div>
<!-- ... -->
</form>

后代元素状态

  • 父元素基础上将 group 替换为 has;
<label class="has-[:checked]:bg-indigo-50 has-[:checked]:text-indigo-900 has-[:checked]:ring-indigo-200 ..">
<svg fill="currentColor">
<!-- ... -->
</svg>
Google Pay
<input type="radio" class="checked:border-indigo-500 ..." />
</label>

设置直接子元素

  • 设置直接子元素样式;
  • 父元素基础上将 group 替换为 *;
<div>
<h2>Categories<h2>
<ul class="*:rounded-full *:border *:border-sky-100 *:bg-sky-50 *:px-2 *:py-0.5 dark:text-sky-300 dark:*:border-sky-500/15 dark:*:bg-sky-500/10 ...">
<li>Sales</li>
<li>Marketing</li>
<li>SEO</li>
<!-- ... -->
</ul>
</div>

基于父元素/同级元素的后代

  • 使用 group-has-*peer-has-* 操作符;

伪元素

before 和 after
  • 使用 beforeafter 操作符;
<label class="block">
<span class="after:content-['*'] after:ml-0.5 after:text-red-500 block text-sm font-medium text-slate-700">
Email
</span>
<input
type="email"
name="email"
class="mt-1 px-3 py-2 bg-white border shadow-sm border-slate-300 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:ring-sky-500 block w-full rounded-md sm:text-sm focus:ring-1"
placeholder="you@example.com"
/>
</label>
Placeholder text
  • input 中的 placeholder;
  • 使用 placeholder 操作符;
<label class="relative block">
<span class="sr-only">Search</span>
<span class="absolute inset-y-0 left-0 flex items-center pl-2">
<svg class="h-5 w-5 fill-slate-300" viewBox="0 0 20 20"><!-- ... --></svg>
</span>
<input class="placeholder:italic placeholder:text-slate-400 block bg-white w-full border border-slate-300 rounded-md py-2 pl-9 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1 sm:text-sm" placeholder="Search for anything..." type="text" name="search"/>
</label>
File input buttons
  • input 中的 file;
  • 使用 file 操作符;
<form class="flex items-center space-x-6">
<div class="shrink-0">
<img
class="h-16 w-16 object-cover rounded-full"
src="https://images.unsplash.com/photo-1580489944761-15a19d654956?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1361&q=80"
alt="Current profile photo"
/>
</div>
<label class="block">
<span class="sr-only">Choose profile photo</span>
<input
type="file"
class="block w-full text-sm text-slate-500
file:mr-4 file:py-2 file:px-4
file:rounded-full file:border-0
file:text-sm file:font-semibold
file:bg-violet-50 file:text-violet-700
hover:file:bg-violet-100
"
/>
</label>
</form>
List markers
  • list 中的 maker;
  • 使用 marker 操作符;
<ul
role="list"
class="marker:text-sky-400 list-disc pl-5 space-y-3 text-slate-500"
>
<li>5 cups chopped Porcini mushrooms</li>
<li>1/2 cup of olive oil</li>
<li>3lb of celery</li>
</ul>
Highlighted text
  • 选中文字;
  • 使用 selection 操作符;
<div class="selection:bg-fuchsia-300 selection:text-fuchsia-900">
<p>
So I started to walk into the water. I won't lie to you boys, I was
terrified. But I pressed on, and as I made my way past the breakers a
strange calm came over me. I don't know if it was divine intervention or the
kinship of all living things but I tell you Jerry at that moment, I{" "}
<em>was</em> a marine biologist.
</p>
</div>
First-line and first-letter
  • 第一行/第一个字母;
  • 使用 first-line/first-letter 操作符;
<p
class="first-line:uppercase first-line:tracking-widest
first-letter:text-7xl first-letter:font-bold first-letter:text-slate-900
first-letter:mr-3 first-letter:float-left
"
>
Well, let me tell you something, funny boy. Y'know that little stamp, the one
that says "New York Public Library"? Well that may not mean anything to you,
but that means a lot to me. One whole hell of a lot.
</p>

媒体查询

响应式

  • [[#响应式设计]];

主题

  • [[#主题切换]];

Viewport orientation

  • 设备视口方向;
  • 使用 portraitlandscape 操作符;
<div>
<div class="portrait:hidden">
<!-- ... -->
</div>
<div class="landscape:hidden">
<p>
This experience is designed to be viewed in landscape. Please rotate your
device to view the site.
</p>
</div>
</div>
  • 打印文档时样式;
  • 使用 print 操作符;
<div>
<article class="print:hidden">
<h1>My Secret Pizza Recipe</h1>
<p>This recipe is a secret, and must not be shared with anyone</p>
<!-- ... -->
</article>
<div class="hidden print:block">
Are you seriously trying to print this? It's secret!
</div>
</div>

Supports rules

  • 浏览器是否支持特定功能;
  • 使用 supports-[...] 操作符;
<div class="flex supports-[display:grid]:grid ...">
<!-- ... -->
</div>

属性选择器

ARIA 属性
  • 根据 ARIA 属性设置样式;
ModifierCSS
aria-checked&[aria-checked="true"]
aria-disabled&[aria-disabled="true"]
aria-expanded&[aria-expanded="true"]
aria-hidden&[aria-hidden="true"]
aria-pressed&[aria-pressed="true"]
aria-readonly&[aria-readonly="true"]
aria-required&[aria-required="true"]
aria-selected&[aria-selected="true"]
Data 属性
  • 根据 data 属性设置样式;
  • 使用 data-* 修饰符;
<!-- Will apply -->
<div data-size="large" class="data-[size=large]:p-8">
<!-- ... -->
</div>

<!-- Will not apply -->
<div data-size="medium" class="data-[size=large]:p-8">
<!-- ... -->
</div>
RTL
  • 根据文字方向设置样式;
  • 使用 rtlltr 操作符;
<div class="group flex items-center">
<img class="shrink-0 h-12 w-12 rounded-full" src="..." alt="" />
<div class="ltr:ml-3 rtl:mr-3">
<p class="text-sm font-medium text-slate-700 group-hover:text-slate-900">
...
</p>
<p class="text-sm font-medium text-slate-500 group-hover:text-slate-700">
...
</p>
</div>
</div>
Open/closed state
  • 作用于 details> 或 <dialog 标签,表示其 open/close 状态;
  • 使用 open 操作符表示打开状态;
<div class="max-w-lg mx-auto p-8">
<details
class="open:bg-white dark:open:bg-slate-900 open:ring-1 open:ring-black/5 dark:open:ring-white/10 open:shadow-lg p-6 rounded-lg"
open
>
<summary class="text-sm leading-6 text-slate-900 dark:text-white font-semibold select-none">
Why do they call it Ovaltine?
</summary>
<div class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">
<p>The mug is round. The jar is round. They should call it Roundtine.</p>
</div>
</details>
</div>

自定义选择器

自定义选择器
  • & 表示 class 对应标签;
  • 使用 [] 包裹;
<ul role="list">
{#each items as item}
<li class="[&:nth-child(3)]:underline">{item}</li>
{/each}
</ul>
选择器堆叠
  • 选择器可任意堆叠;
<ul role="list">
{#each items as item}
<li class="lg:[&:nth-child(3)]:hover:underline">{item}</li>
{/each}
</ul>

操作符列表

设计系统

响应式设计

断点
  • 使用 sm/md/lg/xl/2xl 操作符;
Breakpoint prefixMinimum widthCSS
sm640px@media (min-width: 640px) { ... }
md768px@media (min-width: 768px) { ... }
lg1024px@media (min-width: 1024px) { ... }
xl1280px@media (min-width: 1280px) { ... }
2xl1536px@media (min-width: 1536px) { ... }
<div class="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
<!-- ... -->
</div>
移动优先
  • 默认使用移动布局设计;
  • 单独设置 sm/md/lg。。。;
<div class="text-center sm:text-left"></div>escript
断点范围
  • 使用 max-* 修饰符;
ModifierMedia query
max-sm@media not all and (min-width: 640px) { ... }
max-md@media not all and (min-width: 768px) { ... }
max-lg@media not all and (min-width: 1024px) { ... }
max-xl@media not all and (min-width: 1280px) { ... }
max-2xl@media not all and (min-width: 1536px) { ... }
<div class="md:max-lg:flex">
<!-- ... -->
</div>

主题切换

默认机制
  • 默认使用系统偏好;
dark 属性
  • 默认为浅色模式;
  • 使用 dark 操作符表示深色模式;
<div class="bg-white dark:bg-slate-900 rounded-lg px-6 py-8 ring-1 ring-slate-900/5 shadow-xl">
<h3 class="text-slate-900 dark:text-white mt-5 text-base font-medium tracking-tight">
Writes Upside-Down
</h3>
<p class="text-slate-500 dark:text-slate-400 mt-2 text-sm">
The Zero Gravity Pen can be used to write in any orientation, including
upside-down. It even works in outer space.
</p>
</div>
手动切换
  • 使用 class 策略;
  • 设置 dark 修饰符;
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
// ...
};
<!-- Dark mode not enabled -->
<html>
<body>
<!-- Will be white -->
<div class="bg-white dark:bg-black">
<!-- ... -->
</div>
</body>
</html>

<!-- Dark mode enabled -->
<html class="dark">
<body>
<!-- Will be black -->
<div class="bg-white dark:bg-black">
<!-- ... -->
</div>
</body>
</html>
系统偏好
  • 使用 Window.matchMedia() API 和 prefers-color-scheme 选择器;
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}

自定义样式

自定义主题
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
screens: {
sm: "480px",
md: "768px",
lg: "976px",
xl: "1440px",
},
colors: {
blue: "#1fb6ff",
pink: "#ff49db",
orange: "#ff7849",
green: "#13ce66",
"gray-dark": "#273444",
gray: "#8492a6",
"gray-light": "#d3dce6",
},
fontFamily: {
sans: ["Graphik", "sans-serif"],
serif: ["Merriweather", "serif"],
},
},
};
任意属性值
  • 使用 [] 包裹对应 css 属性值;
<div class="top-[117px]">
<!-- ... -->
</div>
任意 css 属性
  • 使用 [] 包裹对应 css 属性;
<div class="[mask-type:luminance] hover:[mask-type:alpha]">
<!-- ... -->
</div>
处理空白
  • 使用 _ 表示空白;
  • 用于任意 css 简写属性;
<div class="grid grid-cols-[1fr_500px_2fr]">
<!-- ... -->
</div>
基础样式
  • index.css 中设置;
  • 使用 @layer base 操作符;
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
/* ... */
}
自定义原子类
  • tailwindcss 不包含的原子 css 属性;
  • 使用 @layer utilities 操作符;
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer utilities {
.content-auto {
content-visibility: auto;
}
}
<div class="lg:dark:content-auto">
<!-- ... -->
</div>

操作符和函数

操作符

  • @tailwind:tailwindcss 默认规则;
  • @layer:用户自定义规则;
  • @apply:内联自定义 css;
  • @config:加载配置文件;
@config "./tailwind.site.config.js";

@tailwind base;
@tailwind components;
@tailwind utilities;

函数

theme()
  • 访问自定义主题配置;
.content-area {
height: calc(100vh - theme(spacing.12));
}
screen()
  • 通过名称引用断点媒体查询;
@media screen(sm) {
/* ... */
}