Tailwind CSS: utility-first basics
Tailwind exposes design tokens (spacing, color, typography) as small atomic class names you compose directly in HTML. Trades named-component CSS for inline composition; pairs with a build step that strips unused classes.
Tailwind CSS is a utility-first framework. Instead of writing .btn-primary { background: blue; padding: 8px 16px; } and applying class="btn-primary", you compose the styling inline with atomic class names: class="bg-blue-600 px-4 py-2". The CSS file is a fixed library of utilities; the component is whatever HTML you assemble out of them.
Why utility-first
The traditional component-CSS workflow has three friction points Tailwind eliminates:
- Naming — you don't have to invent
card-header-with-icon-and-action. The class composition is the description. - Cascading mistakes — utility classes have no specificity wars. There is no
!importantcascade to debug because each utility does one thing. - Dead CSS accumulation — components fall out of use but their CSS stays. Tailwind generates only the utilities you reference at build time, so unused tokens never ship.
The trade-off is verbosity in HTML and the cognitive load of memorizing the utility vocabulary. Both shrink quickly with use; the verbosity reduces further with @apply or component extraction in your framework of choice.
The vocabulary patterns
Tailwind utilities follow a consistent grammar so you can guess class names without checking the docs:
- Spacing —
p-4(padding 1rem),px-2(horizontal),mt-8(margin-top 2rem). Numbers are 0.25rem multiples by default. - Color —
bg-blue-600,text-gray-900,border-red-500. Color names + 50/100/.../900 weight scale. - Sizing —
w-1/2(width 50%),h-screen,max-w-3xl. - Layout —
flex,grid,grid-cols-3,gap-4,items-center,justify-between. - Typography —
text-lg,font-semibold,leading-tight,tracking-wider.
Responsive + state variants
Prefixes apply utilities conditionally:
<button class="bg-blue-600 hover:bg-blue-700 md:px-8 dark:bg-blue-500">
Click me
</button>
hover:,focus:,active:— interaction statessm:,md:,lg:,xl:— min-width breakpoints (mobile-first)dark:— dark mode (requires config)disabled:,first:,last:,odd:— structural states
The build step
Tailwind scans your templates for class names, generates only the matching CSS, and outputs a single file. In this repo:
npm run css:build # one-shot build
npm run css:watch # rebuild on change
The compiled static/css/styles.css is committed so the Docker image doesn't need Node. Forgetting to rebuild after editing a template is the most common deployment bug — new utility classes silently render unstyled in production.
When utility-first hurts
- Long, repeated class lists — extract to a component (Jinja macro, React component) rather than copy-pasting 30 utility classes across 12 places.
- Truly one-off styling that doesn't fit any utility — drop a
<style>block or a stylesheet rule. Tailwind doesn't replace CSS; it sits on top of it.