Utility classes are combined in markup for styling
You style things with Tailwind by combining many single-purpose presentational classes (utility classes) directly in your markup. For example, a card component might use classes like flex, p-6, rounded-xl, bg-white, and shadow-lg to control layout, padding, border radius, background color, and shadow.
Hover and focus states use variant prefixes
To style an element on hover or focus states, prefix any utility with the state you want to target. For example, hover:bg-sky-700 applies background-color: sky-700 only when the element is hovered. These prefixes are called variants in Tailwind. You can stack variants to apply utilities when multiple conditions match, like disabled:hover:bg-sky-500.
Media queries use breakpoint prefixes
Style elements at different breakpoints by prefixing utilities with the breakpoint name. For example, sm:grid-cols-3 applies grid-template-columns: repeat(3, minmax(0, 1fr)) at the sm breakpoint (40rem and above by default). The prefix only triggers that style at that breakpoint and above.
Dark mode styling uses the dark prefix
To style an element in dark mode, add the dark: prefix to any utility you want to apply when dark mode is active. For example, dark:bg-gray-800 applies background-color: gray-800 only when dark mode is enabled. You use multiple classes to build both light and dark styles—one for light mode and another for dark mode—since a single utility class never includes both.
Class composition with CSS variables for filter effects
Multiple utilities can be combined to build up values for a single CSS property. For example, blur-sm and grayscale both affect the filter property. Tailwind uses CSS variables to compose these effects: each utility sets a CSS variable for its effect, and the filter property references all of these variables. This approach also works for gradients, shadow colors, transforms, and more.
How Tailwind generates CSS from class names
Tailwind CSS isn't a static stylesheet—it generates CSS based on the classes you actually use. It scans project files for symbols that look like class names, then generates CSS for each one and compiles it into a single stylesheet containing only the styles you need. Since CSS is generated based on class names, Tailwind can recognize arbitrary value classes like bg-[#316ff6] and generate the necessary CSS even when the value isn't in your theme.
Complex selectors combine multiple variants
You can style an element under multiple conditions by combining variants in a single class. For example, dark:lg:data-current:hover:bg-indigo-600 applies styles when all conditions match: dark mode is enabled, viewport is at least 64rem wide, the element has a data-current attribute, and it is hovered. Tailwind generates a media query and selector matching all these conditions.
Group hover variant for styling based on parent state
Use group-hover to style an element when a specific parent is hovered. Add the group class to the parent element, then use group-hover:underline (or other utilities) on child elements to style them when the parent is hovered. This variant works with other states too: group-focus, group-active, and many more.
Arbitrary variants for complex selectors
For complex scenarios, especially when styling HTML you don't control, use arbitrary variants to write any selector directly in a class name. Syntax: [&>[data-active]+span]:text-blue-600. The & represents the current element. This lets you target adjacent siblings, specific descendants, and other complex selector patterns without writing custom CSS.
Inline styles for dynamic values from APIs or databases
Inline styles are still useful in Tailwind CSS projects when values come from dynamic sources like databases or APIs. You can combine inline styles with utility classes—use inline styles for dynamic values and utility classes for static styling. Another pattern is setting CSS variables via inline styles, then referencing those variables with utility classes like bg-(--bg-color).
Duplication in loops is not a problem
When design elements appear multiple times in rendered pages but the markup is authored in a loop, there is no actual duplication because the class list is written once in the loop. The element repeats but the styling code doesn't.
Multi-cursor editing for small-scale duplication
When duplication is localized to a group of elements in a single file, use multi-cursor editing (supported by editors like VS Code) to quickly select and edit class lists for multiple elements simultaneously. This is often the best solution when you can quickly edit all duplicated class lists at once without introducing additional abstraction.
Components for reusing styles across files
If you need to reuse styles across multiple files, create a component (in React, Svelte, Vue, etc.) or a template partial (in Blade, ERB, Twig, Nunjucks, etc.). This provides a single source of truth for the styles so they can be easily updated in one place. For complex components, template partials are recommended over custom CSS.
Custom CSS with @layer components for small reusable classes
For small reusable patterns when template partials feel heavy-handed (like a btn-primary button), you can write custom CSS using @layer components. Reference theme variables to keep the design consistent. This is acceptable for simple elements but for anything more complicated, use template partials to encapsulate both structure and styles in one place.
Conflicting utility classes: later in stylesheet wins
When two classes target the same CSS property, the class that appears later in the stylesheet wins, regardless of order in the class attribute. For example, in class="grid flex", grid wins because it appears later in the stylesheet (display: grid). To avoid this, never add two conflicting classes to the same element—only add the one you actually want.
Important modifier with ! suffix
When you need to force a specific utility class to take effect and have no other means of managing specificity, add ! to the end of the class name to make all declarations !important. For example: bg-red-500!. This generates .bg-red-500\! { background-color: var(--color-red-500) !important; }
Important flag in import statement
If adding Tailwind to a project with existing complex CSS with high specificity rules, use the important flag when importing Tailwind to mark all utilities as !important. Syntax: @import "tailwindcss" important; This makes all generated utilities include !important.
Prefix option to avoid class name conflicts
If your project has class names that conflict with Tailwind CSS utilities, use the prefix option when importing Tailwind to prefix all generated classes and CSS variables. Syntax: @import "tailwindcss" prefix(tw); This prefixes utilities with tw: and CSS variables with --tw-, for example tw:text-red-500 and --tw-color-red-500.
Benefits of utility-first approach over traditional CSS
The utility-first approach provides several important benefits: you get things done faster without naming classes or switching between HTML and CSS files; making changes feels safer since adding or removing a class only affects that element; maintaining old projects is easier because you change styles where the element is, not in separate CSS; your code is more portable since structure and styling live together; and your CSS stops growing linearly since utilities are reusable.
Advantages of utility classes over inline styles
Utility classes have important advantages over inline styles: designing with constraints through a predefined design system (not magic numbers); support for hover, focus, and other states which inline styles cannot target; and support for media queries and responsive variants. Inline styles are essentially magic numbers with no constraint or state support.