Skip to content
Rochester, NY design, systems, study tools, and experiments Built in public

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.

web css tailwind frontend design-system 2026-04-15

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:

  1. Naming — you don't have to invent card-header-with-icon-and-action. The class composition is the description.
  2. Cascading mistakes — utility classes have no specificity wars. There is no !important cascade to debug because each utility does one thing.
  3. 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:

  • Spacingp-4 (padding 1rem), px-2 (horizontal), mt-8 (margin-top 2rem). Numbers are 0.25rem multiples by default.
  • Colorbg-blue-600, text-gray-900, border-red-500. Color names + 50/100/.../900 weight scale.
  • Sizingw-1/2 (width 50%), h-screen, max-w-3xl.
  • Layoutflex, grid, grid-cols-3, gap-4, items-center, justify-between.
  • Typographytext-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 states
  • sm:, 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.