Modifier or Variant - condition added to the beginning of the class/utility name to apply it: hover:bg-white , xl:flex
// 1 --- install
npm install -D tailwindcss // in CreateReactApp
npm install -D tailwindcss postcss autoprefixer // in Next.JS, Vite
// 2 --- create tailwind.config.js file
npx tailwindcss init // in CreateReactApp
npx tailwindcss init -p // in Next.JS, Vite
// 3 --- add the paths to all of template files in tailwind.config.js file:
module.exports = {
content: [
// --- in CreateReactApp:
"./src/**/*.{js,jsx,ts,tsx}",
// --- in Vite:
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
// --- in Next.JS:
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
// or if using "src" directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
}
// 4 --- add the Tailwind directives to CSS file (./src/index.css or globals.css):
@tailwind base;
@tailwind components;
@tailwind utilities;
// 5 --- start project build process:
npm run start // in CreateReactApp
npm run dev // in Next.JS
// 6 --- use Tailwind in your project:
export default function App() {
return (
<h1 className="text-3xl font-bold underline">
Hello world!
</h1>
)
}
Pseudo-class
// --- HOVER - ( &:hover )
<div class="bg-black hover:bg-white"> ... </div>
// --- FOCUS - ( &:focus )
<input class="border-gray-300 focus:border-blue-400" />
// --- FOCUS-WITHIN - element or one of its descendants has focus, ( &:focus-within )
<div class="focus-within:shadow-lg"> <input type="text" /> </div>
// --- FOCUS-VISIBLE - element has been focused using the keyboard, ( &:focus-visible )
<button class="focus:outline-none focus-visible:ring"> Submit </button>
// --- ACTIVE - element is being pressed, ( &:active )
<button class="bg-blue-500 active:bg-blue-600"> Submit </button>
// --- EMPTY - element has no content, ( &:empty )
<ul> {arr.map((data) => (<li class="empty:hidden"> {data.value} </li>))} </ul>
// --- DISABLED - ( &:disabled )
<input class="disabled:opacity-75" />
// --- ENABLED - ( &:enabled )
<input class="enabled:hover:border-gray-400 disabled:opacity-75" />
// --- CHECKED - ( &:checked )
<input type="checkbox" class="appearance-none checked:bg-blue-500" />
// --- INDETERMINATE - indeterminate state ( &:indeterminate )
<input type="checkbox" class="appearance-none indeterminate:bg-gray-300" />
// --- DEFAULT - option, checkbox or radio button default value ( &:default )
<input type="checkbox" class="default:ring-2" />
// --- REQUIRED - required input( &:required )
<input class="required:border-red-500" />
// --- VALID - ( &:valid )
<input class="valid:border-green-500" />
// --- INVALID - ( &:invalid )
<input class="invalid:border-red-500" />
// --- IN-RANGE - input value is within a specified range limit ( &:in-range )
<input min="1" max="5" class="in-range:border-green-500" />
// --- OUT-OF-RANGE - input value is outside of a specified range limit ( &:out-of-range )
<input min="1" max="5" class="out-of-range:border-red-500" />
// --- PLACEHOLDER-SHOWN - input placeholder is shown ( &:placeholder-shown )
<input class="placeholder-shown:border-gray-500" placeholder="you@example.com" />
// --- AUTOFILL - input has been autofilled by the browser ( &:autofill )
<input class="autofill:bg-yellow-200" />
// --- READ-ONLY - input is read-only ( &:read-only )
<input class="read-only:bg-gray-100" />
// --- FIRST - first child element, ( &:first-child )
<ul> {arr.map((data) => (<li class="py-4 first:pt-0"> {data.value} </li>))} </ul>
// --- LAST - last child element, ( &:last-child )
<ul> {arr.map((data) => (<li class="py-4 last:pt-0"> {data.value} </li>))} </ul>
// --- ONLY - element is the only child ( &:only-child )
<ul> {arr.map((data) => (<li class="py-4 only:py-0"> {data.value} </li>))} </ul>
// --- ODD - oddly numbered child element, ( &:nth-child(odd) )
<table> {arr.map((data) => (<tr class="bg-white odd:bg-gray-100"> {data.value} </tr>))} </table>
// --- EVEN - evenly numbered child element, ( &:nth-child(even) )
<table> {arr.map((data) => (<tr class="bg-white even:bg-gray-100"> {data.value} </tr>))} </table>
// --- FIRST-OF-TYPE - first child of its type, ( &:first-of-type )
<nav> {arr.map((data) => (<a href="#" class="ml-2 first-of-type:ml-6"> {data.value} </a>))} ... </nav>
// --- LAST-OF-TYPE - last child of its type, ( &:last-of-type )
<nav> {arr.map((data) => (<a href="#" class="mr-2 last-of-type:mr-6"> {data.value} </a>))} ... </nav>
// --- ONLY-OF-TYPE - only child of its type, ( &:only-of-type )
<nav> {arr.map((data) => (<a href="#" class="mx-2 only-of-type:mx-6"> {data.value} </a>))} ... </nav>
// --- VISITED - link has already been visited, ( &:visited )
<a href="https://example.com" class="text-blue-600 visited:text-purple-600"> ... </a>
// --- TARGET - element ID matches the current URL fragment, ( &:target )
<div id="about" class="target:shadow-lg"> ... </div>
Parent, children
// --- GROUP , GROUP-{MODIFIER} - style an element based on the state of some parent element
<a href="#" class="group block max-w-xs mx-auto rounded-lg p-6 bg-white 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>
</div>
<p class="text-slate-500 group-hover:text-white text-sm">Create a new project.</p>
</a>
// --- GROUP/{NAME} , GROUP-{MODIFIER}/{NAME} - specific parent group
<ul role="list">
<ul> {arr.map((data) => (
<li class="group/item hover:bg-slate-100">
<img src="{data.imageUrl}" alt="" />
<div> <a href="{data.url}">{data.name}</a> <p>{data.title}</p> </div>
<a class="group/edit invisible hover:bg-slate-200 group-hover/item:visible" href="tel:{data.phone}">
<span class="group-hover/edit:text-gray-700"> Call </span>
</a>
</li>
))} </ul>
</ul>
// --- GROUP-[{ARBITRARY_VALUE}] - one-off group-* modifiers
<div class="group is-published">
<div class="hidden group-[.is-published]:block"> Published </div>
</div>
// use the & character to mark where .group should end up in the final selector
// relative to the selector passed in
<div class="group">
<div class="group-[:nth-of-type(3)_&]:block"> ... </div>
</div>
// --- PEER , PEER-{MODIFIER} - styling based on sibling state, can be used only on previous siblings
<form>
<label class="block">
<input type="email" class="peer"/>
<p class="mt-2 invisible peer-invalid:visible text-pink-600 text-sm">
Please provide a valid email address.
</p>
</label>
</form>
// --- PEER/{NAME} , PEER-{MODIFIER}/{NAME} - specific peer style
<fieldset> <legend> Status </legend>
<input id="draft" class="peer/draft" type="radio" name="status" checked />
<label for="draft" class="peer-checked/draft:text-sky-500"> Draft </label>
<input id="published" class="peer/published" type="radio" name="status" />
<label for="published" class="peer-checked/published:text-sky-500"> Published </label>
<div class="hidden peer-checked/draft:block"> Drafts are only visible to administrators. </div>
<div class="hidden peer-checked/published:block"> Your post will be publicly visible. </div>
</fieldset>
// --- PEER-[{ARBITRARY_VALUE}] - one-off peer-* modifiers
<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>
// use the & character to mark where .peer should end up in the final selector
// relative to the selector passed in
<div>
<input type="text" class="peer" />
<div class="hidden peer-[:nth-of-type(3)_&]:block"> ... </div>
</div>
// --- * - modifier - styling direct children, (& > *)
<div>
<h2> Categories </h2>
<ul class="*:rounded-full *:border-sky-100 *:bg-sky-50 *:px-2 dark:text-sky-300 dark:*:border-sky-500/15">
<li> Sales </li>
<li> Marketing </li>
<li> SEO </li>
</ul>
</div>
// ! overriding a style with a utility directly on the child itself wont work:
<ul class="*:bg-sky-50">
<li class="bg-red-50"> Sales </li>
</ul>
// --- HAS-[{MODIFIER}] - style an element based on the state or content of its descendants, (&:has)
<label class="has-[:checked]:bg-indigo-50 has-[:checked]:text-indigo-900 has-[:checked]:ring-indigo-200">
<input type="radio" class="checked:border-indigo-500" />
</label>
// use pseudo-class, like has-[:focus], to style an element based on the state of its descendants
// or element selectors, like has-[img] or has-[a], to style an element based on the content of its descendants
// --- GROUP-HAS-* - style an element based on the descendants of a parent element
<div class="group">
<svg class="hidden group-has-[a]:block"> ... </svg>
<p> Product Designer at <a href="...">planeteria.tech</a> </p>
</div>
// --- PEER-HAS-{MODIFIER} - descendants of a peer
<fieldset> <legend>Today</legend>
<div>
<label class="peer">
<input type="checkbox" name="todo[1]" checked /> Create a to do list
</label>
<svg class="peer-has-[:checked]:hidden"> ... </svg>
</div> ...
</fieldset>
Pseudo-elements
// --- BEFORE , AFTER - (::before , ::after)
<label class="block">
<span class="after:content-['*'] after:ml-0.5 after:text-red-500 block">
Email
</span>
<input type="email" name="email" class="px-3 bg-white focus:ring-1" placeholder="you@example.com" />
</label>
<blockquote class="text-2xl font-semibold italic text-center text-slate-900">
When you look
<span class="before:block before:absolute before:-inset-1 before:-skew-y-3 before:bg-pink-500 relative inline-block">
<span class="relative text-white"> annoyed </span>
</span>
all the time, people think that you're busy.
</blockquote>
// html version:
<blockquote class="text-2xl font-semibold italic text-center text-slate-900">
When you look
<span class="relative">
<span class="block absolute -inset-1 -skew-y-3 bg-pink-500" aria-hidden="true"></span>
<span class="relative text-white"> annoyed </span>
</span>
all the time, people think that you're busy.
</blockquote>
// include content-[''], if preflight styles are disabled
// --- PLACEHOLDER - placeholder text of any input or textarea, (&::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 border rounded-md py-2 shadow-sm focus:ring-1 sm:text-sm"
placeholder="Search for anything..."
type="text"
name="search"
/>
</label>
// --- FILE - button in file inputs, (&::file-selector-button)
<form class="flex items-center space-x-6">
<div class="shrink-0">
<img class="h-16 w-16 object-cover rounded-full" src="https://link.com" 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
file:border file:border-solid
"/>
</label>
</form>
// --- MARKER - counters or bullets in lists, use it on li or parent, (&::marker)
<ul role="list" class="marker:text-sky-400 list-disc pl-5 space-y-3 text-slate-400">
<li> 5 cups chopped Porcini mushrooms </li>
<li> 1/2 cup of olive oil </li>
<li> 3lb of celery </li>
</ul>
// --- SELECTION - active text selection, (&::selection)
<div class="selection:bg-fuchsia-300 selection:text-fuchsia-900">
<p>
So I started to walk into the water.
I <em>was</em> a marine biologist.
</p>
</div>
// add it anywhere in the tree, it will be applied to all descendant elements
<body class="selection:bg-pink-300"> ... </body>
// --- FIRST-LINE , 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-white
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.
</p>
// --- BACKDROP - backdrop of a native dialog element, (&::backdrop)
<dialog class="backdrop:bg-gray-50">
<form method="dialog"> ... </form>
</dialog>
Attribute selectors
// --- DATA-* - data attributes, only arbitrary values support by default ( &[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>
// configure shortcuts for common data attribute selectors
// tailwind.config.js file theme.data:
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
data: {
checked: 'ui~="checked"',
},
},
}
// usage:
<div data-ui="checked active" class="data-checked:underline"> ... </div>
// --- ARIA-* - ARIA attributes
<div aria-checked="true" class="bg-gray-600 aria-checked:bg-sky-700"> ... </div>
// default attributes:
// 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"]
// generate a property on the fly, ( &[aria-…] ) :
<th
aria-sort="ascending"
class="aria-[sort=ascending]:bg-[url('/img/down-arrow.svg')] aria-[sort=descending]:bg-[url('/img/up-arrow.svg')]"
>
Invoice #
</th>
// GROUP-ARIA-* , PEER-ARIA-* - target parent and sibling elements:
<th aria-sort="ascending" class="group">
<svg class="group-aria-[sort=ascending]:rotate-0 group-aria-[sort=descending]:rotate-180"> ... </svg>
</th>
// --- OPEN - "details" or "dialog" element open state
<div class="max-w-lg mx-auto p-8">
<details class="open:bg-white dark:open:bg-slate-900 open:ring-1 p-6 rounded-lg" open>
<summary class="text-sm leading-6 text-slate-900 dark:text-white">
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>
// --- RTL , LTR - building multi-directional layouts
// for sites that needs to support both
<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-300 group-hover:text-white"> ... </p>
<p class="text-sm font-medium text-slate-500 group-hover:text-slate-300"> ... </p>
</div>
</div>
// ltr modifier will not take effect unless the dir attribute is explicitly set to ltr
<html dir="ltr"> ... </html>
Responsive design, media and feature queries
mobile-first breakpoint system - unprefixed utilities ( like uppercase ) take effect on all screen sizes, while prefixed utilities (like xl:uppercase) only take effect at the specified breakpoint and above
styles applied by rules like xl:flex will apply at that breakpoint, and stay applied at larger breakpoints
// --- BREAKPOINTS - responsive utility variants to apply utility classes conditionally
// default, by common device resolutions:
// sm - @media (min-width: 640px) { ... }
// md - @media (min-width: 768px) { ... }
// lg - @media (min-width: 1024px) { ... }
// xl - @media (min-width: 1280px) { ... }
// 2xl - @media (min-width: 1536px) { ... }
// usage:
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md xl:max-w-2xl">
<div class="xl:flex">
<div class="xl:shrink-0">
<img class="h-48 w-full object-cover xl:h-full xl:w-48"
src="/img/building.jpg" alt="Modern building architecture">
</div>
</div>
</div>
<img class="w-16 xl:w-32 lg:w-48" src="..." />
// breakpoint range - stack a responsive modifier with a max-* modifier
<div class="xl:max-xl:flex"> ... </div>
// 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) { ... }
// single breakpoint - stack a responsive modifier with a max-* modifier for the next breakpoint
<div class="xl:max-lg:flex"> ... </div>
// arbitrary values - 'min' or 'max' modifiers for custom breakpoint generation
<div class="min-[320px]:text-center max-[600px]:bg-sky-300"> ... </div>
// --- PORTRAIT , LANDSCAPE - viewport is in a specific orientation
// @media (orientation: portrait) , @media (orientation: landscape)
<div>
<div class="portrait:hidden"> ... </div>
<div class="landscape:hidden"> Landscape design, rotate your device to view the site. </div>
</div>
// --- SUPPORTS-[{BROWSER_FEATURE}] - whether a feature is supported by browser, ( @supports (…) )
// '@supports' rules, takes anything, like a property/value pair, and even expressions using "and" and "or"
<div class="flex supports-[display:grid]:grid"> ... </div>
// specifying just the property name
<div class="bg-black/75 supports-[backdrop-filter]:bg-black/25 supports-[backdrop-filter]:backdrop-blur"> ... </div>
// configure shortcuts for common @supports rules, tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
supports: { grid: 'display: grid' },
},
}
// then use these custom supports-* modifiers:
<div class="supports-grid:grid"> ... </div>
// --- MOTION-REDUCE - user has requested reduced motion
// @media (prefers-reduced-motion: reduce)
<button type="button" class="bg-indigo-500" disabled>
<svg class="motion-reduce:hidden animate-spin" viewBox="0 0 24 24"> ... </svg>
Processing...
</button>
// --- MOTION-SAFE - adds styles when the user has NOT requested reduced motion, vs. "undoing" styles
// @media (prefers-reduced-motion: no-preference)
<button class="motion-safe:hover:-translate-x-0.5 motion-safe:transition">
Save changes
</button>
// --- CONTRAST-MORE , CONTRAST-LESS - user has requested more contrast
// @media (prefers-contrast: more|less)
<label class="block">
<span class="block text-sm font-medium text-slate-700">Social Security Number</span>
<input class="border-slate-200 placeholder-slate-400
contrast-more:border-slate-400 contrast-more:placeholder-slate-500"/>
<p class="mt-2 opacity-10 contrast-more:opacity-100 text-slate-600 text-sm">
Required
</p>
</label>
// --- FORCED-COLORS - user has enabled a forced color mode
// forced-color-adjust: auto|none;
<form> <legend> Choose a theme: </legend>
<label>
<input type="radio" class="forced-colors:appearance-auto appearance-none" />
<p class="forced-colors:block hidden"> Cyan </p>
<div class="forced-colors:hidden h-6 w-6 rounded-full bg-cyan-200"></div>
<div class="forced-colors:hidden h-6 w-6 rounded-full bg-cyan-500"></div>
</label>
</form>
// --- PRINT - only apply when the document is being printed, @media 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>
Dark mode
by default is used prefers-color-scheme CSS media feature
also supports toggling dark mode manually using the 'class' strategy
with a prefix in Tailwind config, add it to the 'dark' class (with 'tw' prefix use 'tw-dark')
// --- DARK - dark mode modifier
// target light mode with no modifier, and "dark" modifier for dark mode
// ( @media (prefers-color-scheme: dark) )
<div class="bg-white dark:bg-slate-900 rounded-lg px-6 py-8 ring-1 ring-slate-900/5 shadow-xl">
<div>
<span class="inline-flex items-center justify-center p-2 bg-indigo-500 rounded-md shadow-lg">
<svg class="h-6 w-6 text-white" viewBox="0 0 24 24"> ... </svg>
</span>
</div>
<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.
</p>
</div>
// --- toggling dark mode manually, tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
// ...
}
// applied whenever dark class is present earlier in the HTML tree.
// dark mode not enabled, bg-white applied:
<html>
<body>
<div class="bg-white dark:bg-black"> ... </div>
</body>
</html>
// dark mode enabled, bg-black applied:
<html class="dark">
<body>
<div class="bg-white dark:bg-black"> ... </div>
</body>
</html>
// JS example:
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')
}
// choose light mode
localStorage.theme = 'light'
// choose dark mode
localStorage.theme = 'dark'
// respect the OS preference
localStorage.removeItem('theme')
// customize the dark mode selector name
// setting darkMode to an array with custom selector as the second item
// tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class', '[data-mode="dark"]'],
// ...
}
Advanced concepts
STACKED MODIFIERS are applied from the inside-out, like nested function calls
'dark:group-hover:focus:opacity-100'
// ...are applied like this:
dark(groupHover(focus('opacity-100')))
// "dark" element needs to be a parent of the group element:
/* dark:group-hover:opacity-100 */
.dark .group:hover .dark\:group-hover\:opacity-100 {
opacity: 1;
}
// reversed:
/* group-hover:dark:opacity-100 */
.group:hover .dark .group-hover\:dark\:opacity-100 {
opacity: 1;
}
// in prose-headings (official typography plugin)
// every single heading is underlined when you hover over the article itself:
/* prose-headings:hover:underline */
.prose-headings\:hover\:underline:hover :is(:where(h1, h2, h3, h4, th)) {
text-decoration: underline;
}
// each heading is only underlined when you hover over that heading:
/* hover:prose-headings:underline */
.hover\:prose-headings\:underline :is(:where(h1, h2, h3, h4, th)):hover {
text-decoration: underline;
}
ARBITRARY VALUES, [...]
<div class="top-[117px] lg:top-[344px]"> ... </div>
<div class="bg-[#bada55] text-[22px] before:content-['Festivus']"> ... </div>
// use the theme function to reference the design tokens in tailwind.config.js:
<div class="grid grid-cols-[fit-content(theme(spacing.32))]"> ... </div>
// using a CSS variable as an arbitrary value, just providing the actual variable name:
<div class="bg-[--my-color]"> ... </div>
// when an arbitrary value needs to contain a space, use an underscore:
<div class="grid grid-cols-[1fr_500px_2fr]"> ... </div>
// Tailwind preserves the underscore instead of converting it to a space for example in URLs:
<div class="bg-[url('/what_a_rush.png')]"> ... </div>
// escape the underscore with a backslash and Tailwind wont convert it to a space:
<div class="before:content-['hello\_world']"> ... </div>
// use String.raw() where the backslash is stripped from the rendered HTML (JSX, etc..):
<div className={String.raw`before:content-['hello\_world']`}> ... </div>
// add a CSS data type before the value to help Tailwind with the underlying type:
<div class="text-[length:var(--my-var)]"> ... </div> // font-size utility
<div class="text-[color:var(--my-var)]"> ... </div> // color utility
// Tailwind generally handles ambiguity automatically based on the passed value:
<div class="text-[22px]"> ... </div> // font-size utility
<div class="text-[#bada55]"> ... </div> // color utility
// --- PROPERTIES - properties not included out of the box
<div class="[mask-type:luminance]"> ... </div>
<div class="[mask-type:luminance] hover:[mask-type:alpha]"> ... </div>
// write completely arbitrary CSS:
<div class="[--scroll-offset:56px] lg:[--scroll-offset:44px]"> ... </div>
// --- VARIANTS / MODIFIERS - selector, wrapped in square brackets
<ul role="list">
{arr.map((data) => (<li class="lg:[&:nth-child(3)]:hover:underline"> {data.value} </li>))}
</ul>
// can be stacked with built-in modifiers or with each other:
{arr.map((data) => (<li class="lg:[&:nth-child(3)]:hover:underline"> {data.value} </li>))}
// use an underscore for spaces in selector, select all p elements within the element:
<div class="[&_p]:mt-4">
<p> ... </p>
<ul>
<li> <p> ... </p> </li>
// ...
</ul>
</div>
// use at-rules like @media or @supports in arbitrary variants:
<div class="flex [@supports(display:grid)]:grid"> ... </div>
// combine at-rules and regular selector modifiers:
<button type="button" class="[@media(any-hover:hover){&:hover}]:opacity-100"> ... </button>
// - creating a plugin - addVariant API for arbitrary modifier used multiple times
// tailwind.config.js :
let plugin = require('tailwindcss/plugin')
module.exports = {
// ...
plugins: [
plugin(function ({ addVariant }) { // add a "third" variant, ie. "third:pb-0"
addVariant('third', '&:nth-child(3)')
})
]
}
Custom CSS and @layer directive
Tailwind organizes the styles it generates into three different 'layers'
base - things like reset rules or default styles applied to plain HTML elements
components - class-based styles that you want to be able to override with utilities
utilities - small, single-purpose classes that should always take precedence over any other styles
@layer directive helps control declaration order by automatically relocating custom styles to the corresponding @tailwind directive, and also enables features like modifiers and tree-shaking for custom CSS
get the precedence behavior by placing custom styles before or after @tailwind directives
use the theme() function or @apply directive when adding custom base styles to refer to any of the values defined in theme
styles outside@layer directive are always included, otherwise, only when actually used in HTML
primarily, reuse styles using frameworks components system
// main.css
@tailwind base;
@tailwind components;
@tailwind utilities;
// add custom CSS directly
.my-custom-style {
/* ... */
}
// or use the @layer directive to add styles to Tailwind 'base', 'components', and 'utilities' layers
@layer components {
.my-custom-style {
/* ... */
}
}
// custom styles added with @layer will automatically support Tailwind modifier syntax:
@layer utilities {
.content-auto {
content-visibility: auto;
}
}
...
<div class="lg:dark:content-auto"> ... </div>
'base' layer
// set some defaults for the page by adding some classes to the html or body elements:
<!doctype html>
<html lang="en" class="text-gray-900 bg-gray-100 font-serif"> ... </html>
// or add default base styles for specific HTML elements with @layer directive
@layer base {
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
...
}
'components' layer
// complicated classes for project that can be overridden with utility classes
@layer components {
.card {
background-color: theme('colors.white');
border-radius: theme('borderRadius.lg');
padding: theme('spacing.6');
box-shadow: theme('boxShadow.xl');
}
...
}
// use utility classes to override them when necessary:
<div class="card rounded-none"> ... </div>
// 'components' layer is also a good place to put custom styles for any third-party components:
@layer components {
...
.select2-dropdown {
@apply rounded-b-lg shadow-md;
}
...
}
// or write those styles without using @layer directive:
@tailwind base;
@tailwind components;
.select2-dropdown {
@apply rounded-b-lg shadow-md;
}
.select2-search {
@apply border border-gray-300 rounded;
}
@tailwind utilities;
'utilities' layer
// for custom utility classes that Tailwind doesnt include out of the box
@layer utilities {
.content-auto {
content-visibility: auto;
}
}
DIRECTIVES & FUNCTIONS exposed to CSS
@tailwind directive inserts Tailwind styles into your CSS (base, components, utilities and variants)
DIRECTIVES
/* base styles and any base styles registered by plugins */
@tailwind base;
/* component classes and any component classes registered by plugins */
@tailwind components;
/* utility classes and any utility classes registered by plugins */
@tailwind utilities;
/**
* this directive controls where Tailwind injects the
* hover, focus, responsive, dark mode, and other variants of each class.
* if omitted, Tailwind appends these classes to the very end of stylesheet by default.
*/
@tailwind variants;
/**
* @layer directive
* tells Tailwind to which layer (base, components, and utilities) a set of custom styles belong to
*/
@layer base {
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
}
@layer components {
.btn-blue {
@apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
}
}
@layer utilities {
.filter-none {
filter: none;
}
.filter-grayscale {
filter: grayscale(100%);
}
}
/**
* @apply directive
* inline any existing utility classes into custom CSS
* work with design tokens and use the same syntax.
*/
.select2-dropdown {
@apply rounded-b-lg shadow-md;
}
.select2-search {
@apply border border-gray-300 rounded;
}
.select2-results__group {
@apply text-lg font-bold text-gray-900;
}
/**
* '!important' is removed by default to avoid specificity issues,
* add '!important' to the end of the declaration if required.
*/
.btn {
@apply font-bold py-2 px-4 rounded !important;
}
/* with interpolation feature in Sass/SCSS */
.btn {
@apply font-bold py-2 px-4 rounded #{!important};
}
/**
* @config directive
* specify which config file Tailwind should use when compiling that CSS file.
* provided path is relative to that CSS file,
* and takes precedence over a path defined in PostCSS config or in the Tailwind CLI.
*/
/* site.css */
@config "./tailwind.admin.config.js";
@tailwind base;
@tailwind components;
@tailwind utilities;
/* admin.css */
@config "./tailwind.admin.config.js";
@tailwind base;
@tailwind components;
@tailwind utilities;
/* with postcss-import, @import statements need to come before @config */
@config "./tailwind.admin.config.js";
FUNCTIONS - access Tailwind-specific values
/**
* theme()
* access Tailwind config values using dot notation
*/
.content-area {
height: calc(100vh - theme(spacing.12));
}
/* use dot notation to access nested color values */
.btn-blue {
background-color: theme(colors.blue.500);
}
/* use square bracket notation for value that contains a dot */
.content-area {
height: calc(100vh - theme(spacing[2.5]));
}
/* use a slash followed by the opacity value */
.btn-blue {
background-color: theme(colors.blue.500 / 75%);
}
/**
* screen()
* create media queries that reference breakpoints by name
* instead of duplicating their values in CSS
*/
@media screen(sm) {
/* ... */
}
/**
* generate a regular media query that matches specified breakpoint
* resolve to the underlying screen value at build-time
*/
@media (min-width: 640px) {
/* ... */
}
CLASS DETECTION
// regular expressions is used to extract every string that could possibly be a class name.
// use complete unbroken strings, dont construct class names dynamically.
// inside any file like JS, JSX:
const sizes = {
md: 'px-4 py-2 rounded-md text-base',
lg: 'px-5 py-3 rounded-lg text-lg',
}
export default function Button({ color, size, children }) {
let sizeClasses = sizes[size]
const colorVariants = {
blue: 'bg-blue-600 hover:bg-blue-500 text-white',
red: 'bg-red-500 hover:bg-red-400 text-white',
yellow: 'bg-yellow-300 hover:bg-yellow-400 text-black',
}
return (
)
}
Config
tailwind.config.js - file, by default at the project root, where customizations are defined
every section of the config file is optional, missing sections will fall back to Tailwind default configuration
to generate multiple CSS files using different Tailwind configurations, use the @config directive
// --- GENERATE Tailwind config file for project, if dont exists,
// using the Tailwind CLI utility included when you install the tailwindcss npm package
npx tailwindcss init
npx tailwindcss init --full // scaffold a complete config file with all Tailwind defaults
// Tailwind detects if project is an ES Module and automatically generates correct config file
npx tailwindcss init --esm // generate an ESM config file explicitly
npx tailwindcss init --ts // generate a TypeScript config file
// generate a basic postcss.config.js file alongside config file
npx tailwindcss init -p
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
// --- CUSTOM CONFIG FILE NAME
npx tailwindcss init custom-config-file-name.js
// specify custom file name as a command-line argument when compiling CSS with the Tailwind CLI tool
npx tailwindcss -c ./tailwindcss-config.js -i input.css -o output.css
// alternatively, specify custom configuration path using the @config directive
@config "./tailwindcss-config.js";
@tailwind base;
@tailwind components;
@tailwind utilities;
Referencing in JavaScript
import resolveConfig from 'tailwindcss/resolveConfig'
import tailwindConfig from './tailwind.config.js'
const fullConfig = resolveConfig(tailwindConfig)
fullConfig.theme.width[4] // => '1rem'
fullConfig.theme.screens.md // => '768px'
fullConfig.theme.boxShadow['2xl'] // => '0 25px 50px -12px rgba(0, 0, 0, 0.25)'
// use tool like babel-plugin-preval to avoid bigger client-side bundle size in this case
Configuration file
/** @type {import('tailwindcss').Config} */
module.exports = {
// --- CONTENT
// paths to all of source files that contain Tailwind class names (HTML templates, JS components)
content: [
// Tailwind resolves non-absolute content paths relative to the current working directory
'./public/index.html', // include HTML entry point if applicable
'./index.html', // list files located at the root independently
'./src/index.html', // be specific to avoid issues
// use glob patterns paths match (fast-glob) :
// * - anything except slashes and hidden files
// ** - zero or more directories
// use comma between values inside {} to match against a list of options
'./pages/**/*.{html,js}',
'./components/**/*.{html,js}',
'./util/**/*.{html,js}',
'./src/**/*.js', // any JS files that manipulate HTML to add classes: .classList.toggle('hidden'),...
// always resolve paths relative to the tailwind.config.js file:
relative: true,
// own reusable set of components:
'./node_modules/@my-company/tailwind-components/**/*.js',
// use require.resolve (import: const path = require('path'); ) in monorepo with workspaces:
path.join(path.dirname(require.resolve('@my-company/tailwind-components')), '**/*.js'),
// DO NOT:
// - SCAN ANY CSS FILES
// - USE EXTREMELY BROAD PATTERNS ( ./**/*.{html,js} )
// - WATCH DIECTORIES (like src/**/*.html) THAT GET A GENERATED OUTPUT ON REBUILD (like src/css/styles.css)
],
// transforming source files
// compile content in a format that compiles to HTML (like Markdown) before scanning it for class names:
// example imports: const remark = require('remark')
content: {
files: ['./src/**/*.{html,md}'],
transform: { md: (content) => (remark().process(content)) }
},
// override the logic Tailwind uses to detect class names for specific file extensions
content: {
files: ['./src/**/*.{html,wtf}'],
extract: { wtf: (content) => (content.match(/[^<>"'`\s]*/g)) }
},
// --- THEME, anything related to the visual design (colors, fonts, type scale, border sizes, breakpoints)
// - override default theme by adding options in 'theme' section directly, unprovided keys will be inherited
// - use 'extend' with same keys to add new classes/variants.
// override some parts of the default theme, and extend other parts within the same configuration.
theme: {
// responsive breakpoints, modifiers (like 'xl:text-center')
screens: {
'sm': '640px', // @media (min-width: 640px) { ... }
'md': '768px', // @media (min-width: 768px) { ... }
'lg': '1024px', // @media (min-width: 1024px) { ... }
'xl': '1280px', // @media (min-width: 1280px) { ... }
'2xl': '1536px', // @media (min-width: 1536px) { ... }
// --- custom names, class="grid grid-cols-1 tablet:grid-cols-2 laptop:grid-cols-3 desktop:grid-cols-4"
'tablet': '640px', // @media (min-width: 640px) { ... }
'laptop': '1024px', // @media (min-width: 1024px) { ... }
'desktop': '1280px', // @media (min-width: 1280px) { ... }
// --- work with max-width breakpoints instead of min-width:
'2xl': {'max': '1535px'}, // => @media (max-width: 1535px) { ... }
// ... list in a descending order
'sm': {'max': '639px'}, // => @media (max-width: 639px) { ... }
// --- specify a min-width and a max-width:
'sm': {'min': '640px', 'max': '767px'}, // @media (min-width: 640px and max-width: 767px) { ... }
// ... list in an ascending order
'2xl': {'min': '1536px'}, // @media (min-width: 1536px) { ... }
// --- multi-range breakpoints:
'sm': '500px',
'md': [
// element appears at 768px,
// revert to 'sm:' styles between 768px and 868px,
// after which the main content area is wide enough again to apply the 'md:' styles
{'min': '668px', 'max': '767px'},
{'min': '868px'}
],
'lg': '1100px',
'xl': '1400px',
// --- custom media queries:
'tall': { 'raw': '(min-height: 800px)' },
},
// color palette, made available everywhere in the framework:
// 'bg-blue', 'text-gray-dark', 'bg-tahiti-900'
colors: {
// completely replace the default color palette, and leave only required.
transparent: 'transparent',
current: 'currentColor',
primary: '#5c6ac4',
secondary: '#ecc94b',
'blue': '#1fb6ff',
// ...
'gray': '#8492a6',
'gray-dark': '#273444',
'tahiti': {
light: '#67e8f9', // bg-tahiti-light
DEFAULT: '#06b6d4', // value with no suffix: bg-tahiti
dark: '#0e7490', // bg-tahiti-dark
100: '#f2e8e5',
// ...
900: '#43302b', // bg-tahiti-900
},
// --- using the default colors
// example imports: const colors = require('tailwindcss/colors')
black: colors.black,
white: colors.white,
gray: colors.gray,
emerald: colors.emerald,
indigo: colors.indigo,
yellow: colors.amber, // alias
pink: colors.fuchsia, // alias
// --- using CSS variables:
// - 1 - define CSS variables as channels with no color space function, main.css :
// @layer base {
// :root {
// --color-primary: 255 115 179; // for rgb(255 115 179 / <alpha-value>)
// --color-secondary: 111 114 185;
// --color-primary: 198deg 93% 60%; // for hsl(198deg 93% 60% / <alpha-value>)
// --color-primary: 255, 115, 179; // for rgba(255, 115, 179, <alpha-value>)
// } }
// - 2 - define colors in configuration file,
// include the color space and special <alpha-value> placeholder:
primary: 'rgb(var(--color-primary) / <alpha-value>)',
secondary: 'rgb(var(--color-secondary) / <alpha-value>)',
primary: 'hsl(var(--color-primary) / <alpha-value>)',
secondary: 'hsl(var(--color-secondary) / <alpha-value>)',
primary: 'rgba(var(--color-primary), <alpha-value>)',
secondary: 'rgba(var(--color-secondary), <alpha-value>)',
},
// spacing and sizing scale, inherited by the core plugins:
// padding, margin, width, minWidth, maxWidth, height, minHeight, maxHeight,
// flex-basis, gap, inset, space, translate, scrollMargin, scrollPadding, and textIndent
spacing: {
px: '1px',
0: '0',
0.5: '0.125rem',
1: '0.25rem', // ...
96: '24rem',
// ...
sm: '8px',
md: '12px',
lg: '16px',
xl: '24px',
},
// replace all of the default 'opacity' values
opacity: {
'0': '0',
'20': '0.2',
// ...
'100': '1',
}
// referencing other values. only in top-level theme keys.
// generate background-size utilities for every value in your spacing scale:
backgroundSize: ({ theme }) => ({ // theme() function object
auto: 'auto',
cover: 'cover',
contain: 'contain',
...theme('spacing') // look up other theme values using dot notation
})
// EXTENDING the default theme
// examples imports: const defaultTheme = require('tailwindcss/defaultTheme')
extend: {
screens: {
'lg': '992px', // replace the default screens value with the same name
'3xl': '1600px', // new '3xl:' screen variant, alongside the existing
'9xl': '128rem', // adds to the end of the default breakpoint list
// --- adding to the beginning of the list:
'xs': '475px',
...defaultTheme.screens,
},
fontFamily: {
display: 'Oswald, ui-serif', // new 'font-display' class
sans: [
'Lato',
...defaultTheme.fontFamily.sans, // add a font family to one of default font stacks
]
},
// add a brand new color to the default palette:
colors: {
brown: {
50: '#fdf8f6',
// ...
900: '#43302b',
},
blue: {
950: '#17275c', // add additional shades to an existing color
},
},
borderRadius: {
'4xl': '2rem',
}
}
// rest of the section is used to configure which values are available for each individual core plugin
fontFamily: {
sans: ['Graphik', 'sans-serif'],
serif: ['Merriweather', 'serif'],
},
borderRadius: {
'none': '0',
'sm': '.125rem',
DEFAULT: '.25rem', // DEFAULT = core plugins no suffix style version: .rounded {...}
'lg': '.5rem', // .rounded-lg
'full': '9999px', // .rounded-full
},
},
// --- PLUGINS
// register plugins used to generate extra utilities, components, base styles, or custom variants
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'),
require('@tailwindcss/typography'),
require('tailwindcss-children'),
],
corePlugins: {
// DISABLING an entire core plugin
opacity: false,
}
// --- PRESETS, specifies a custom base configuration, instead of using Tailwind defaults
presets: [
require('@acmecorp/base-tailwind-config')
// when adding multiple presets, the last configuration wins if they overlap in any way
require('@acmecorp/tailwind-colors'),
require('@acmecorp/tailwind-spacing'),
],
// then add project-specific customizations: theme, ...
// --- create a config/preset that completely replaces the default config
// disable all of Tailwind defaults, no colors, font, spacing, etc. will be generated.
presets: [], // include an empty presets
// --- SAFELIST, generate certain class names that dont exist in content files
// only for situations where its impossible to scan certain content for class names
safelist: [
'bg-red-500',
'text-3xl',
'lg:text-4xl',
// using regular expressions, can only match against base utility names: '/bg-red-.+/'
// wont match if the pattern includes a variant modifier: '/hover:bg-red-.+/'
{
pattern: /bg-(red|green|blue)-(100|200|300)/,
},
// force Tailwind to generate variants for any matched classes
{
pattern: /bg-(red|green|blue)-(100|200|300)/,
variants: ['lg', 'hover', 'focus', 'lg:hover'],
},
]
// --- BLOCKLIST, prevent generating certain classes (conflict with some existing CSS, etc.)
// only affects CSS that would be generated by Tailwind
// only supports strings, regular expressions blocking is not available
blocklist: [
'container',
'collapse',
],
// --- PREFIX, useful when layering Tailwind on top of existing CSS
// every class will be generated with the configured prefix:
// tw-text-left, .tw-text-center, ...
// prefix is added after any variant modifiers:
// tw-text-lg xl:tw-text-xl tw-bg-red-500 hover:tw-bg-blue-500
// dash modifier for negative values is be added before prefix: -tw-mt-8
prefix: 'tw-',
// no prefix will be added to custom classes, even with @layer directive
// prefix utilities as well, just add the prefix to the class definition
// --- IMPORTANT
// control whether or not Tailwind utilities should be marked with !important
// useful with existing CSS that has high specificity selectors
important: true,
// - selector strategy - set 'important' to an ID selector like #app instead
important: '#app',
// then set root element (body, div) 'id' attribute to 'app' in order for styles to work properly
// template file that includes root selector should be included in 'content' configuration.
// - alternatively, make any utility important by adding a ! character to the beginning:
// !font-medium , sm:hover:!tw-font-bold
// --- SEPARATOR
// character used to separate modifiers (screen sizes, hover, etc.) from utility names (text-center, etc.).
// colon is by default (:), change for templating language like Pug
separator: '_',
// --- CORE PLUGINS - disable classes that Tailwind would normally generate by default
corePlugins: {
float: false,
objectFit: false,
objectPosition: false,
},
// - safelist which core plugins should be enabled
// provide an array that includes a list of the core plugins to use:
corePlugins: [
'margin',
'padding',
'backgroundColor',
// ...
],
// provide an empty array to disable all core plugins
// use Tailwind as a tool for processing custom plugins:
corePlugins: []
}
// --- PRESET - reusable configuration presets,
// regular Tailwind configuration objects, with exact same shape.
// will extend by default the default config.
// - 1 - my-preset.js :
module.exports = {
theme: {
colors: {
blue: {
light: '#85d7ff',
DEFAULT: '#1fb6ff',
dark: '#009eeb',
},
// ...
},
fontFamily: {
sans: ['Graphik', 'sans-serif'],
},
extend: {
flexGrow: {
2: '2',
3: '3',
},
// ...
}
},
plugins: [
require('@tailwindcss/typography'),
require('@tailwindcss/aspect-ratio'),
],
}
// - 2 - tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
presets: [
require('./my-preset.js'),
],
// customizations specific to this project would go here
theme: {
extend: {
minHeight: {
48: '12rem',
}
}
},
}
// --- presets merging logic:
// - options replaced by tailwind.config.js if present in a preset:
// content , darkMode , prefix , important , variantOrder , separator , safelist
// - 'theme' object is merged shallowly,
// top-level keys in tailwind.config.js replacing the same top-level keys in any presets,
// 'extend' key is collected across all configurations and applied on top of the rest.
// - 'presets' and 'plugins' array are merged across configurations
// - 'corePlugins', configured as an object, it is merged across configurations
// configured as an array, it replaces any configuration provided by configured preset(s)
opinionated set of base styles for Tailwind projects
@tailwind base; /* preflight will be injected here */
/* add own base styles on top of Preflight using the @layer base directive */
@layer base {
h1 {
@apply text-2xl;
}
a {
@apply text-blue-600 underline;
}
}
@tailwind components;
@tailwind utilities;
/*
1. Prevent padding and border from affecting element width.
2. Allow adding a border to an element by just adding a border-width.
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */
font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings', normal); /* 6 */
-webkit-tap-highlight-color: transparent; /* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/* Add the correct text decoration in Chrome, Edge, and Safari. */
abbr:where([title]) {
text-decoration: underline dotted;
}
/* Remove the default font size and weight for headings. */
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/* Reset links to optimize for opt-in styling instead of opt-out. */
a {
color: inherit;
text-decoration: inherit;
}
/* Add the correct font weight in Edge and Safari. */
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */
font-feature-settings: theme('fontFamily.mono[1].fontFeatureSettings', normal); /* 2 */
font-variation-settings: theme('fontFamily.mono[1].fontVariationSettings', normal); /* 3 */
font-size: 1em; /* 4 */
}
/* Add the correct font size in all browsers. */
small {
font-size: 80%;
}
/* Prevent `sub` and `sup` elements from affecting the line height in all browsers. */
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari.
2. Correct table border color inheritance in all Chrome and Safari.
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-feature-settings: inherit; /* 1 */
font-variation-settings: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/* Remove the inheritance of text transform in Edge and Firefox. */
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */
}
/* Use the modern Firefox focus style for all focusable elements. */
:-moz-focusring {
outline: auto;
}
/* Remove the additional `:invalid` styles in Firefox. */
:-moz-ui-invalid {
box-shadow: none;
}
/* Add the correct vertical alignment in Chrome and Firefox. */
progress {
vertical-align: baseline;
}
/* Correct the cursor style of increment and decrement buttons in Safari. */
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/* Remove the inner padding in Chrome and Safari on macOS. */
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Add the correct display in Chrome and Safari. */
summary {
display: list-item;
}
/* Removes the default spacing and border for appropriate elements. */
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
/*
Lists are unstyled/
Ordered and unordered lists are unstyled by default, with no bullets/numbers and no margin or padding.
Use 'list-style-type' and 'list-style-position' utilities: class="list-disc list-inside"
Unstyled lists are not announced as lists by VoiceOver.
If your content is truly a list but you would like to keep it unstyled, add a "list" role to the element:
<ul role="list">
*/
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/* Reset default styling for dialogs. */
dialog {
padding: 0;
}
/* Prevent resizing textareas horizontally by default. */
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox.
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: theme('colors.gray.400', #9ca3af); /* 2 */
}
/* Set the default cursor for buttons. */
button,
[role="button"] {
cursor: pointer;
}
/* Make sure disabled buttons don't get the pointer cursor. */
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default.
2. Add `vertical-align: middle` to align replaced elements more sensibly by default.
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/* Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. */
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
Layout
prefix the class name with a dash to convert it to a negative value: class="h-14 w-14 -left-4 -top-4"
start-* and end-* utilities, and 'start' and 'end' values map to either the left or right side based on the text direction
// --- ASPECT RATIO - aspect ratio of an element
aspect-auto // aspect-ratio: auto;
aspect-square // aspect-ratio: 1 / 1;
aspect-video // aspect-ratio: 16 / 9;
aspect-[4/3] // arbitrary value
// usage:
<iframe class="w-full aspect-video hover:aspect-square xl:aspect-square" src="..."></iframe>
// tailwind.config.js > theme.extend.aspectRatio
module.exports = {
theme: {
extend: {
aspectRatio: {
'4/3': '4 / 3',
} } } }
// --- CONTAINER - fixing element width to the current breakpoint
// sets the 'max-width' of an element to match the 'min-width' of the current breakpoint.
// design for a fixed set of screen sizes.
// usage, container at only a certain breakpoint and up:
<div class="xl:container xl:mx-auto"> ... </div>
// centered container with horizontal padding:
<div class="container mx-auto px-4"> ... </div>
// tailwind.config.js > theme.container
module.exports = {
theme: {
container: {
center: true, // center containers by default
padding: '2rem', // horizontal padding by default
// different padding amount for each breakpoint:
padding: {
DEFAULT: '1rem',
sm: '2rem',
lg: '4rem',
xl: '5rem',
'2xl': '6rem',
} } } }
// --- COLUMNS - number of columns for the content within an element
// column width will be automatically adjusted to accommodate that number
columns-1 // columns: 1;
// ...
columns-12 // columns: 12;
columns-auto// columns: auto;
columns-3xs // columns: 16rem; /* 256px */
columns-2xs // columns: 18rem; /* 288px */
columns-xs // columns: 20rem; /* 320px */
columns-sm // columns: 24rem; /* 384px */
columns-md // columns: 28rem; /* 448px */
columns-lg // columns: 32rem; /* 512px */
columns-xl // columns: 36rem; /* 576px */
// ...
columns-7xl // columns: 80rem; /* 1280px */
columns-[10rem] // arbitrary value
// usage:
<div class="columns-2 hover:columns-3 xl:columns-1">
<img class="w-full aspect-video" src="..." />
<img class="w-full aspect-square" src="..." /> // ...
</div>
<div class="gap-8 columns-3xs"> // specify width between columns
<img class="w-full aspect-video" src="..." />
<img class="w-full aspect-square" src="..." />
// ...
</div>
// tailwind.config.js > theme.columns | theme.extend.columns
module.exports = {
theme: {
extend: {
columns: {
'4xs': '14rem',
} } } }
// --- BREAK AFTER/BEFORE - column or page break after/before an element
break-[after|before]-*
break-after-auto // break-after: auto;
break-after-avoid // break-after: avoid;
break-after-all // break-after: all;
break-after-left // break-after: left;
break-after-right // break-after: right;
break-after-column // break-after: column;
break-after-page // break-after: page;
break-after-avoid-page // break-after: avoid-page;
// --- BREAK INSIDE - column or page break within an element
break-inside-auto // break-inside: auto;
break-inside-avoid // break-inside: avoid;
break-inside-avoid-column // break-inside: avoid-column;
break-inside-avoid-page // break-inside: avoid-page;
// usage:
<div class="columns-2">
<p> ... </p>
<p class="break-after-column"> ... </p>
<p class="hover:break-before-column"> ... </p>
<p class="xl:break-inside-avoid-column"> ... </p>
</div>
// --- BOX DECORATION BREAK - element fragments rendering across multiple lines, columns, or pages
// whether properties like background, border, border-image, box-shadow, clip-page, margin, and padding
// should be rendered as if the element were one continuous fragment, or distinct blocks.
box-decoration-clone // box-decoration-break: clone;
box-decoration-slice // box-decoration-break: slice;
// usage:
<span class="box-decoration-clone hover:box-decoration-slice xl:box-decoration-slice
bg-gradient-to-r from-indigo-600 to-pink-500 text-white px-2"
>
Hello<br />
World
</span>
// --- BOX SIZING - how the browser should calculate element total size
// to include or not the element borders and padding when a height or width is set
box-border // box-sizing: border-box; (default with preflight enabled)
box-content // box-sizing: content-box;
// usage:
<div class="box-border hover:box-content xl:box-content h-32 w-32 p-4 border-4"> ... </div>
// --- DISPLAY - display box type of an element
block // display: block;
inline-block // display: inline-block;
inline // display: inline;
flex // display: flex; // block-level flex container
inline-flex // display: inline-flex;
table // display: table;
inline-table // display: inline-table;
table-caption // display: table-caption;
table-cell // display: table-cell;
table-column // display: table-column;
table-column-group // display: table-column-group;
table-footer-group // display: table-footer-group;
table-header-group // display: table-header-group;
table-row-group // display: table-row-group;
table-row // display: table-row;
flow-root // display: flow-root; // block-level element with its own block formatting context
grid // display: grid;
inline-grid // display: inline-grid;
contents // display: contents; // 'phantom' container whose children act like direct children of the parent
list-item // display: list-item;
hidden // display: none;
// usage:
<div class="flex hover:inline-flex xl:inline-flex"> ... </div>
// --- FLOATS - wrapping of content around an element
float-start // float: inline-start;
float-end // float: inline-end;
float-right // float: right;
float-left // float: left;
float-none // float: none;
// usage:
<img class="float-right hover:float-left xl:float-left" src="path/to/image.jpg" />
<p> ... </p>
// --- CLEAR - wrapping of content around an element
clear-start // clear: inline-start;
clear-end // clear: inline-end;
clear-left // clear: left;
clear-right // clear: right;
clear-both // clear: both;
clear-none // clear: none;
// usage:
<article>
<img class="float-left" src="path/to/image.jpg" />
<img class="float-right" src="path/to/image.jpg" />
<p class="clear-left hover:clear-none xl:clear-none"> ... </p>
</article>
// --- ISOLATION - whether an element should explicitly create a new stacking context
isolate // isolation: isolate;
isolation-auto // isolation: auto;
// usage:
<div class="isolate hover:isolation-auto xl:isolation-auto"> ... </div>
// --- OBJECT FIT - replaced element content resize behavior
object-contain // object-fit: contain;
object-cover // object-fit: cover;
object-fill // object-fit: fill;
object-none // object-fit: none;
object-scale-down // object-fit: scale-down;
// usage:
<div class="bg-indigo-300">
<img class="object-cover hover:object-scale-down xl:object-none h-48 w-96" />
</div>
// --- OBJECT POSITION - replaced element content position within its container
object-bottom // object-position: bottom;
object-center // object-position: center;
object-left // object-position: left;
object-left-bottom // object-position: left bottom;
object-left-top // object-position: left top;
object-right // object-position: right;
object-right-bottom // object-position: right bottom;
object-right-top // object-position: right top;
object-top // object-position: top;
object-[center_bottom] // with arbitrary value 'center_bottom'
// usage:
<img class="object-center hover:object-top xl:object-[center_bottom]" src="...">
// tailwind.config.js > theme.objectPosition | theme.extend.objectPosition
module.exports = {
theme: {
extend: {
objectPosition: {
'center-bottom': 'center bottom',
} } } }
// --- OVERFLOW - element handling of content that is too large for the container
overflow-auto // overflow: auto;
overflow-hidden // overflow: hidden;
overflow-clip // overflow: clip;
overflow-visible // overflow: visible;
overflow-scroll // overflow: scroll;
overflow-x-auto // overflow-x: auto;
overflow-y-auto // overflow-y: auto;
overflow-x-hidden // overflow-x: hidden;
overflow-y-hidden // overflow-y: hidden;
overflow-x-clip // overflow-x: clip;
overflow-y-clip // overflow-y: clip;
overflow-x-visible // overflow-x: visible;
overflow-y-visible // overflow-y: visible;
overflow-x-scroll // overflow-x: scroll;
overflow-y-scroll // overflow-y: scroll;
// usage:
<div class="overflow-visible hover:overflow-scroll xl:overflow-auto"> ... </div>
// --- OVERSCROLL BEHAVIOR - browser behavior when reaching the boundary of a scrolling area
overscroll-auto // overscroll-behavior: auto;
overscroll-contain // overscroll-behavior: contain;
overscroll-none // overscroll-behavior: none;
overscroll-y-auto // overscroll-behavior-y: auto;
overscroll-y-contain // overscroll-behavior-y: contain;
overscroll-y-none // overscroll-behavior-y: none;
overscroll-x-auto // overscroll-behavior-x: auto;
overscroll-x-contain // overscroll-behavior-x: contain;
overscroll-x-none // overscroll-behavior-x: none;
// usage:
<html class="overscroll-auto focus:overscroll-contain xl:overscroll-contain">
// ...
</html>
// --- POSITION - element position in the DOM
static // position: static;
fixed // position: fixed;
absolute // position: absolute;
relative // position: relative;
sticky // position: sticky;
// usage:
<div class="relative hover:absolute xl:absolute">
<p> relative parent </p>
<div class="absolute bottom-0 left-0">
<p> absolute child </p>
</div>
</div>
<div class="static">
<!-- Static parent -->
<div class="static"><p> static child </p></div>
<div class="inline-block"><p> static sibling </p></div>
<!-- Static parent -->
<div class="absolute"><p> absolute child </p></div>
<div class="inline-block"><p> static sibling </p></div>
</div>
<div class="relative">
<div class="fixed top-0 left-0 right-0"> ... </div>
<div> ... </div>
</div>
// sticky positioning elements:
// relative until it crosses a specified threshold, then treat it as fixed until its parent is off screen.
// any offsets are calculated relative to the element normal position
// and the element will act as a position reference for absolutely positioned children.
<div>
<div>
<div class="sticky top-0"> A </div>
<div>
<div>
<img src="..." /> <strong> ... </strong>
</div>
// ...
</div>
</div>
<div>
<div class="sticky top-0"> B </div>
// ...
</div>
// ...
</div>
// --- TOP / RIGHT / BOTTOM / LEFT - placement of positioned elements
inset-{VALUE} // inset: VALUE;
inset-x-{VALUE} // left: VALUE; right: VALUE;
inset-y-{VALUE} // top: VALUE; bottom: VALUE;
start-{VALUE} // inset-inline-start: VALUE;
end-{VALUE} // inset-inline-end: VALUE;
top-{VALUE} // top: VALUE;
right-{VALUE} // right: VALUE;
bottom-{VALUE} // bottom: VALUE;
left-{VALUE} // left: VALUE;
// values:
0, px, 0.5, 1, ..., 96
1/2, 1/3, 2/3, 1/4, 2/4, 3/4
full, auto
[{ARBITRARY_VALUE}]
// usage:
<div class="relative h-32 w-32">
<div class="absolute left-0 hover:top-6 xl:h-16 w-16"> 01 </div> // Pin to top left corner
<div class="absolute inset-x-0 top-0 h-16"> 02 </div> // Span top edge
<div class="absolute top-0 right-0 h-16 w-16"> 03 </div> // Pin to top right corner
<div class="absolute inset-y-0 left-0 w-16">04 </div> // Span left edge
<div class="absolute inset-0"> 05 </div> // Fill entire parent
<div class="absolute inset-y-0 right-0 w-16"> 06 </div> // Span right edge
<div class="absolute bottom-0 left-0 h-16 w-16"> 07 </div> // Pin to bottom left corner
<div class="absolute inset-x-0 bottom-0 h-16"> 08 </div> // Span bottom edge
<div class="absolute bottom-0 right-0 h-16 w-16"> 09 </div> // Pin to bottom right corner
</div>
// prefix the class name with a dash to convert it to a negative value:
<div class="absolute h-14 w-14 -left-4 -top-4"> ... </div>
// arbitrary values:
<div class="top-[3px]"> ... </div>
// tailwind.config.js > theme.spacing | theme.extend.spacing | theme.extend.top|right|bottom|left|inset
module.exports = {
theme: {
extend: {
spacing: { // customize Tailwind spacing scale
'3px': '3px',
},
inset: { // customize just the top/right/bottom/left/inset scale
'3px': '3px',
}
} } }
// --- VISIBILITY - visibility of an element
visible // visibility: visible; // undoing the 'invisible' utility
invisible // visibility: hidden;
collapse // visibility: collapse;
// usage:
// invisible - to hide an element, but still maintain its place in the DOM, affecting the layout of other elements
<div class="grid grid-cols-3 gap-4">
<div> 01 </div>
<div class="visible hover:invisible xl:invisible"> 02 </div>
<div> 03 </div>
</div>
// collapse - hide table rows, row groups, columns, and column groups
// as if they were set to 'display: none', but without impacting the size of other rows and columns.
// dynamically toggle rows and columns without affecting the table layout
<table>
<thead>
<tr> <th> Invoice #</th> <th> Client </th> <th> Amount </th> </tr>
</thead>
<tbody>
<tr>
<td> #100 </td> <td> ... </td> <td> $2,000.00 </td>
</tr>
<tr class="collapse">
<td>#101</td> <td> ... </td> <td> $545.00 </td>
</tr>
<tr>
<td>#102</td> <td> ... </td> <td> $10,000.25 </td>
</tr>
</tbody>
</table>
// --- Z-INDEX - stack order of an element
z-0 // z-index: 0;
z-10 // z-index: 10;
z-20 // z-index: 20;
z-30 // z-index: 30;
z-40 // z-index: 40;
z-50 // z-index: 50;
z-auto // z-index: auto;
// usage:
<div class="z-40 hover:z-50 xl:z-50"> ... </div>
<div class="z-0"> ... </div>
<div class="z-[100]"> arbitrary value </div>
<div class="-z-50"> negative value </div>
// tailwind.config.js > theme.zIndex | theme.extend.zIndex
module.exports = {
theme: {
extend: {
zIndex: {
'100': '100',
} } } }
// --- SCREEN READERS - improving accessibility with screen readers
sr-only // hide an element visually without hiding it from screen readers.
// position: absolute;
// width: 1px;
// height: 1px;
// padding: 0;
// margin: -1px;
// overflow: hidden;
// clip: rect(0, 0, 0, 0);
// white-space: nowrap;
// border-width: 0;
not-sr-only // undo sr-only, make an element visible to sighted users as well as screen readers.
// position: static;
// width: auto;
// height: auto;
// padding: 0;
// margin: 0;
// overflow: visible;
// clip: auto;
// white-space: normal;
// usage:
<a href="#"> <svg> ... </svg> <span class="sr-only"> Settings </span> </a>
<a href="#content" class="sr-only focus:not-sr-only xl:not-sr-only"> Skip to content </a>
// --- FORCED COLOR ADJUST - opting in and out of forced colors
forced-color-adjust-auto // forced-color-adjust: auto;
forced-color-adjust-none // forced-color-adjust: none; - opt an element out the colors enforced by forced colors mode
// usage:
<a href="#content" class="forced-color-adjust-none focus:forced-color-adjust-auto xl:forced-color-adjust-auto">
Skip to content
</a>
// opt an element out the colors enforced by forced colors mode
// useful in situations where enforcing a limited color palette will degrade usability:
<form>
<img src="..." />
<div>
<h3> Basic Tee - $35 </h3>
<fieldset>
<legend class="sr-only"> Choose a color </legend>
<div class="forced-color-adjust-none ...">
<label >
<input class="sr-only" type="radio" name="color-choice" value="White" />
<span class="sr-only"> White </span>
<span class="size-6 rounded-full border border-black border-opacity-10 bg-white"> ... </span>
</label>
...
</div>
</fieldset>
</form>
// undo forced-color-adjust-none, making an element adhere to colors enforced by forced colors mode:
<form>
<fieldset class="forced-color-adjust-none lg:forced-color-adjust-auto ...">
<legend> Choose a color: </legend>
<select class="hidden lg:block">
<option value="White"> White </option>
<option value="Gray"> Gray </option>
<option value="Black"> Black </option>
</select>
<div class="lg:hidden">
<label>
<input class="sr-only" type="radio" name="color-choice" value="White" />
...
</label>
...
</div>
</fieldset>
</form>
Plugins
extend Tailwind with reusable third-party plugins
register new styles to inject into the stylesheet using JavaScript instead of CSS
plugin utilities automatically respect the prefix and important preferences
component classes respect the prefix preference (all classes in a selector will be prefixed by default), but they are not affected by the important preference (add manually)
base styles do not respect prefix or important preferences, are meant to target bare selectors like div or h1
plugin system expects CSS rules written as JavaScript objects (CSS-in-JS) with camelCase OR dash-case property names and nesting possibilities
// --- tailwind.config.js :
const plugin = require('tailwindcss/plugin')
module.exports = {
plugins: [
plugin(function({ // helper functions:
addUtilities, // registering new static utility styles
matchUtilities, // registering new dynamic utility styles
addComponents, // registering new static component styles
matchComponents, // registering new dynamic component styles
addBase, // registering new base styles
addVariant, // registering custom static variants
matchVariant, // registering custom dynamic variants
theme, // looking up values in the theme configuration
config, // looking up values in the Tailwind configuration
corePlugins, // checking if a core plugin is enabled
e, // manually escaping strings meant to be used in class names
}) {
// --- static utilities that dont support user-provided values: class="content-auto"
// can automatically be used with modifiers ( 'lg:content-auto' )
addUtilities({ '.content-auto': { 'content-visibility': 'auto' } });
// --- register utilities that map to values defined in the 'theme' config
// and arbitrary values: class="tab-[13]"
matchUtilities(
{ tab: (value) => ({ tabSize: value }) },
{ values: theme('tabSize') } // default values, classes that plugin generates: tab-1, ..., tab-8
);
// --- register new styles in 'components' layer, add more opinionated, complex classes,
// that might need to be overridden with utility classes, and can be used with modifiers.
addComponents({
'.btn': {
padding: '.5rem 1rem',
borderRadius: '.25rem', // camelCase property name
'font-weight': '600', // dash-case property name
},
'.btn-red': {
color: '#fff',
backgroundColor: '#e3342f',
fontWeight: '600 !important', // with 'important'
'&:hover': { // nesting is supported
backgroundColor: '#cc1f1a'
},
{
'@media (min-width: 500px)': {
// ...
}
},
},
})
// --- register new styles 'base' layer:
// base typography styles, opinionated global resets, or @font-face rules
addBase({
'h1': { fontSize: theme('fontSize.2xl') },\
'h2': { fontSize: theme('fontSize.xl') },
'h3': { fontSize: theme('fontSize.lg') },
})
// --- register own custom modifiers, static:
addVariant('inverted-colors', '@media (inverted-colors: inverted)') // class="flex inverted-colors:outline"
addVariant('optional', '&:optional') // class="optional:border-gray-300"
addVariant('hocus', ['&:hover', '&:focus']) // class="bg-blue-500 hocus:bg-blue-600"
// dynamic, parameterized variants:
matchVariant(
'nth',
(value) => (`&:nth-child(${value})`),
{ values: { 1: '1', 2: '2', 3: '3' } }
);
// also supports arbitrary values ( class="nth-[3n+1]:bg-blue-500" )
// control the source order of the generated CSS:
matchVariant("min", (value) => `@media (min-width: ${value})`, {
sort(a, z) { return parseInt(a.value) - parseInt(z.value); },
});
// to support the group-* and peer-* versions of own custom modifiers,
// register variants as separate using the special :merge directive
// to ensure the .group and .peer classes only appear once in the final selector
addVariant('optional', '&:optional')
addVariant('group-optional', ':merge(.group):optional &')
addVariant('peer-optional', ':merge(.peer):optional ~ &')
}),
],
theme: {
tabSize: {
1: '1',
2: '2',
4: '4',
8: '8',
}
},
}
// --- official plugins ( 'Plugins' > 'OFFICIAL PLUGINS' section )
module.exports = {
// ...
plugins: [
// set of 'prose' classes:
require('@tailwindcss/typography'), //
// opinionated form reset layer:
require('@tailwindcss/forms'),
// alternative to native aspect-ratio, and adds aspect-w-{n} and aspect-h-{n} :
require('@tailwindcss/aspect-ratio'),
// add new @{size} variants (like @sm and @md)
// style an element based on the dimensions of a parent marked with @container:
require('@tailwindcss/container-queries'),
]
}
// --- EXPOSING OPTIONS
// configurable plugins, invoked with a config object.
// each argument should be a function that receives the options
// and returns the value that you would have normally passed in using the regular API.
// --- ./plugins/markdown.js :
const plugin = require('tailwindcss/plugin')
module.exports = plugin.withOptions(function (options = {}) {
return function({ addComponents }) {
const className = options.className ?? 'markdown';
addComponents({
[`.${className}`]: {
// ...
}
})
}
}, function (options) {
return {
theme: {
markdown: {
// ...
}
},
}
})
// invoke plugin:
// --- tailwind.config.js :
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
// ...
},
plugins: [
require('./plugins/markdown.js')({
className: 'wysiwyg'
})
// also register plugins created this way normally
// without invoking them if they dont need to pass in any custom options:
require('./plugins/markdown.js')
],
}
OFFICIAL PLUGINS @tailwindcss/typography (v0.5.10)
// set of 'prose' classes for vanilla HTML (rendered from Markdown, or pulled from a CMS)
// install:
npm install -D @tailwindcss/typography
// tailwind.config.js
module.exports = {
theme: {
extend: {
typography: ({ theme }) => ({
// --- adding custom color theme:
pink: {
css: {
'--tw-prose-body': theme('colors.pink[800]'),
'--tw-prose-headings': theme('colors.pink[900]'),
'--tw-prose-lead': theme('colors.pink[700]'),
'--tw-prose-links': theme('colors.pink[900]'),
'--tw-prose-bold': theme('colors.pink[900]'),
'--tw-prose-counters': theme('colors.pink[600]'),
'--tw-prose-bullets': theme('colors.pink[400]'),
'--tw-prose-hr': theme('colors.pink[300]'),
'--tw-prose-quotes': theme('colors.pink[900]'),
'--tw-prose-quote-borders': theme('colors.pink[300]'),
'--tw-prose-captions': theme('colors.pink[700]'),
'--tw-prose-code': theme('colors.pink[900]'),
'--tw-prose-pre-code': theme('colors.pink[100]'),
'--tw-prose-pre-bg': theme('colors.pink[900]'),
'--tw-prose-th-borders': theme('colors.pink[300]'),
'--tw-prose-td-borders': theme('colors.pink[200]'),
'--tw-prose-invert-body': theme('colors.pink[200]'), // for dark mode (class="prose dark:prose-invert")
// ...
'--tw-prose-invert-td-borders': theme('coloFormsrs.pink[700]'),,
}
},
// --- customize the raw CSS generated by 'typography' plugin:
DEFAULT: {
css: {
color: theme('colors.gray.800'), // access 'theme' helper
a: {Forms
color: '#3182ce',
'&:hover': { color: '#2c5282' },
} } }
}),
},
},
plugins: [
require('@tailwindcss/typography'),
// --- changing the default class name:
require('@tailwindcss/typography')({ className: 'wysiwyg' }),
// ...
],
}
// must be used with the base 'prose' class:
<article class="prose lg:prose-xl">
<h1> ... </h1>
<p> ... </p>
</article>
<article class="prose lg:prose-xl"> {{ markdown }} </article>
// --- choosing a gray scale
prose-gray // Gray (default)
prose-slate // Slate
prose-zinc // Zinc
prose-neutral // Neutral
prose-stone // Stone
// usage:
<article class="prose prose-slate"> {{ markdown }} </article>
// --- type scale, adjust the overall size of typography for different contexts
prose-sm // 0.875rem (14px)
prose-base // 1rem (16px) (default)
prose-lg // 1.125rem (18px)
prose-xl // 1.25rem (20px)
prose-2xl // 1.5rem (24px)
// must be used with the base 'prose' class:
<article class="prose md:prose-lg xl:prose-xl"> {{ markdown }} </article>
// --- dark mode:
<article class="prose dark:prose-invert"> {{ markdown }} </article>
// --- element modifiers
prose-headings:{utility} // h1, h2, h3, h4, th
prose-lead:{utility} // [class~="lead"]
prose-h1:{utility} // h1
prose-h2:{utility} // h2
prose-h3:{utility} // h3
prose-h4:{utility} // h4
prose-p:{utility} // p
prose-a:{utility} // a
prose-blockquote:{utility} // blockquote
prose-figure:{utility} // figure
prose-figcaption:{utility} // figcaption
prose-strong:{utility} // strong
prose-em:{utility} // em
prose-code:{utility} // code
prose-pre:{utility} // pre
prose-ol:{utility} // ol
prose-ul:{utility} // ul
prose-li:{utility} // li
prose-table:{utility} // tableForms
prose-thead:{utility} // thead
prose-tr:{utility} // tr
prose-th:{utility} // th
prose-td:{utility} // td
prose-img:{utility} // img
prose-video:{utility} // video
prose-hr:{utility} // hr
// usage:
<article class="prose prose-img:rounded-xl prose-headings:underline prose-a:text-blue-600"> {{ markdown }} </article>
// stacking with other modifiers:
<article class="prose prose-a:text-blue-600 hover:prose-a:text-blue-500"> {{ markdown }} </article>
// --- overriding max-width, add 'max-w-none' to override the embedded 'max-width':
<article class="prose max-w-none"> {{ markdown }} </article>
// --- undoing typography styles
<article class="prose">
<h1> ... </h1>
<p> ... </p>
<div class="not-prose"> prose-free block, nesting new 'prose' instances here is not available </div>
<p> ... </p>
</article>
@tailwindcss/forms (v0.5.7)
// form styles reset to a simple defaults that are easy to override with utilities
// install:
npm install -D @tailwindcss/forms
// tailwind.config.js
module.exports = {
theme: {
// ...
},
plugins: [
require('@tailwindcss/forms'),
// class generation strategy, if both the global (base) styles and classes doesnt work well
require("@tailwindcss/forms")({
// base strategy, form elements are styled globally, and no form-{name} classes are generated
strategy: 'base', // only generate global styles
// class strategy, form elements are not styled globally, and instead must be styled using the generated form-{name} classes
strategy: 'class', // only generate classes
}),
// ...
],
}
// customize padding on a select element:
<select class="px-4 py-3 rounded-full"> ... </select>
// change a checkbox color using 'text' color utilities:
<input type="checkbox" class="rounded text-pink-500" />
// list of supported form elements, and classes which can be used to explicitly apply the form styles to a non-form element
form-input // [type='text']
form-input // [type='password']
form-input // [type='email']
form-input // [type='number']
form-input // [type='url']
form-input // [type='date']
form-input // [type='datetime-local']
form-input // [type='month']
form-input // [type='week']
form-input // [type='time']
form-input // [type='search']
form-input // [type='tel']
form-checkbox //[type='checkbox']
form-radio // [type='radio']
form-select // select
form-multiselect // select[multiple]
form-textarea // textarea
// classes usage:
<input type="email" class="form-input px-4 py-3 rounded-full" />
<select class="form-select px-4 py-3 rounded-full"> ... </select>
<input type="checkbox" class="form-checkbox rounded text-pink-500" />
// --- EXAMPLES
<div class="py-12">
<h2 class="text-2xl font-bold">Simple</h2>
<div class="mt-8 max-w-md">
<div class="grid grid-cols-1 gap-6">
<label class="block">
<span class="text-gray-700">Full name</span>
<input type="text" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" placeholder="">
</label>
<label class="block">
<span class="text-gray-700">Email address</span>
<input type="email" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" placeholder="john@example.com">
</label>
<label class="block">
<span class="text-gray-700">When is your event?</span>
<input type="date" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
</label>
<label class="block">
<span class="text-gray-700">What type of event is it?</span>
<select class="block w-full mt-1 rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<option>Corporate event</option> <option>Wedding</option> <option>Birthday</option> <option>Other</option>
</select>
</label>
<label class="block">
<span class="text-gray-700">Additional details</span>
<textarea class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" rows="3"></textarea>
</label>
<div class="block">
<div class="mt-2">
<div>
<label class="inline-flex items-center">
<input type="checkbox" class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-offset-0 focus:ring-indigo-200 focus:ring-opacity-50" checked="">
<span class="ml-2">Email me news and special offers</spa// inherit;n>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="py-12">
<h2 class="text-2xl font-bold">Underline</h2>
<div class="mt-8 max-w-md">
<div class="grid grid-cols-1 gap-6">
<label class="block">
<span class="text-gray-700">Full name</span>
<input type="text" class="mt-0 block w-full px-0.5 border-0 border-b-2 border-gray-200 focus:ring-0 focus:border-black" placeholder="">
</label>
<label class="block">
<span class="text-gray-700">Email address</span>
<input type="email" class="mt-0 block w-full px-0.5 border-0 border-b-2 border-gray-200 focus:ring-0 focus:border-black" placeholder="john@example.com">
</label>
<label class="block">
<span class="text-gray-700">When is your event?</span>
<input type="date" class="mt-0 block w-full px-0.5 border-0 border-b-2 border-gray-200 focus:ring-0 focus:border-black">
</label>
<label class="block">
<span class="text-gray-700">What type of event is it?</span>
<select class="block w-full mt-0 px-0.5 border-0 border-b-2 border-gray-200 focus:ring-0 focus:border-black">
<option>Corporate event</option> <option>Wedding</option> <option>Birthday</option> <option>Other</option>
</select>
</label>
<label class="block">
<span class="text-gray-700">Additional details</span>
<textarea class="mt-0 block w-full px-0.5 border-0 border-b-2 border-gray-200 focus:ring-0 focus:border-black" rows="2"></textarea>
</label>
<div class="block">
<div class="mt-2">
<div>
<label class="inline-flex items-center">
<input type="checkbox" class="border-gray-300 border-2 text-black focus:border-gray-300 focus:ring-black">
<span class="ml-2">Email me news and special offers</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="py-12">
<h2 class="text-2xl font-bold">Solid</h2>
<div class="mt-8 max-w-md">
<div class="grid grid-cols-1 gap-6">
<label class="block">
<span class="text-gray-700">Full name</span>
<input type="text" class="mt-1 block w-full rounded-md bg-gray-100 border-transparent focus:border-gray-500 focus:bg-white focus:ring-0" placeholder="">
</label>
<label class="block">
<span class="text-gray-700">Email address</span>
<input type="email" class="mt-1 block w-full rounded-md bg-gray-100 border-transparent focus:border-gray-500 focus:bg-white focus:ring-0" placeholder="john@example.com">
</label>
<label class="block">
<span class="text-gray-700">When is your event?</span>
<input type="date" class="mt-1 block w-full rounded-md bg-gray-100 border-transparent focus:border-gray-500 focus:bg-white focus:ring-0">
</label>
<label class="block">
<span class="text-gray-700">What type of event is it?</span>
<select class="block w-full mt-1 rounded-md bg-gray-100 border-transparent focus:border-gray-500 focus:bg-white focus:ring-0">
<option>Corporate event</option> <option>Wedding</option> <option>Birthday</option> <option>Other</option>
</select>
</label>
<label class="block">
<span class="text-gray-700">Additional details</span>
<textarea class="mt-1 block w-full rounded-md bg-gray-100 border-transparent focus:border-gray-500 focus:bg-white focus:ring-0" rows="3"></textarea>
</label>
<div class="block">
<div class="mt-2">
<div>
<label class="inline-flex items-center">
<input type="checkbox" class="rounded bg-gray-200 border-transparent focus:border-transparent focus:bg-gray-200 text-gray-700 focus:ring-1 focus:ring-offset-2 focus:ring-gray-500">
<span class="ml-2">Email me news and special offers</span>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
@tailwindcss/aspect-ratio (v0.4.2)
// composable API for giving elements a fixed aspect ratio
// will be deprecated once the aspect-ratio property is supported in modern browsers in favor of official support in Tailwind CSS itself.
// install:
npm install -D @tailwindcss/aspect-ratio
// tailwind.config.js
module.exports = {
theme: {
// ...
},
corePlugins: {
aspectRatio: false, // disable the aspectRatio core plugin to avoid conflicts
},
plugins: [
require('@tailwindcss/aspect-ratio'),
// ...
],
}
// classes:
aspect-w-{1|...|16}
aspect-h-{1|...|16}
// combine the aspect-w-{n} and aspect-h-{n} classes to specify the aspect ratio for an element,
// assign the aspect ratio to a parent element, and make the actual element you are trying to size the only child of that parent:
<div class="aspect-w-16 aspect-h-9">
<iframe src="https://..." frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
// removing any aspect ratio behavior:
<div class="aspect-w-16 aspect-h-9 xl:aspect-none"> ... </div>
// when removing aspect ratio behavior, if nested elements have w-{n} or h-{n} classes,
// ensure they are re-declared with a matching breakpoint prefix:
<div class="aspect-w-16 aspect-h-9 xl:aspect-none">
<img src="..." alt="..." class="w-full h-full object-center object-cover lg:w-full lg:h-full" />
</div>
// tailwind.config.js
module.exports = {
theme: {
aspectRatio: {
1: '1',
2: '2',
3: '3',
4: '4',
}
},
variants: {
aspectRatio: ['responsive', 'hover']
}
}
// use this plugin to support Safari 14, where aspect-ratio CSS property isnt supported (native Tailwind utilities way).
// to use both approaches (transitioning, ...), overwrite default aspectRatio values by this plugin values,
// tailwind.config.js
module.exports = {
// ...
theme: {
aspectRatio: {
auto: 'auto',
square: '1 / 1',
video: '16 / 9',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
14: '14',
15: '15',
16: '16',
},
},
}
@tailwindcss/container-queries (v0.1.1)
// utilities for container queries
// install:
npm install @tailwindcss/container-queries
// tailwind.config.js
module.exports = {
theme: {
// ...
},
plugins: [
require('@tailwindcss/container-queries'),
// ...
],
}
// container sizes:
@xs // @container (min-width: 20rem /* 320px */)
@sm // @container (min-width: 24rem /* 384px */)
@md // @container (min-width: 28rem /* 448px */)
@lg // @container (min-width: 32rem /* 512px */)
@xl // @container (min-width: 36rem /* 576px */)
@2xl // @container (min-width: 42rem /* 672px */)
@3xl // @container (min-width: 48rem /* 768px */)
@4xl // @container (min-width: 56rem /* 896px */)
@5xl // @container (min-width: 64rem /* 1024px */)
@6xl // @container (min-width: 72rem /* 1152px */)
@7xl // @container (min-width: 80rem /* 1280px */)
// mark an element with @container class,
// and then applying styles based on the size of that container using the container variants:
<div class="@container">
<div class="@lg:underline"> this text will be underlined when the container is larger than '32rem' </div>
</div>
// removing a container:
<div class="@container xl:@container-normal"> ... </div>
// @container/{name} - named containers:
<div class="@container/main">
// ...
<div class="@lg/main:underline"> this text will be underlined when the 'main' container is larger than '32rem' </div>
</div>
// arbitrary container sizes:
<div class="@container">
<div class="@[17.5rem]:underline"> this text will be underlined when the 'main' container is larger than '17.5rem' </div>
</div>
// with a Tailwind prefix, prefix both the @container class and any classes where a container query modifier is used:
<div class="tw-@container">
// ...
<div class="@lg:tw-underline"> ... </div>
</div>
// tailwind.config.js > containers
module.exports = {
theme: {
extend: {
containers: {
'2xs': '16rem',
},
},
},
}