Tailwind CSS Core Patterns
# Overview
Tailwind CSS is a CSS framework that minimizes writing separate CSS files and lets you style quickly by applying utility classes and the like right in your HTML markup.
It also lets you handle other styling, such as responsive design, dark mode, and hover states, inside your HTML.
This is especially useful in frameworks like React, where you modularize CSS into separate files or use CSS-in-JS libraries such as Emotion or styled-components.
You can of course use Tailwind CSS in Vue and Svelte too, but since they let you include and modularize CSS in single-file components (SFC) such as *.vue and *.svelte without any extra library, it may be relatively less useful there.
Also, unlike typical CSS frameworks such as Bootstrap, Tailwind CSS doesn't provide predefined styled components by default.
Instead, you build the design you want by combining small utility classes directly in your HTML.
This way, only the styles you use end up in the final build, which reduces the bundle size even further.
In the following example, the two elements use an inline style and utility classes, and both produce the same result.
12<div style={{ height: '100px', width: '100px', backgroundColor: 'red' }}></div> <div className="h-[100px] w-[100px] bg-[red]"></div>
Tailwind CSS also provides predefined class values to help you write optimized styles.
However, these values are less intuitive, and you need to learn their patterns to use them effectively.
So this post analyzes and introduces the main patterns you will use most often.
12<div style={{ height: '100px', width: '100px', backgroundColor: 'red' }}></div> <div className="h-25 w-25 bg-red-500"></div>
Tailwind CSS makes it easy to handle state-based styles with something called variants.
First, with plain inline styles you can't handle a hover effect, so you have to write separate CSS like the .box class definition below.
12345678.box { width: 100px; height: 100px; background-color: red; } .box:hover { background-color: blue; }
With Tailwind CSS, however, you can handle the hover effect easily with the hover: variant.
In the following example, the bg-blue-500 utility class is applied only in the hover state.
12<div className="box"></div> <div className="h-25 w-25 bg-red-500 hover:bg-blue-500"></div>
If you need to reuse styles, you can abstract them with the @utility directive as follows.
As in the example below, you can write properties and values, or use utility classes with the @apply directive.
1234567891011@utility box1 { width: 100px; height: 100px; background-color: red; &:hover { background-color: blue; } } @utility box2 { @apply w-25 h-25 bg-red-500 hover:bg-blue-500; }
12<div className="box1"></div> <div className="box2"></div>
# Installation and setup
# VS Code extensions
There are several Tailwind CSS extensions you can use in VS Code.
# Tailwind CSS IntelliSense
Tailwind CSS IntelliSense is the official extension, offering features such as autocomplete, syntax highlighting, and linting.
When you type part of a class, it autocompletes a list of matching utility classes.
You can pick the class you want from the list and apply it easily.
When you hover over an applied utility class, it shows the CSS that actually gets applied.
This makes it easy to understand styles that are already written.
# Tailwind Docs
Tailwind Docs lets you search the Tailwind CSS documentation easily.
In VS Code, open the Command Palette (Cmd or Ctrl + Shift + P) and type tailwind <search term>.
In the search results, select the style you want to apply to see how to use its utility classes right away.
The documentation page opens inside VS Code instead of a separate browser.
# Vite
In a Vite project, install and set up Tailwind CSS as follows.
If you don't use Prettier, skip installing prettier-plugin-tailwindcss.
1npm i -D tailwindcss @tailwindcss/vite prettier-plugin-tailwindcss
12345678import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tailwindcss()] })
Set up Tailwind CSS in the global stylesheet connected to /src/main.tsx as follows.
Earlier versions used the @tailwind directive, but starting with v4, you use the CSS @import rule.
1234/* @tailwind base; @tailwind components; @tailwind utilities; */ @import "tailwindcss";
If you use Prettier, register the plugin as follows.
12345{ "semi": false, "singleQuote": true, "plugins": ["prettier-plugin-tailwindcss"] }
# Removing non-standard rule warnings
When you use Tailwind CSS directives such as @theme and @apply, your CSS or SCSS files may show warnings.
If you use the Tailwind CSS IntelliSense extension in VS Code, you can remove the warnings by adding the following to your settings.json file.
123456{ "files.associations": { "*.css": "tailwindcss", "*.scss": "tailwindcss" } }
# Next
When you create a new project with the latest Next, you don't need to set up Tailwind CSS separately if you choose the option to use it.
For an existing Next project, see Starting with the new Tailwind CSS v4 in a Next.js project.
12345678npx create-next-app@latest <project-name> ✔ Would you like to use TypeScript? … Yes # Use TypeScript? ✔ Would you like to use ESLint? … Yes # Use ESLint? ✔ Would you like to use Tailwind CSS? … Yes # Use Tailwind CSS? ✔ Would you like your code inside a `src/` directory? … No # Use the src/ directory? ✔ Would you like to use App Router? (recommended) … Yes # Use the App Router? ✔ Would you like to use Turbopack for next dev? … No # Use Turbopack? ✔ Would you like to customize the import alias (@/* by default)? … No # Use a path alias other than `@/*`?
If you use Prettier, install and register the plugin as follows, just like in the Vite setup.
Removing non-standard rule warnings works the same way, so see the Vite setup described earlier.
1npm i -D prettier-plugin-tailwindcss
12345{ "semi": false, "singleQuote": true, "plugins": ["prettier-plugin-tailwindcss"] }
# Utility classes
A Tailwind CSS utility class is a small CSS class that holds one or more CSS properties and values, and it lets you apply CSS properties easily.
# Understanding the common patterns
Most utility classes map directly to a single CSS property, such as w-* for width and m-* for margin, but some classes combine several properties, like size-*, which combines width and height.
On top of that, the naming patterns aren't consistent: the flex-basis property uses the basis-* class, but the flex-wrap property uses the flex-* class. The usage patterns vary too: the number in the px-2 class is multiplied by the base spacing (--spacing), but the number in the border-1 class is applied in px. So there is a bit of a learning curve.
To make this easier to understand, I've summarized the common patterns of utility classes.
Once you understand the common patterns, check out the table of properties and utility classes.
# Basic usage
The most common pattern combines a CSS property prefix and that property's CSS value with a dash (-).
For example, you take the CSS float: left; property and value and join them as is to write the float-left class.
For the box-sizing: border-box; property and value, you have to shorten the property name to box-*, as in the box-border class.
Also, the abbreviations aren't consistent (width becomes w-*, text-indent becomes indent-*, and border-radius becomes rounded-*), so be careful when using them.
Be careful not to write an invalid class, such as text-indent-1 instead of indent-1.
1<div className="float-left box-border w-10 rounded-sm indent-1"></div>
# Special values
There is also a pattern that combines a property prefix with special values such as predefined keywords, numbers, and ratios.
For example, you write the aspect-ratio: 16/9; property and value as a class with a ratio, aspect-16/9, or with a keyword, aspect-video, and you write width: 100%; as the w-full class.
Some classes, like columns-*, accept a size name as the value, as in columns-md.
There are five basic size names (xs, sm, md, lg, xl), and you can put a number of 2 or higher in front of the smallest (xs) or largest (xl) size to express even smaller or larger values, such as 3xs, 2xs, 6xl, and 7xl.
Also, for some CSS properties that accept negative values, you can add a minus sign (-) in front of the class.
For example, you write order: -1; as the -order-1 class.
1<div className="-order-1 -m-4 aspect-square columns-md"></div>
# Value-only classes
There is a pattern that uses only the value as the class, without a property prefix.
For example, you write the display: block property and value as the block class, not display-block.
1<span className="visible absolute block"></span>
# Using variables
You can add parentheses (()) to the property prefix to specify a variable without the var() function.
For example, you can write the width: var(--size); property and value as the w-(--size) class.
You can define a local variable like [--size:200px], using the arbitrary values approach covered in the next topic.
1<div className="h-[calc(var(--size)+50px)] w-(--size) [--size:200px]"></div>
# Arbitrary values
You can add square brackets ([]) to the property prefix to specify an arbitrary value that isn't predefined.
For example, you can write the width: 123px; property and value as the w-[123px] class.
You can also use CSS functions, as in w-[calc(100%-40px)].
Note that whitespace (such as spaces) in the middle, as in 100% - 40px, is not allowed.
In addition, you can write a property and value as is inside [].
You can use this freely, for example to declare a local variable or to apply a property that Tailwind CSS doesn't support.
1<div className="h-[calc(var(--size)+50px)] w-[var(--size)] [--size:200px]"></div>
1<input className="[width:200px] text-[#ff0000] [-webkit-text-security:circle]" />
# Colors
Tailwind CSS includes a color palette by default.
You write classes with the PREFIX-COLOR-SHADE pattern, like bg-red-500 and text-blue-300.
There are 22 color names you can use, from Red to Stone.
For shades, there are 9 basic steps from 100 to 900 in increments of 100, plus the lightest 50 and the darkest 950, for a total of 11.
The idea is that 500 in the middle is the base color, and you lower the number for a lighter color or raise it for a darker one.
White and black have no shades, so you write them as bg-white and border-black.
If you want to make a color transparent, add an opacity from 0 to 100 at the end of the class, like bg-red-500/70 or bg-black/15.
Note that 100 is fully opaque and 0 is fully transparent.
123<div className="size-[100px] border-2 border-blue-700/90 bg-red-500/50 text-pink-50"> Colors </div>
Also, every utility class that applies a color can additionally use the *-inherit, *-current, and *-transparent classes.
They let you inherit the parent's color (inherit), explicitly use the current color (currentColor), or make it transparent (transparent).
123<div className="bg-inherit"> background-color: inherit; </div> <div className="accent-current"> accent-color: currentColor; </div> <div className="border-transparent"> border-color: transparent; </div>
# Properties and utility classes table
Based on the common patterns we just looked at, here is a list of each property and its utility classes.
Press Cmd or Ctrl + F in your browser to search, and you can find a property or class name more easily.
For details, you can select a CSS property name to go to the matching Tailwind CSS documentation.
In each table, (Variable) shows whether you can use a CSS variable, like w-(--size).[Arbitrary] shows whether you can specify an arbitrary value, like w-[100px].
# Layout
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| aspect-ratio | aspect-RATIO aspect-square aspect-video aspect-auto |
1 / 1 16 / 9 auto |
O | O |
| columns | columns-NUMBER columns-3xs to 7xl columns-auto |
auto |
O | O |
| break-after | break-after-auto break-after-avoid break-after-all break-after-avoid-page break-after-page break-after-left break-after-right break-after-column |
auto avoid all avoid-page page left right column |
||
| break-before | break-before-auto break-before-avoid break-before-all break-before-avoid-page break-before-page break-before-left break-before-right break-before-column |
auto avoid all avoid-page page left right column |
||
| break-inside | break-inside-auto break-inside-avoid break-inside-avoid-page break-inside-avoid-column |
auto avoid avoid-page avoid-column |
||
| box-decoration-break | box-decoration-clone box-decoration-slice |
clone slice |
||
| box-sizing | box-border box-content |
border-box content-box |
||
| display | inline block inline-block ... hidden sr-only not-sr-only |
inline block inline-block ... none |
||
| float | float-right float-left float-start float-end float-none |
right left inline-start inline-end none |
||
| clear | clear-left clear-right clear-both clear-start clear-end clear-none |
left right both inline-start inline-end none |
||
| isolation | isolate isolation-auto |
isolate auto |
||
| object-fit | object-contain object-cover object-fill object-none object-scale-down |
contain cover fill none scale-down |
||
| object-position | object-bottom object-center object-left object-left-bottom object-left-top object-right object-right-bottom object-right-top object-top |
bottom center left left bottom left top right right bottom right top top |
O | O |
| overflow | overflow-auto overflow-hidden overflow-clip overflow-visible overflow-scroll overflow-x-auto overflow-y-auto overflow-x-hidden overflow-y-hidden overflow-x-clip overflow-y-clip overflow-x-visible overflow-y-visible overflow-x-scroll overflow-y-scroll |
auto hidden clip visible scroll auto auto hidden hidden clip clip visible visible scroll scroll |
||
| overscroll-behavior | overscroll-auto overscroll-contain overscroll-none overscroll-x-auto overscroll-x-contain overscroll-x-none overscroll-y-auto overscroll-y-contain overscroll-y-none |
auto contain none auto contain none auto contain none |
||
| position | static fixed absolute relative sticky |
static fixed absolute relative sticky |
||
| top / right / bottom / left | inset-* inset-x-* inset-y-* start-* end-* top-* right-* bottom-* left-* *-NUMBER *-FRACTION *-px *-full *-auto |
0.25rem * NUMBER 100% * FRACTION 1px 100% auto |
O | O |
| visibility | visible invisible collapse |
visible hidden collapse |
||
| z-index | z-NUMBER z-auto |
auto |
O | O |
# Flex & grid
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| flex-basis | basis-NUMBER basis-FRACTION basis-full basis-auto basis-3xs to 7xl |
0.25rem * NUMBER 100% * FRACTION 100% auto |
O | O |
| flex-direction | flex-row flex-row-reverse flex-col flex-col-reverse |
row row-reverse column column-reverse |
||
| flex-wrap | flex-nowrap flex-wrap flex-wrap-reverse |
nowrap wrap wrap-reverse |
||
| flex | flex-NUMBER flex-FRACTION flex-auto flex-initial flex-none |
100% * FRACTION 1 1 auto 0 1 auto none |
O | O |
| flex-grow | grow grow-NUMBER |
1 | O | O |
| flex-shrink | shrink shrink-NUMBER |
1 | O | O |
| order | order-NUMBER order-first order-last order-none |
-infinity infinity 0 |
O | O |
| grid-template-columns | grid-cols-NUMBER grid-cols-none grid-cols-subgrid |
repeat(NUMBER, minmax(0, 1fr)) none subgrid |
O | O |
| grid-column | col-span-NUMBER col-span-full col-start-NUMBER col-start-auto col-end-NUMBER col-end-auto col-auto col-NUMBER |
span NUMBER / span NUMBER 1 / -1 auto auto auto |
O | O |
| grid-template-rows | grid-rows-NUMBER grid-rows-none grid-rows-subgrid |
repeat(NUMBER, minmax(0, 1fr)) none subgrid |
O | O |
| grid-row | row-span-NUMBER row-span-full row-start-NUMBER row-start-auto row-end-NUMBER row-end-auto row-auto row-NUMBER |
span NUMBER / span NUMBER 1 / -1 auto auto auto |
O | O |
| grid-auto-flow | grid-flow-row grid-flow-col grid-flow-dense grid-flow-row-dense grid-flow-col-dense |
row column dense row dense column dense |
||
| grid-auto-columns | auto-cols-auto auto-cols-min auto-cols-max auto-cols-fr |
auto min-content max-content minmax(0, 1fr) |
O | O |
| grid-auto-rows | auto-rows-auto auto-rows-min auto-rows-max auto-rows-fr |
auto min-content max-content minmax(0, 1fr) |
O | O |
| gap | gap-NUMBER gap-x-NUMBER gap-y-NUMBER |
0.25rem * NUMBER | O | O |
| justify-content | justify-start justify-end justify-center justify-between justify-around justify-evenly justify-stretch justify-baseline justify-normal |
flex-start flex-end center space-between space-around space-evenly stretch baseline normal |
||
| justify-items | justify-items-start justify-items-end justify-items-center justify-items-stretch justify-items-normal |
start end center stretch normal |
||
| justify-self | justify-self-auto justify-self-start justify-self-end justify-self-center justify-self-stretch |
auto start end center stretch |
||
| align-content | content-normal content-center content-start content-end content-between content-around content-evenly content-baseline content-stretch |
normal center flex-start flex-end space-between space-around space-evenly baseline stretch |
||
| align-items | items-start items-end items-center items-baseline items-stretch |
flex-start flex-end center baseline stretch |
||
| align-self | self-auto self-start self-end self-center self-stretch self-baseline |
auto flex-start flex-end center stretch baseline |
||
| place-content | place-content-center place-content-start place-content-end place-content-between place-content-around place-content-evenly place-content-baseline place-content-stretch |
center start end space-between space-around space-evenly baseline stretch |
||
| place-items | place-items-start place-items-end place-items-center place-items-baseline place-items-stretch |
start end center baseline stretch |
||
| place-self | place-self-auto place-self-start place-self-end place-self-center place-self-stretch |
auto start end center stretch |
# Spacing
space-* means the margin between elements, and you apply it to the parent element, not to the target elements.
The following example adds 8px (0.5rem) of margin between each <li>.
No margin is added above the first <li> or below the last <li>.
12345<ul className="space-y-2"> <li>Apple</li> <li>Banana</li> <li>Cherry</li> </ul>
# Sizing
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| width | w-* w-screen size-* *-3xs to 7xl *-NUMBER *-FRACTION *-auto *-px *-full *-dvw *-dvh *-lvw *-lvh *-svw *-svh *-min *-max *-fit |
100vw 0.25rem * NUMBER 100% * FRACTION auto 1px 100% 100dvw 100dvh 100lvw 100lvh 100svw 100svh min-content max-content fit-content |
O | O |
| min-width | min-w-* *-(same as above) |
O | O | |
| max-width | max-w-* *-(same as above) |
O | O | |
| height | h-* h-screen size-* *-(same as above) |
100vh |
O | O |
| min-height | min-h-* *-(same as above) |
O | O | |
| max-height | max-h-* *-(same as above) |
O | O |
# Fonts & text
When you set the font size with a text-* class, you can also set the line height (line-height) by adding a /.
12<div className="text-sm">font-size: 14px;</div> <div className="text-sm/6">font-size: 14px; line-height: 1.5;</div>
The CSS content property can be used in the ::before and ::after pseudo-element selectors, so you use content-* classes with the before: and after: variants.
When you write text as the value, you must use _ instead of spaces, and conversely, to use an actual _, you need to escape it.
Note that inside the url() function, you don't need to escape it.
123<div className="before:content-['Have_a_great_day!']">Have a great day!</div> <div className="before:content-['Have\_a\_great\_day!']">Have_a_great_day!</div> <div className="before:content-[url('https://www.heropy.dev/images/profile_m.jpg')]">HEROPY</div>
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| font-family | font-sans font-serif font-mono |
var(--font-sans) var(--font-serif) var(--font-mono) |
O | O |
| font-size | text-xs to 9xl text-base |
1rem |
O | O |
| font-smoothing | antialiased subpixel-antialiased |
antialiased auto |
||
| font-style | italic not-italic |
italic normal |
||
| font-weight | font-thin font-extralight font-light font-normal font-medium font-semibold font-bold font-extrabold font-black |
100 200 300 400 500 600 700 800 900 |
O | O |
| font-stretch | font-stretch-ultra-condensed font-stretch-extra-condensed font-stretch-condensed font-stretch-semi-condensed font-stretch-normal font-stretch-semi-expanded font-stretch-expanded font-stretch-extra-expanded font-stretch-ultra-expanded font-stretch-PERCENT |
ultra-condensed (50%) extra-condensed (62.5%) condensed (75%) semi-condensed (87.5%) normal (100%) semi-expanded (112.5%) expanded (125%) extra-expanded (150%) ultra-expanded (200%) |
O | O |
| font-variant-numeric | normal-nums ordinal slashed-zero lining-nums oldstyle-nums proportional-nums tabular-nums diagonal-fractions stacked-fractions |
normal ordinal slashed-zero lining-nums oldstyle-nums proportional-nums tabular-nums diagonal-fractions stacked-fractions |
||
| letter-spacing | tracking-tighter tracking-tight tracking-normal tracking-wide tracking-wider tracking-widest |
-0.05em -0.025em 0em 0.025em 0.05em 0.1em |
O | O |
| line-clamp | line-clamp-NUMBER line-clamp-none |
unset |
O | O |
| line-height | leading-none leading-NUMBER |
0.25rem * NUMBER |
O | O |
| list-style-image | list-image-none | O | O | |
| list-style-position | list-inside list-outside |
inside outside |
||
| list-style-type | list-disc list-decimal list-none |
disc decimal none |
O | O |
| text-align | text-left text-center text-right text-justify text-start text-end |
left center right justify start end |
||
| color | text-COLOR | O | O | |
| text-decoration-line | underline overline line-through no-underline |
underline overline line-through none |
||
| text-decoration-color | decoration-COLOR | O | O | |
| text-decoration-style | decoration-solid decoration-double decoration-dotted decoration-dashed decoration-wavy |
solid double dotted dashed wavy |
||
| text-decoration-thickness | decoration-NUMBER decoration-from-font decoration-auto |
NUMBERpx from-font auto |
O | O |
| text-underline-offset | underline-offset-NUMBER underline-offset-auto |
NUMBERpx auto |
O | O |
| text-transform | uppercase lowercase capitalize normal-case |
uppercase lowercase capitalize none |
||
| text-overflow | truncate text-ellipsis text-clip |
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ellipsis clip |
||
| text-wrap | text-wrap text-nowrap text-balance text-pretty |
wrap nowrap balance pretty |
||
| text-indent | indent-NUMBER indent-px |
0.25rem * NUMBER 1px |
O | O |
| vertical-align | align-baseline align-top align-middle align-bottom align-text-top align-text-bottom align-sub align-super |
baseline top middle bottom text-top text-bottom sub super |
O | O |
| white-space | whitespace-normal whitespace-nowrap whitespace-pre whitespace-pre-line whitespace-pre-wrap whitespace-break-spaces |
normal nowrap pre pre-line pre-wrap break-spaces |
||
| word-break | break-normal break-words break-all break-keep |
overflow-wrap: normal word-break: normal break-word break-all keep-all |
||
| hyphens | hyphens-none hyphens-manual hyphens-auto |
none manual auto |
||
| content | content-none | none | O | O |
# Backgrounds
To set a gradient on an element's background, you can use the from-*, via-*, and to-* classes to set the colors of the start, middle, and end points.
The gradient in the following example starts with red on the left, passes through yellow, and ends with blue on the right.
1<div className="h-20 w-full bg-linear-to-r from-red-500 via-yellow-500 to-blue-500"></div>
The from-*, via-*, and to-* classes can set not only the color of each point but also its stop position.
In the following example, red stops at 10%, yellow at 30%, and blue at 50%, and the colors in between blend naturally.
1<div className="h-20 w-full bg-linear-to-r from-red-500 from-10% via-yellow-500 via-30% to-blue-500 to-50%"></div>
Note that in CSS, an element's background color uses the background-color property, but a background gradient has to use the background-image property.
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| background-attachment | bg-fixed bg-local bg-scroll |
fixed local scroll |
||
| background-clip | bg-clip-border bg-clip-padding bg-clip-content bg-clip-text |
border-box padding-box content-box text |
||
| background-color | bg-COLOR | O | O | |
| background-image | bg-none bg-linear-to-t bg-linear-to-tr bg-linear-to-r bg-linear-to-br bg-linear-to-b bg-linear-to-bl bg-linear-to-l bg-linear-to-tl bg-linear-ANGLE bg-radial bg-conic-ANGLE from-COLOR from-PERCENT via-COLOR via-PERCENT to-COLOR to-PERCENT |
none linear-gradient(to top) linear-gradient(to top right) linear-gradient(to right) linear-gradient(to bottom right) linear-gradient(to bottom) linear-gradient(to bottom left) linear-gradient(to left) linear-gradient(to top left) linear-gradient(ANGLE) radial-gradient(in oklab) conic-gradient(from ANGLE in oklab) |
O | O |
| background-origin | bg-origin-border bg-origin-padding bg-origin-content |
border-box padding-box content-box |
||
| background-position | bg-bottom bg-center bg-left bg-left-bottom bg-left-top bg-right bg-right-bottom bg-right-top bg-top |
bottom center left left bottom left top right right bottom right top top |
O | O |
| background-repeat | bg-repeat bg-repeat-x bg-repeat-y bg-repeat-space bg-repeat-round bg-no-repeat |
repeat repeat-x repeat-y space round no-repeat |
||
| background-size | bg-auto bg-cover bg-contain |
auto cover contain |
O | O |
# Borders
divide-* means the line (border) between elements, and you apply it to the parent element, not to the target elements.
The following example adds a line between each <li>.
No line appears above the first <li> or below the last <li>.
12345<ul className="divide-y-2"> <li>Apple</li> <li>Banana</li> <li>Cherry</li> </ul>
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| border-radius | rounded-* rounded-s-* rounded-e-* rounded-t-* rounded-r-* rounded-b-* rounded-l-* rounded-ss-* rounded-se-* rounded-ee-* rounded-es-* rounded-tl-* rounded-tr-* rounded-br-* rounded-bl-* *-xs to *-3xl *-none *-full |
start-start (top left) + end-start (bottom left) start-end (top right) + end-end (bottom right) top-left + top-right top-right + bottom-right bottom-right + bottom-left top-left + bottom-left start-start start-end end-end end-start top-left top-right bottom-right bottom-left 0.125rem (2px) 1.5rem (24px) 0 infinity px |
O | O |
| border-width | border border-NUMBER border-x border-x-NUMBER border-y border-y-NUMBER border-s border-s-NUMBER border-e border-e-NUMBER border-t border-t-NUMBER border-r border-r-NUMBER border-b border-b-NUMBER border-l border-l-NUMBER divide-x divide-x-NUMBER divide-y divide-y-NUMBER divide-x-reverse divide-y-reverse |
1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx 1px NUMBERpx |
O | O |
| border-color | border-COLOR border-x-COLOR border-y-COLOR border-s-COLOR border-e-COLOR border-t-COLOR border-r-COLOR border-b-COLOR border-l-COLOR divide-COLOR |
O | O | |
| border-style | border-solid border-dashed border-dotted border-double border-hidden border-none divide-solid divide-dashed divide-dotted divide-double divide-hidden divide-none |
solid dashed dotted double hidden none |
||
| outline-width | outline outline-NUMBER |
1px NUMBERpx |
O | O |
| outline-color | outline-COLOR | O | O | |
| outline-style | outline-solid outline-dashed outline-dotted outline-double outline-none outline-hidden |
solid dashed dotted double none outline: 2px solid transparent; outline-offset: 2px; |
||
| outline-offset | outline-offset-NUMBER | NUMBERpx | O | O |
# Effects
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| box-shadow | shadow-2xs to 2xl shadow-none shadow-COLOR inset-shadow-2xs to sm inset-shadow-none inset-shadow-COLOR ring ring-NUMBER ring-COLOR inset-ring inset-ring-NUMBER inset-ring-COLOR |
none none 0 0 0 1px 0 0 0 NUMBERpx inset 0 0 0 1px inset 0 0 0 NUMBERpx |
O | O |
| opacity | opacity-NUMBER | NUMBER% | O | O |
| mix-blend-mode | mix-blend-normal mix-blend-multiply mix-blend-screen mix-blend-overlay mix-blend-darken mix-blend-lighten mix-blend-color-dodge mix-blend-color-burn mix-blend-hard-light mix-blend-soft-light mix-blend-difference mix-blend-exclusion mix-blend-hue mix-blend-saturation mix-blend-color mix-blend-luminosity mix-blend-plus-darker mix-blend-plus-lighter |
normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue saturation color luminosity plus-darker plus-lighter |
||
| background-blend-mode | bg-blend-normal bg-blend-multiply bg-blend-screen bg-blend-overlay bg-blend-darken bg-blend-lighten bg-blend-color-dodge bg-blend-color-burn bg-blend-hard-light bg-blend-soft-light bg-blend-difference bg-blend-exclusion bg-blend-hue bg-blend-saturation bg-blend-color bg-blend-luminosity |
normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue saturation color luminosity |
# Filters
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| filter | filter-none blur-xs to 3xl blur-none brightness-NUMBER contrast-NUMBER drop-shadow-xs to 2xl drop-shadow-none grayscale grayscale-NUMBER hue-rotate-NUMBER invert invert-NUMBER saturate-NUMBER sepia sepia-NUMBER |
none none brightness(NUMBER%) contrast(NUMBER%) drop-shadow(0 0 #0000) grayscale(100%) grayscale(NUMBER%) hue-rotate(NUMBERdeg) invert(100%) invert(NUMBER%) saturate(NUMBER%) sepia(100%) sepia(NUMBER%) |
O | O |
| backdrop-filter | backdrop-filter-none backdrop-blur-xs to 3xl backdrop-blur-none backdrop-brightness-NUMBER backdrop-contrast-NUMBER backdrop-grayscale backdrop-grayscale-NUMBER backdrop-hue-rotate-NUMBER backdrop-invert backdrop-invert-NUMBER backdrop-opacity-NUMBER backdrop-saturate-NUMBER backdrop-sepia backdrop-sepia-NUMBER |
none none brightness(NUMBER%) contrast(NUMBER%) grayscale(100%) grayscale(NUMBER%) hue-rotate(NUMBERdeg) invert(100%) invert(NUMBER%) opacity(NUMBER%) saturate(NUMBER%) sepia(100%) sepia(NUMBER%) |
O | O |
# Tables
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| border-collapse | border-collapse border-separate |
collapse separate |
||
| border-spacing | border-spacing-NUMBER border-spacing-x-NUMBER border-spacing-y-NUMBER |
O | O | |
| table-layout | table-auto table-fixed |
auto fixed |
||
| caption-side | caption-top caption-bottom |
top bottom |
# Transitions & animation
You can create your own animation utility classes by defining --animate-* theme variables inside the @theme directive.
This is useful for defining an animation you'll reuse many times.
1234567891011@theme { --animate-my-animation: my-keyframes 1s infinite alternate; @keyframes my-keyframes { 0% { transform: translateX(0); } 100% { transform: translateX(100px); } } }
1<div className="animate-my-animation"></div>
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| transition-property | transition transition-all transition-colors transition-opacity transition-shadow transition-transform transition-none |
colors + opacity + shadow + transform + filters all |
O | O |
| transition-behavior | transition-normal transition-discrete |
normal allow-discrete |
||
| transition-duration | duration-NUMBER duration-initial |
NUMBERms initial |
O | O |
| transition-timing-function | ease-linear ease-in ease-out ease-in-out ease-initial |
linear cubic-bezier(0.4, 0, 1, 1) cubic-bezier(0, 0, 0.2, 1) cubic-bezier(0.4, 0, 0.2, 1) initial |
O | O |
| transition-delay | delay-NUMBER | NUMBERms | O | O |
| animation | animate-spin animate-ping animate-pulse animate-bounce animate-none |
spin 1s linear infinite ping 1s cubic-bezier(0, 0, 0.2, 1) infinite pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite bounce 1s infinite none |
O | O |
# Transforms
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| backface-visibility | backface-hidden backface-visible |
hidden visible |
||
| perspective | perspective-dramatic perspective-near perspective-normal perspective-midrange perspective-distant perspective-none |
100px 300px 500px 800px 1200px none |
O | O |
| perspective-origin | perspective-origin-center perspective-origin-top perspective-origin-top-right perspective-origin-right perspective-origin-bottom-right perspective-origin-bottom perspective-origin-bottom-left perspective-origin-left perspective-origin-top-left |
center top top right right bottom right bottom bottom left left top left |
O | O |
| rotate | rotate-none rotate-NUMBER rotate-x-NUMBER rotate-y-NUMBER rotate-z-NUMBER |
none NUMBERdeg |
O | O |
| scale | scale-none scale-NUMBER scale-x-NUMBER scale-y-NUMBER scale-z-NUMBER scale-3d |
none NUMBER% NUMBER% NUMBER% 1 1 NUMBER% 1 1 NUMBER% 1 1 1 |
O | O |
| skew | skew-NUMBER skew-x-NUMBER skew-y-NUMBER |
skewX(NUMBERdeg) skewY(NUMBERdeg) skewX(NUMBERdeg) skewY(NUMBERdeg) |
O | O |
| transform | transform-none transform-gpu transform-cpu |
none translateZ(0) |
O | O |
| transform-origin | origin-center origin-top origin-top-right origin-right origin-bottom-right origin-bottom origin-bottom-left origin-left origin-top-left |
center top top right right bottom right bottom bottom left left top left |
O | O |
| transform-style | transform-3d transform-flat |
preserve-3d flat |
||
| translate | translate-* translate-x-* translate-y-* translate-z-* translate-none *-NUMBER *-FRACTION *-full *-px |
0.25rem * NUMBER 100% * FRACTION 100% 100% 1px 1px |
O | O |
# Interactivity
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| accent-color | accent-COLOR | O | O | |
| appearance | appearance-none appearance-auto |
none auto |
||
| caret-color | caret-COLOR | O | O | |
| color-scheme | scheme-normal scheme-dark scheme-light scheme-light-dark scheme-only-dark scheme-only-light |
normal dark light light dark only dark only light |
||
| cursor | cursor-auto cursor-default cursor-pointer cursor-wait ... |
auto default pointer wait ... |
O | O |
| field-sizing | field-sizing-fixed field-sizing-content |
fixed content |
||
| pointer-events | pointer-events-auto pointer-events-none |
auto none |
||
| resize | resize-none resize resize-x resize-y |
none both horizontal vertical |
||
| scroll-behavior | scroll-auto scroll-smooth |
auto smooth |
||
| scroll-margin | scroll-m-* scroll-mx-* scroll-my-* scroll-ms-* scroll-me-* scroll-mt-* scroll-mr-* scroll-mb-* scroll-ml-* *-NUMBER |
0.25rem * NUMBER |
O | O |
| scroll-padding | scroll-p-* scroll-px-* scroll-py-* scroll-ps-* scroll-pe-* scroll-pt-* scroll-pr-* scroll-pb-* scroll-pl-* *-NUMBER |
0.25rem * NUMBER |
O | O |
| scroll-snap-align | snap-start snap-end snap-center snap-align-none |
start end center none |
||
| scroll-snap-stop | snap-normal snap-always |
normal always |
||
| scroll-snap-type | snap-none snap-x snap-y snap-both snap-mandatory snap-proximity |
none x proximity y proximity both proximity mandatory proximity |
||
| touch-action | touch-auto touch-none touch-pan-x touch-pan-left touch-pan-right touch-pan-y touch-pan-up touch-pan-down touch-pinch-zoom touch-manipulation |
auto none pan-x pan-left pan-right pan-y pan-up pan-down pinch-zoom manipulation |
||
| user-select | select-none select-text select-all select-auto |
none text all auto |
||
| will-change | will-change-auto will-change-scroll will-change-contents will-change-transform |
auto scroll-position contents transform |
O | O |
# SVG
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| fill | fill-none fill-COLOR |
none | O | O |
| stroke | stroke-none stroke-COLOR |
none | O | O |
| stroke-width | stroke-NUMBER | O | O |
# Accessibility
| CSS property | Utility class | Style or value | (Variable) | [Arbitrary] |
|---|---|---|---|---|
| forced-color-adjust | forced-color-adjust-auto forced-color-adjust-none |
auto none |
# Variants
Every Tailwind CSS utility class can be applied conditionally through variants.
A typical example is the hover: variant, which applies styles when you hover over an element.
In the following example, the blue background is applied only on hover.
1<div className="size-25 bg-red-500 hover:bg-blue-500"></div>
You can also nest variants.
In the following example, the first <div> contains a <span>, so the blue background is applied on hover, but the second <div> has no <span>, so the blue background is not applied even when you hover over it.
1234<div className="size-25 bg-red-500 hover:has-[span]:bg-blue-500"> <span>1</span> </div> <div className="size-25 bg-red-500 hover:has-[span]:bg-blue-500">2</div>
# Understanding the main variants
# Dark mode
Many operating systems, such as Windows and macOS, let you choose light or dark mode, and the choice is automatically applied to the browser as well.
You can detect dark mode with the prefers-color-scheme feature of the CSS @media rule.
1234567@media (prefers-color-scheme: dark) { html { color-scheme: dark; background-color: oklch(0.13 0.028 261.692); /* bg-gray-900 */ color: #fff; } }
With Tailwind CSS, you only need to specify which styles to use in dark mode with the dark: variant.
1<input className="border-1 border-gray-300 dark:border-gray-600" />
If you want to control dark mode manually instead of automatically, you can use the @custom-variant directive as follows.
The dark: variant then works when an element has the dark class itself or is a descendant of such an element.
123@import "tailwindcss"; @custom-variant dark (&:where(.dark, .dark *));
Now add the dark class manually.
To control the whole document, you usually add it to <html>.
1234<!doctype html> <html lang="ko" class="dark"> <!-- ... --> </html>
Adding the dark class doesn't automatically apply dark mode styles.
If there are styles you want, you have to write them yourself.
12345html.dark { color-scheme: dark; background-color: oklch(0.13 0.028 261.692); /* bg-gray-900 */ color: #fff; }
# Media queries
Tailwind CSS provides five breakpoints from sm to 2xl, and besides the usual mobile-first breakpoints, it also provides the opposite, a desktop-first system.
For desktop-first development, you use the max-* variants.
| Variant | CSS | Width (px) |
|---|---|---|
sm |
@media (width >= 40rem) |
640px |
md |
@media (width >= 48rem) |
768px |
lg |
@media (width >= 64rem) |
1024px |
xl |
@media (width >= 80rem) |
1280px |
2xl |
@media (width >= 96rem) |
1536px |
max-sm |
@media (width < 40rem) |
640px |
max-md |
@media (width < 48rem) |
768px |
max-lg |
@media (width < 64rem) |
1024px |
max-xl |
@media (width < 80rem) |
1280px |
max-2xl |
@media (width < 96rem) |
1536px |
max-[...] |
@media (width < ...) |
|
min-[...] |
@media (width >= ...) |
Let's look at a simple example to make this clear.
In the following example, the element's background color is red when the viewport width is 767px or less, and blue when it is 768px or more (md:).
1<div className="size-25 bg-red-500 md:bg-blue-500"></div>
In the following example, the element's background color is blue when the viewport width is 767px or less (max-md:), and red when it is 768px or more.
1<div className="size-25 bg-red-500 max-md:bg-blue-500"></div>
You can also nest variants to specify a breakpoint range.
In the following example, the element's background color is red by default, and blue when the viewport width is 768px or more and 1279px or less.
1<div className="size-25 bg-red-500 md:max-xl:bg-blue-500"></div>
If you want to define your own breakpoints, change the default --breakpoint-* theme variables in the Tailwind CSS @theme directive.
The following example uses the breakpoints from Bootstrap.
1234567@theme { --breakpoint-sm: 576px; --breakpoint-md: 768px; --breakpoint-lg: 992px; --breakpoint-xl: 1200px; --breakpoint-2xl: 1400px; }
# Container queries
Container queries follow a pattern similar to the media queries we just looked at.
They provide 13 breakpoints from 3xs to 7xl, you can use the max-* variants, and you can also specify ranges.
However, unlike media queries, which are based on the viewport, container queries require you to mark the reference ancestor element with the @container class and put an @ in front of the variant you use.
In the following example, the background color of the third <div> is blue when the first <div>, which has the @container class, is 3xl (768px) wide or wider, and red when it is 767px wide or narrower.
12345<div className="@container max-w-[1000px] border-2"> <div> <div className="size-25 bg-red-500 @3xl:bg-blue-500"></div> </div> </div>
If the default container query breakpoints such as 3xs don't fit a particular situation, you can also use an arbitrary value like @min-[768px].
The following example produces the same result as the previous one.
12345<div className="@container max-w-[1000px] border-2"> <div> <div className="size-25 bg-red-500 @min-[768px]:bg-blue-500"></div> </div> </div>
# Styling descendant (child) elements
In most cases, you write utility classes directly on the element itself, but sometimes you may need to write styles on an ancestor element.
In that case, you can style child elements with the *: variant and descendant elements with the **: variant.
In the following example, the classes on <ul> add padding to each <li> and a text color to each <a> that has an href attribute.
So <a>Apple</a> doesn't get the text color.
1234567891011<ul className="*:p-2 **:[a[href]]:text-red-400"> <li> <a>Apple</a> </li> <li> <a href="#">Banana</a> </li> <li> <a href="#">Cherry</a> </li> </ul>
If you want to style a descendant element based on the state of an ancestor element, you can use the group-*: variant.
First, add the group class to the ancestor element to mark it as the target.
Then, on the descendant element, use the group-*: variant to write the utility classes that apply based on the ancestor's state (a selector, utility class, variant, and so on).
Each <p> in the following example is shown only when you hover over its <li> or when the <li> has the active class.
So Answer B is shown right away, and Answer A is shown on hover.
12345678910111213<section> <h2>Frequently asked questions</h2> <ul> <li className="group"> <h4>Question A</h4> <p className="invisible group-[.active]:visible group-hover:visible">Answer A</p> </li> <li className="group active"> <h4>Question B</h4> <p className="invisible group-[.active]:visible group-hover:visible">Answer B</p> </li> </ul> </section>
# Styling sibling elements
You can also apply styles based on the state of a sibling element.
First, add the peer class to the sibling element to mark it as the target.
Then use the peer-*: variant to write the utility classes that apply based on the sibling's state.
In the following example, <p> is shown on the screen depending on whether the email in <input> is valid.
Note that the invalid: variant is the same as the CSS :invalid pseudo-class.
123456789<label> <input type="email" className="peer" /> <p className="invisible text-red-500 peer-invalid:visible"> Please enter a valid email address. </p> </label>
# Variants and CSS table
Now that you understand what variants are, take a quick look at the following table to see which variants exist and which CSS each one matches.
You don't need to remember all of them!
When you need details, select a variant name to go to the matching Tailwind CSS documentation.
| Variant | CSS |
|---|---|
| hover | @media (hover: hover) { &:hover } |
| focus | &:focus |
| focus-within | &:focus-within |
| focus-visible | &:focus-visible |
| active | &:active |
| visited | &:visited |
| target | &:target |
| * | :is(& > *) |
| ** | :is(& *) |
| has-[...] | &:has(...) |
| group-[...] | &:is(:where(.group)... *) |
| peer-[...] | &:is(:where(.peer)... ~ *) |
| in-[...] | :where(...) & |
| not-[...] | &:not(...) |
| inert | &:is([inert], [inert] *) |
| first | &:first-child |
| last | &:last-child |
| only | &:only-child |
| odd | &:nth-child(odd) |
| even | &:nth-child(even) |
| first-of-type | &:first-of-type |
| last-of-type | &:last-of-type |
| only-of-type | &:only-of-type |
| nth-[...] | &:nth-child(...) |
| nth-last-[...] | &:nth-last-child(...) |
| nth-of-type-[...] | &:nth-of-type(...) |
| nth-last-of-type-[...] | &:nth-last-of-type(...) |
| empty | &:empty |
| disabled | &:disabled |
| enabled | &:enabled |
| checked | &:checked |
| indeterminate | &:indeterminate |
| default | &:default |
| optional | &:optional |
| required | &:required |
| valid | &:valid |
| invalid | &:invalid |
| in-range | &:in-range |
| out-of-range | &:out-of-range |
| placeholder-shown | &:placeholder-shown |
| autofill | &:autofill |
| read-only | &:read-only |
| before | &::before |
| after | &::after |
| first-letter | &::first-letter |
| first-line | &::first-line |
| marker | &::marker, & *::marker |
| selection | &::selection |
| file | &::file-selector-button |
| backdrop | &::backdrop |
| placeholder | &::placeholder |
| sm | @media (width >= 40rem) |
| md | @media (width >= 48rem) |
| lg | @media (width >= 64rem) |
| xl | @media (width >= 80rem) |
| 2xl | @media (width >= 96rem) |
| min-[...] | @media (width >= ...) |
| max-sm | @media (width < 40rem) |
| max-md | @media (width < 48rem) |
| max-lg | @media (width < 64rem) |
| max-xl | @media (width < 80rem) |
| max-2xl | @media (width < 96rem) |
| max-[...] | @media (width < ...) |
| @3xs | @container (width >= 16rem) |
| @2xs | @container (width >= 18rem) |
| @xs | @container (width >= 20rem) |
| @sm | @container (width >= 24rem) |
| @md | @container (width >= 28rem) |
| @lg | @container (width >= 32rem) |
| @xl | @container (width >= 36rem) |
| @2xl | @container (width >= 42rem) |
| @3xl | @container (width >= 48rem) |
| @4xl | @container (width >= 56rem) |
| @5xl | @container (width >= 64rem) |
| @6xl | @container (width >= 72rem) |
| @7xl | @container (width >= 80rem) |
| @min-[...] | @container (width >= ...) |
| @max-3xs | @container (width < 16rem) |
| @max-2xs | @container (width < 18rem) |
| @max-xs | @container (width < 20rem) |
| @max-sm | @container (width < 24rem) |
| @max-md | @container (width < 28rem) |
| @max-lg | @container (width < 32rem) |
| @max-xl | @container (width < 36rem) |
| @max-2xl | @container (width < 42rem) |
| @max-3xl | @container (width < 48rem) |
| @max-4xl | @container (width < 56rem) |
| @max-5xl | @container (width < 64rem) |
| @max-6xl | @container (width < 72rem) |
| @max-7xl | @container (width < 80rem) |
| @max-[...] | @container (width < ...) |
| dark | @media (prefers-color-scheme: dark) |
| portrait | @media (orientation: portrait) |
| landscape | @media (orientation: landscape) |
| motion-safe | @media (prefers-reduced-motion: no-preference) |
| motion-reduce | @media (prefers-reduced-motion: reduce) |
| contrast-more | @media (prefers-contrast: more) |
| contrast-less | @media (prefers-contrast: less) |
@media print |
|
| supports-[...] | @supports (...) |
| aria-busy | &[aria-busy="true"] |
| 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"] |
| aria-[...] | &[aria-...] |
| data-[...] | &[data-...] |
| rtl | &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) |
| ltr | &:where(:dir(ltr), [dir="ltr"], [dir="ltr"] *) |
| open | &:is([open], :popover-open, :open) |
| forced-colors | @media (forced-colors: active) |
| starting | @starting-style |
# Directives
Directives are special Tailwind CSS rules that you use in CSS, not in HTML.
Let's look at the main directives you'll use often.
# @theme
You can redefine the default theme variables that Tailwind CSS uses, such as fonts, colors, and breakpoints, or create new theme variables.
1234567891011121314@theme { /* Modify default theme variables */ --font-sans: 'Pretendard', sans-serif; /* ui-sans-serif, system-ui, ... */ --color-white: #e9e9e9; /* #fff */ --color-black: #1a1a1a; /* #000 */ --spacing: 10px; /* 0.25rem */ --radius-md: 10px; /* 0.375rem */ --breakpoint-md: 800px; /* 768px */ --breakpoint-lg: 1100px; /* 1024px */ --breakpoint-xl: 1400px; /* 1280px */ /* Create custom theme variables */ --color-my-highlight: #e96900; }
1<div className="bg-(--color-my-highlight)"</div>
The namespaces of theme variables you can redefine are as follows.
| Theme variable | Utility class |
|---|---|
--color-* |
Color-related, like bg-red-500 and text-sky-300 |
--font-* |
Font-related, like font-sans |
--text-* |
Text size, like text-xl |
--font-weight-* |
Font weight, like font-bold |
--tracking-* |
Letter spacing, like tracking-wide |
--leading-* |
Line height, like leading-tight |
--breakpoint-* |
Media query breakpoints, like sm:* |
--container-* |
Container query breakpoints, like @sm:* and max-w-md |
--spacing-* |
Spacing-related, like px-4 and max-h-16 |
--radius-* |
Corner rounding, like rounded-sm |
--shadow-* |
Shadows, like shadow-md |
--inset-shadow-* |
Inset shadows, like inset-shadow-xs |
--drop-shadow-* |
Filter shadows, like drop-shadow-md |
--blur-* |
Blur effects, like blur-md |
--perspective-* |
Perspective effects, like perspective-near |
--aspect-* |
Aspect ratios, like aspect-video |
--ease-* |
Timing functions, like ease-out |
--animate-* |
Animations, like animate-spin |
# @utility
You can define your own utility classes with the @utility directive.
12345@utility heropy-box-300 { width: 300px; height: 300px; border-radius: 10px; }
1<div className="heropy-box-300"></div>
You can also define more complex utility classes.
When you pass a plain number without a unit, you can use the number or integer type in the --value() function.
There are a few more types you can use this way, such as percentage and ratio, but they are hard to recommend because they aren't intuitive, behave differently depending on the property, and don't handle every value.
12345@utility heropy-box-* { width: --value(number)px; height: --value(number)px; border-radius: 10px; }
123<div className="heropy-box-100"></div> <div className="heropy-box-300"></div> <div className="heropy-box-500"></div>
So if you're creating a utility class that accepts many kinds of values, I recommend the arbitrary value approach with [].
With a wildcard character, you can assign any value to the --value() function.
12345@utility heropy-box-* { width: --value([*]); height: --value([*]); border-radius: 10px; }
123<div className="heropy-box-[300px]"></div> <div className="heropy-box-[50%]"></div> <div className="heropy-box-[10dvw]"></div>
For colors, specify the --color-* theme variable.
Then you can use the many colors that Tailwind CSS provides.
123456@utility heropy-box-color-* { width: 100px; height: 100px; border-radius: 10px; background-color: --value(--color-*); }
12<div className="heropy-box-color-red-500"></div> <div className="heropy-box-color-blue-400"></div>
Along with the --value() function, you can also use the --modifier() function.
This lets you handle an extra value written with /, like text-sm/6 or bg-red-500/70.
1234567@utility heropy-box-color-* { width: 100px; height: 100px; border-radius: 10px; background-color: --value(--color-*); opacity: --modifier(number); }
12<div className="heropy-box-color-red-500"></div> <div className="heropy-box-color-blue-400/0.5"></div>
# @apply
The @apply directive lets you use utility classes in CSS.
The following example defines a new utility class by writing CSS properties and values directly, without @apply.
12345@utility heropy-flex-center { display: flex; align-items: center; justify-content: center; }
The following example produces the same result as the previous one, but uses utility classes instead of CSS properties.
If you're familiar with utility classes, this may be more convenient.
123@utility heropy-flex-center { @apply flex items-center justify-center; }
1<div className="heropy-flex-center"></div>
# @variant
You can use the @variant directive to add variants to a utility class.
The following example defines the hover style for elements that have a heropy-box-* class.
123456789@utility heropy-box-* { width: --value([*]); height: --value([*]); border-radius: 10px; transition: transform 0.3s; @variant hover { transform: scale(1.1); } }
You can also rewrite the previous example with the @apply directive as follows.
123456@utility heropy-box-* { @apply size-[--value([*])] rounded-[10px] transition duration-300; @variant hover { @apply scale-[1.1]; } }
1<div className="heropy-box-[300px]"></div>
You can also nest the @variant directive multiple times.
In the following example, the background color (bg-blue-100) is applied only when you hover over a focused element.
123456789@utility heropy-input { @apply rounded-[10px] transition duration-300; @variant focus { @apply border-blue-400; @variant hover { @apply bg-blue-100; } } }
1<input className="heropy-input" />
# @custom-variant
You can also create your own variants with the @custom-variant directive.
Here, I created a state-open: variant that checks whether the data-state HTML attribute of an ancestor element is open.
1@custom-variant state-open '[data-state="open"] &';
When the data-state attribute of the first <div> is open, the background color of the second <div> becomes blue.
123456789101112131415import { useState } from 'react' export default function () { const [isActive, setIsActive] = useState(false) return ( <> <div data-state={isActive ? 'open' : 'close'} onClick={() => setIsActive(!isActive)}> <div className="state-open:bg-blue-500"></div> </div> </> ) }
# More usage patterns
# Using a prefix
If utility classes conflict with classes that already exist in your project, or if you want to mark utility classes clearly, you can use the prefix option as follows.
1@import "tailwindcss" prefix(tw);
Now you must always add the tw: prefix to utility classes.
1<div className="tw:h-25 tw:w-25 tw:bg-red-500"></div>
# Priority
If you need to force a utility class to apply in a special situation, add ! to the end of its name to apply !important.
In the following example, the h-50 class takes priority.
1<div className="h-50! h-100"></div>
# Applying dynamic styles
Tailwind CSS scans your project for utility classes and generates the CSS you need based on the classes you actually use.
So you shouldn't build classes dynamically.
For example, say you want to make an element whose width grows every time the count state increases. If you write the utility class so that it's built dynamically, like w-[${width}px] in the following example, the style won't be applied.
12345678910111213import { useState } from 'react' export default function () { const [width, setWidth] = useState(100) return ( <div className={`w-[${width}px] rounded-md bg-blue-500 p-3 font-bold text-white transition duration-300`} onClick={() => setWidth(width + 20)}> {width}px </div> ) }
So you need a separate style binding as follows.
1234567891011121314import { useState } from 'react' export default function () { const [width, setWidth] = useState(100) return ( <div className={`rounded-md bg-blue-500 p-3 font-bold text-white transition duration-300`} style={{ width: `${width}px` }} onClick={() => setWidth(width + 20)}> {width}px </div> ) }
Instead of binding an inline style for every piece of dynamic data, you can also predefine utility classes and apply them when needed, as in the following example.
12345678910111213141516171819export default function Button({ color, children }: { color: 'primary' | 'secondary' | 'error' children: React.ReactNode }) { const colors = { primary: 'bg-blue-500 hover:bg-blue-600 text-white', secondary: 'bg-gray-400 hover:bg-gray-500 text-black', error: 'bg-red-500 hover:bg-red-600 text-white' } return ( <button className={`${colors[color]} cursor-pointer rounded-md px-3 py-2 transition duration-200`}> {children} </button> ) }
To make styles more reusable and easier to maintain, you can also organize utility classes with the CVA (Class Variance Authority) library.
1npm i class-variance-authority
123456789101112131415161718192021222324252627import { cva, type VariantProps } from 'class-variance-authority' const buttonStyles = cva( 'cursor-pointer rounded-md px-3 py-2 transition duration-200', { variants: { color: { primary: 'bg-blue-500 hover:bg-blue-600 text-white', secondary: 'bg-gray-400 hover:bg-gray-500 text-black', error: 'bg-red-500 hover:bg-red-600 text-white' } }, defaultVariants: { color: 'primary' } } ) export default function Button({ color, children }: { color?: VariantProps<typeof buttonStyles>['color'] children: React.ReactNode }) { return <button className={buttonStyles({ color })}>{children}</button> }
The utility classes you write in the cva function are plain JS strings, so autocomplete and preview don't work.
If you want to use the features of the Tailwind CSS IntelliSense extension in the cva or cx function, you can configure it as follows.
123456{ "tailwindCSS.experimental.classRegex": [ ["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], ["cx\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] ] }
# Merging styles
When you use Tailwind CSS and conflicting utility classes are applied to a single element, it's hard to tell which class will take priority, and the style you want may not be applied.
In the following example, the isActive state is true, but the styles of the w-25 and bg-blue-500 classes aren't applied to the element.
That's because the w-75 and bg-red-500 classes take priority.
Note that in this situation, priority is decided by declaration order, not specificity, and that order can change at any time with the Tailwind CSS build, so trying to learn the pattern is pointless.
So how should you solve this?
12345678910import { useState } from 'react' export default function App() { const [isActive] = useState(true) return ( <> <div className={`h-25 w-75 bg-red-500 ${isActive && 'w-25 bg-blue-500'}`}></div> </> ) }
This is where you can use the tailwind-merge library to solve the problem.
First, install the library.
1npm i tailwind-merge
The library's twMerge() function makes the class applied later take priority.
You can list all the classes in a single argument as in the first <div>, but code formatting may change the order of the classes.
So if you split them into multiple arguments in order, as in the second <div>, you can set the priority more clearly and safely.
Now the styles of the w-25 and bg-blue-500 classes are applied according to the isActive state.
12// The higher the number, the higher the priority! twMerge('priority1', 'priority2', 'priority3')
12345678910111213141516171819import { useState } from 'react' import { twMerge } from 'tailwind-merge' export default function App() { const [isActive] = useState(true) return ( <> <div className={twMerge( `h-25 w-75 bg-red-500 ${isActive && 'w-25 bg-blue-500'}` )}></div> <div className={twMerge( `h-25 w-75 bg-red-500`, isActive && 'w-25 bg-blue-500' )}></div> </> ) }
Sometimes you may just need to combine utility classes regardless of priority.
Since you're already using tailwind-merge, try the twJoin() function instead of clsx.
1234567891011121314151617181920import { useState } from 'react' import { twJoin } from 'tailwind-merge' export default function App() { const [isActive] = useState(true) const colorRed = 'bg-red-500' return ( <> <div className={twJoin( ['h-25 w-25'], colorRed, isActive && 'bg-blue-500', ['w-75'] )}></div> {/* ⬇️ They are combined like this. */} <div className="h-25 w-25 w-75 bg-blue-500 bg-red-500"></div> </> ) }
Thanks for reading to the end.
Likes and kind comments help keep this blog going!