Основные концепции
Построение сложных компонентов из ограниченного набора примитивных утилит.
Вы стилизуете элементы в Tailwind, комбинируя множество однозадачных презентационных классов (утилитарных классов) прямо в вашей разметке:
У вас новое сообщение!
<div class="mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg outline outline-black/5 dark:bg-slate-800 dark:shadow-none dark:-outline-offset-1 dark:outline-white/10"> <img class="size-12 shrink-0" src="/img/logo.svg" alt="ChitChat Logo" /> <div> <div class="text-xl font-medium text-black dark:text-white">ChitChat</div> <p class="text-gray-500 dark:text-gray-400">У вас новое сообщение!</p> </div></div>
Например, в приведённом выше UI мы использовали:
flex
, shrink-0
, и p-6
) для управления общим расположениемmax-w-sm
и mx-auto
) для ограничения ширины карточки и центрирования по горизонталиbg-white
, rounded-xl
и shadow-lg
) для стилизации внешнего вида карточкиsize-12
) для задания ширины и высоты логотипаgap-x-4
) для управления расстоянием между логотипом и текстомtext-xl
, text-black
, font-medium
и др.) для стилизации текста карточкиСтилизовать элементы таким образом противоречит многим традиционным best practices, но как только вы попробуете — быстро заметите важные преимущества:
Эти преимущества заметны даже на маленьких проектах, но особенно ценны для команд, работающих над большими долгоживущими продуктами.
Частая реакция на такой подход — удивление: "разве это не просто инлайн-стили?" И в каком-то смысле так и есть — вы применяете стили напрямую к элементам вместо того, чтобы присвоить им имя класса и затем стилизовать этот класс.
Но использование утилитарных классов имеет много важных преимуществ перед инлайн-стилями, например:
Этот компонент полностью адаптивен и включает кнопку с hover и active стилями, построенную полностью из утилитарных классов:
Наведите на эту кнопку, чтобы увидеть изменение цвета фона
Erin Lindford
Product Engineer
<div class="flex flex-col gap-2 p-8 sm:flex-row sm:items-center sm:gap-6 sm:py-4 ..."> <img class="mx-auto block h-24 rounded-full sm:mx-0 sm:shrink-0" src="/img/erin-lindford.jpg" alt="" /> <div class="space-y-2 text-center sm:text-left"> <div class="space-y-0.5"> <p class="text-lg font-semibold text-black">Erin Lindford</p> <p class="font-medium text-gray-500">Product Engineer</p> </div> <button class="border-purple-200 text-purple-600 hover:border-transparent hover:bg-purple-600 hover:text-white active:bg-purple-700 ..."> Message </button> </div></div>
Чтобы стилизовать элемент в состояниях типа hover или focus, добавьте к любой утилите префикс состояния, на которое хотите нацелиться, например hover:bg-sky-700
:
Наведите на эту кнопку, чтобы увидеть изменение цвета фона
<button class="bg-sky-500 hover:bg-sky-700 ...">Сохранить изменения</button>
Эти префиксы называются вариантами в Tailwind, и они применяют стили из утилитарного класса только когда условие для этого варианта выполняется.
Вот как выглядит сгенерированный CSS для класса hover:bg-sky-700
:
.hover\:bg-sky-700 { &:hover { background-color: var(--color-sky-700); }}
Обратите внимание, что этот класс ничего не делает если только элемент не находится в состоянии hover? Его единственная задача — предоставить стили для hover — ничего больше.
Это отличается от того, как вы писали бы традиционный CSS, где один класс обычно предоставлял бы стили для многих состояний:
<button class="btn">Сохранить изменения</button><style> .btn { background-color: var(--color-sky-500); &:hover { background-color: var(--color-sky-700); } }</style>
Вы даже можете комбинировать варианты в Tailwind, чтобы применять утилиту, когда выполняются несколько условий, например, комбинируя hover:
и disabled:
:
<button class="bg-sky-500 disabled:hover:bg-sky-500 ...">Сохранить изменения</button>
Узнайте больше в документации по стилизации элементов в состояниях hover, focus и других.
Так же, как состояния hover и focus, вы можете стилизовать элементы на разных контрольных точках, добавляя к любой утилите префикс контрольной точки(брейкпоинта), на котором хотите применить этот стиль:
Измените размер этого примера, чтобы увидеть изменение макета
<div class="grid grid-cols-2 sm:grid-cols-3"> <!-- ... --></div>
В примере выше префикс sm:
гарантирует, что grid-cols-3
срабатывает только на контрольной точке sm
и выше, что составляет 40rem по умолчанию:
.sm\:grid-cols-3 { @media (width >= 40rem) { grid-template-columns: repeat(3, minmax(0, 1fr)); }}
Узнайте больше в документации по адаптивному дизайну.
Стилизовать элемент в тёмном режиме — это просто вопрос добавления префикса dark:
к любой утилите, которую вы хотите применить, когда активен тёмный режим:
Светлый режим
Пишет вверх ногами
Ручка Zero Gravity может использоваться для письма в любой ориентации, включая вверх ногами. Она даже работает в космосе.
Тёмный режим
Пишет вверх ногами
Ручка Zero Gravity может использоваться для письма в любой ориентации, включая вверх ногами. Она даже работает в космосе.
<div class="bg-white dark:bg-gray-800 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5"> <div> <span class="inline-flex items-center justify-center rounded-md bg-indigo-500 p-2 shadow-lg"> <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true" > <!-- ... --> </svg> </span> </div> <h3 class="text-gray-900 dark:text-white mt-5 text-base font-medium tracking-tight ">Пишет вверх ногами</h3> <p class="text-gray-500 dark:text-gray-400 mt-2 text-sm "> Ручка Zero Gravity может использоваться для письма в любой ориентации, включая вверх ногами. Она даже работает в космосе. </p></div>
Так же, как с состояниями hover или медиа-запросами, важно понимать, что один утилитарный класс никогда не будет включать оба светлых и тёмных стиля — вы стилизуете элементы в тёмном режиме, используя несколько классов, один для светлых стилей и другой для тёмных стилей.
.dark\:bg-gray-800 { @media (prefers-color-scheme: dark) { background-color: var(--color-gray-800); }}
Узнайте больше в документации по тёмному режиму.
Много раз с Tailwind вы будете даже использовать несколько классов для создания значения для одного CSS свойства, например добавляя несколько фильтров к элементу:
<div class="blur-sm grayscale"> <!-- ... --></div>
Оба этих эффекта основаны на свойстве filter
в CSS, поэтому Tailwind использует CSS-переменные, чтобы сделать возможным композицию этих эффектов вместе:
.blur-sm { --tw-blur: blur(var(--blur-sm)); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}.grayscale { --tw-grayscale: grayscale(100%); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}
Сгенерированный CSS выше немного упрощён, но хитрость здесь в том, что каждая утилита устанавливает CSS-переменную только для того эффекта, который она должна применить. Затем свойство filter
смотрит на все эти переменные, возвращаясь к пустому значению, если переменная не была установлена.
Tailwind использует этот же подход для градиентов, цветов теней, трансформаций и многого другого.
Много утилит в Tailwind являются темами переменных, такими как bg-blue-500
, text-xl
, и shadow-md
, которые сопоставляются с вашим базовым палитрой, шкалой типов, и тенями.
Когда вам нужно использовать одноразовое значение вне вашей темы, используйте специальную квадратную скобочную нотацию для указания произвольных значений:
<button class="bg-[#316ff6] ..."> Sign in with Facebook</button>
Это может быть полезно для одноразовых цветов вне вашей палитры (как выше для Facebook blue), но также, когда вам нужно сложное значение, например сложный сетка:
<div class="grid grid-cols-[24rem_2.5rem_minmax(0,1fr)]"> <!-- ... --></div>
Это также полезно, когда вам нужно использовать CSS функции, даже если вы используете свои значения темы:
<div class="max-h-[calc(100dvh-(--spacing(6)))]"> <!-- ... --></div>
Есть даже синтаксис для генерации полностью произвольного CSS, включая произвольное имя свойства, что может быть полезно для установки CSS переменных:
<div class="[--gutter-width:1rem] lg:[--gutter-width:2rem]"> <!-- ... --></div>
Узнайте больше в документации по использованию произвольных значений.
Tailwind CSS не один большой статический лист стилей, как вы могли бы привыкнуть с другими CSS фреймворками — он генерирует необходимый CSS на основе классов, которые вы фактически используете при компиляции CSS.
Это делается сканированием всех файлов в вашем проекте, чтобы найти любой символ, который может выглядеть как имя класса:
export default function Button({ size, children }) { let sizeClasses = { md: "px-4 py-2 rounded-md text-base", lg: "px-5 py-3 rounded-lg text-lg", }[size]; return ( <button type="button" className={`font-bold ${sizeClasses}`}> {children} </button> );}
После того, как он найдет все потенциальные классы, Tailwind генерирует CSS для каждого и компилирует все в один лист стилей, содержащий только необходимые стили.
Так как CSS генерируется на основе имени класса, Tailwind может распознавать классы, используя произвольные значения, такие как bg-[#316ff6]
, и генерировать необходимый CSS, даже если значение не является частью вашей темы.
Узнайте больше о том, как это работает в обнаружении классов в исходных файлах.
Иногда вам нужно стилизовать элемент под комбинацией условий, например в тёмном режиме, на конкретном брейкпоинте, когда элемент находится в состоянии hover, и когда элемент имеет конкретный атрибут данных.
Вот пример того, как это выглядит с Tailwind:
<button class="dark:lg:data-current:hover:bg-indigo-600 ..."> <!-- ... --></button>
@media (prefers-color-scheme: dark) and (width >= 64rem) { button[data-current]:hover { background-color: var(--color-indigo-600); }}
Tailwind также поддерживает вещи, такие как group-hover
, которые позволяют вам стилизовать элемент, когда конкретный родитель находится в состоянии hover:
<a href="#" class="group rounded-lg p-8"> <!-- ... --> <span class="group-hover:underline">Read more…</span></a>
@media (hover: hover) { a:hover span { text-decoration-line: underline; }}
Этот group-*
синтаксис работает с другими вариантами тоже, как group-focus
, group-active
, и многими другими.
Для действительно сложных сценариев (особенно при стилизации HTML, который вы не контролируете), Tailwind поддерживает произвольные варианты, которые позволяют вам написать любой селектор, который вы хотите, прямо в имени класса:
<div class="[&>[data-active]+span]:text-blue-600 ..."> <span data-active><!-- ... --></span> <span>This text will be blue</span></div>
div > [data-active] + span { color: var(--color-blue-600);}
Инлайн-стили все еще очень полезны в Tailwind CSS проектах, особенно когда значение приходит из динамического источника, такого как база данных или API:
export function BrandedButton({ buttonColor, textColor, children }) { return ( <button style={{ backgroundColor: buttonColor, color: textColor, }} className="rounded-md px-3 py-1.5 font-medium" > {children} </button> );}
Вы также можете использовать инлайн-стиль для очень сложных произвольных значений, которые трудно прочитать, когда они форматируются как имя класса:
<div class="grid-[2fr_max(0,var(--gutter-width))_calc(var(--gutter-width)+10px)]"><div style="grid-template-columns: 2fr max(0, var(--gutter-width)) calc(var(--gutter-width) + 10px)"> <!-- ... --></div>
Другой полезный шаблон — установка CSS переменных на основе динамических источников с помощью инлайн-стилей, а затем ссылаться на эти переменные с помощью утилитарных классов:
export function BrandedButton({ buttonColor, buttonColorHover, textColor, children }) { return ( <button style={{ "--bg-color": buttonColor, "--bg-color-hover": buttonColorHover, "--text-color": textColor, }} className="bg-(--bg-color) text-(--text-color) hover:bg-(--bg-color-hover) ..." > {children} </button> );}
Когда вы создаете целые проекты только с утилитарными классами, вы, несомненно, столкнетесь с повторением определенных шаблонов, чтобы повторить один и тот же дизайн в разных местах.
Например, вот повторяющиеся классы для каждого изображения аватара:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</h4> <span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span> </div> <div class="mt-3 flex -space-x-2 overflow-hidden"> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> </div> <div class="mt-3 text-sm font-medium"> <a href="#" class="text-blue-500">+ 198 others</a> </div></div>
Не паникуйте! На практике это не та проблема, которой вы могли бы беспокоиться, и стратегии для её решения — это то, что вы уже делаете каждый день.
Часто дизайн-элемент, который появляется более одного раза на отрендеренной странице, на самом деле создаётся только один раз, потому что фактическая разметка рендерится в цикле.
Например, дублирующиеся аватары в начале этого руководства почти наверняка были бы отрендерены в цикле в реальном проекте:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Contributors</h4> <span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span> </div> <div class="mt-3 flex -space-x-2 overflow-hidden"> {#each contributors as user} <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src={user.avatarUrl} alt={user.handle} /> {/each} </div> <div class="mt-3 text-sm font-medium"> <a href="#" class="text-blue-500">+ 198 others</a> </div></div>
Когда элементы создаются в цикле, фактический список классов создается только один раз, поэтому нет реальной проблемы с дублированием.
Когда дублирование локализовано в группе элементов в одном файле, самый простой способ справиться с этим — использовать мультикурсор редактирования для быстрого выбора и редактирования списка классов для каждого элемента одновременно:
<nav class="flex justify-center space-x-4"> <a href="/dashboard" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Home </a> <a href="/team" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Team </a> <a href="/projects" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Projects </a> <a href="/reports" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Reports </a></nav>
Вы удивитесь, как часто это оказывается лучшим решением. Если вы можете быстро редактировать все дублирующиеся списки классов одновременно, нет смысла вводить дополнительные абстракции.
Если вам нужно повторно использовать некоторые стили в нескольких файлах, лучшая стратегия — создать компонент, если вы используете фронтенд-фреймворк, такой как React, Svelte или Vue, или шаблонную часть, если вы используете шаблонный язык, такой как Blade, ERB, Twig или Nunjucks.
export function VacationCard({ img, imgAlt, eyebrow, title, pricing, url }) { return ( <div> <img className="rounded-lg" src={img} alt={imgAlt} /> <div className="mt-4"> <div className="text-xs font-bold text-sky-500">{eyebrow}</div> <div className="mt-1 font-bold text-gray-700"> <a href={url} className="hover:underline"> {title} </a> </div> <div className="mt-2 text-sm text-gray-600">{pricing}</div> </div> </div> );}
Теперь вы можете использовать этот компонент в стольких местах, сколько захотите, при этом сохраняя единый источник истины для стилей, чтобы их можно было легко обновлять вместе в одном месте.
Если вы используете шаблонный язык, такой как ERB или Twig, вместо чего-то вроде React или Vue, создание шаблонной части для чего-то такого маленького, как кнопка, может показаться избыточным по сравнению с простым CSS классом, таким как btn
.
Хотя настоятельно рекомендуется создавать правильные шаблонные части для более сложных компонентов, написание некоторого пользовательского CSS вполне допустимо, когда шаблонная часть кажется слишком громоздкой.
Вот как может выглядеть класс btn-primary
, используя переменные темы для сохранения согласованности дизайна:
<button class="btn-primary">Сохранить изменения</button>
@import "tailwindcss";@layer components { .btn-primary { border-radius: calc(infinity * 1px); background-color: var(--color-violet-500); padding-inline: --spacing(5); padding-block: --spacing(2); font-weight: var(--font-weight-semibold); color: var(--color-white); box-shadow: var(--shadow-md); &:hover { @media (hover: hover) { background-color: var(--color-violet-700); } } }}
Однако, для чего-то более сложного, чем просто HTML элемент, мы настоятельно рекомендуем использовать шаблонные части, чтобы стили и структура могли быть заключены в одном месте.
Когда вы добавляете два класса, которые нацелены на одно CSS свойство, класс, который появляется последним в стилях, выигрывает. Так в этом примере элемент получит display: grid
даже если flex
приходит последним в фактическом class
атрибуте:
<div class="grid flex"> <!-- ... --></div>
.flex { display: flex;}.grid { display: grid;}
В целом вы должны просто никогда не добавлять два конфликтующих класса к одному элементу — всегда добавляйте только один, который вы действительно хотите применить:
export function Example({ gridLayout }) { return <div className={gridLayout ? "grid" : "flex"}>{/* ... */}</div>;}
Используя компонентно-ориентированные библиотеки, такие как React или Vue, это часто означает предоставление конкретных пропов для стилизации пользовательских настроек вместо того, чтобы позволять потребляющим добавлять дополнительные классы извне компонента, так как эти стили часто конфликтовать.
Когда вам действительно нужно принудительно применить конкретный утилитарный класс и не иметь другого способа управления специфичностью, вы можете добавить !
в конец имени класса, чтобы сделать все объявления !important
:
<div class="bg-teal-500 bg-red-500!"> <!-- ... --></div>
.bg-red-500\! { background-color: var(--color-red-500) !important;}.bg-teal-500 { background-color: var(--color-teal-500);}
Если вы добавляете Tailwind в проект, который имеет существующий сложный CSS с высокой специфичностью правилами, вы можете использовать important
флаг при импорте Tailwind для отмечания всех утилит как !important
:
@import "tailwindcss" important;
@layer utilities { .flex { display: flex !important; } .gap-4 { gap: 1rem !important; } .underline { text-decoration-line: underline !important; }}
Если у вашего проекта есть имена классов, которые конфликтуют с утилитами Tailwind CSS, вы можете префикс все утилитарные классы и CSS переменные, используя prefix
параметр:
@import "tailwindcss" prefix(tw);
@layer theme { :root { --tw-color-red-500: oklch(0.637 0.237 25.331); }}@layer utilities { .tw\:text-red-500 { color: var(--tw-color-red-500); }}