Atomic CSS: 10 Code-Along Examples

Learn Atomic CSS by building it. Ten copy-and-run examples: a utility library plus buttons, cards, nav bars, alerts, a pricing tile, responsive variants, and when to extract a component class.

The Atomic CSS guide explains the idea: instead of writing a custom rule for every component, you make tiny classes that each do one thing, like .p-4 for padding or .text-center for alignment, then build interfaces by listing those classes in your markup. This workbook puts that into practice. You will build a small utility library once, then assemble buttons, cards, nav bars, alerts and a pricing tile out of nothing but those classes. By the end you will see both why the approach scales and where it starts to hurt.

1. Build the utility library

Everything below depends on this one stylesheet. These are your single-purpose classes: spacing, typography, colour, layout and a few visual effects. Save this as styles.css and leave it open, because later examples add a handful of utilities to it.

/* Reset and base */
* { box-sizing: border-box; }
body { margin: 0; font-family: sans-serif; color: #11243a; }
/* Layout and display */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.flex-1 { flex: 1; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; }
/* Sizing */
.w-full { width: 100%; }
.max-w-sm { max-width: 360px; }
.mx-auto { margin-left: auto; margin-right: auto; }
/* Spacing: padding */
.p-2 { padding: 0.5rem; }
.p-3 { padding: 0.75rem; }
.p-4 { padding: 1rem; }
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
/* Spacing: margin */
.m-2 { margin: 0.5rem; }
.mt-2 { margin-top: 0.5rem; }
.mb-1 { margin-bottom: 0.25rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-3 { margin-bottom: 0.75rem; }
/* Typography */
.text-sm { font-size: 0.85rem; }
.text-base { font-size: 1rem; }
.text-lg { font-size: 1.25rem; }
.text-xl { font-size: 1.5rem; }
.text-2xl { font-size: 2rem; }
.font-semibold { font-weight: 600; }
.font-bold { font-weight: 700; }
.text-center { text-align: center; }
.leading-snug { line-height: 1.3; }
/* Colours: background */
.bg-white { background: #ffffff; }
.bg-light { background: #f0f4f8; }
.bg-blue { background: #1f4e78; }
.bg-dark { background: #11243a; }
.bg-green { background: #1f7a4d; }
.bg-amber { background: #b7791f; }
/* Colours: text */
.text-white { color: #ffffff; }
.text-dark { color: #11243a; }
.text-muted { color: #5b6b7d; }
.text-blue { color: #1f4e78; }
/* Effects */
.rounded { border-radius: 6px; }
.rounded-lg { border-radius: 10px; }
.rounded-full { border-radius: 999px; }
.shadow { box-shadow: 0 2px 8px rgba(17, 36, 58, 0.12); }
.border { border: 1px solid #d9e2ec; }
/* Helpers */
.inline-block { display: inline-block; }
.no-underline { text-decoration: none; }
.cursor-pointer { cursor: pointer; }

Here is the smallest possible test that it works.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<p class="p-4 bg-light text-blue text-xl font-bold">Utilities are loaded.</p>
</body>
</html>

Notice that the paragraph has no custom CSS of its own. Its entire appearance comes from the four classes listed in the markup. That inversion, design decisions living in the HTML rather than the stylesheet, is the heart of atomic CSS.

2. The spacing scale in action

Atomic systems lean on a consistent spacing scale so everything lines up without arbitrary values. Here the same box is shown with increasing padding, all drawn from the p-* classes you already defined.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4">
<div class="p-2 mb-2 bg-light rounded">p-2 (0.5rem)</div>
<div class="p-3 mb-2 bg-light rounded">p-3 (0.75rem)</div>
<div class="p-4 mb-2 bg-light rounded">p-4 (1rem)</div>
</body>
</html>

Because the steps are predefined, you never type a raw pixel value in the markup. This is what keeps an atomic interface visually consistent: every gap is one of a small set of known sizes.

3. A button from utilities only

A button is just padding, a colour, rounded corners and bold text. No .button rule required, only a list of utilities.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4">
<a href="#" class="inline-block no-underline px-4 py-2 bg-blue text-white font-semibold rounded cursor-pointer">
Subscribe
</a>
</body>
</html>

To make a second, quieter button you change only the colour classes, swapping bg-blue text-white for bg-light text-blue. The structural classes stay identical, which is exactly the reuse the article describes.

4. The card component

This is the example from the article: a card assembled entirely from bg-white shadow p-4 rounded and a few typography utilities. It shows how a finished component emerges from stacking single-purpose classes.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4 bg-light">
<div class="max-w-sm bg-white shadow rounded-lg p-4">
<h3 class="text-lg font-bold mb-1">Atomic CSS</h3>
<p class="text-sm text-muted mb-3 leading-snug">
Build interfaces by composing single-purpose classes in your markup.
</p>
<a href="#" class="inline-block no-underline px-4 py-2 bg-blue text-white font-semibold rounded">
Read more
</a>
</div>
</body>
</html>

Read the class list and you can picture the card before you open it: white background, shadow, rounded corners, padding, a bold heading, muted small text, a blue button. The markup has become self-documenting, at the cost of being wordy.

5. A navigation bar with flex utilities

Atomic CSS handles layout too. The same flex utilities you would write by hand are available as classes, so a header is flex items-center justify-between plus spacing.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="flex items-center justify-between px-4 py-3 bg-dark">
<span class="text-white font-bold text-lg">datalad</span>
<nav class="flex gap-4">
<a href="#" class="no-underline text-white">Home</a>
<a href="#" class="no-underline text-white">Articles</a>
<a href="#" class="no-underline text-white">Shop</a>
</nav>
</header>
</body>
</html>

The header and the nav are both flex containers, expressed purely through classes. If you have done the Flexbox workbook, you are writing the same CSS, just naming the rules ahead of time and reusing them.

6. A media row: avatar beside text

Combining layout and spacing utilities gives you a comment or list row. flex items-start gap-3 does the structure, and the rest is colour and type.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4">
<div class="flex items-start gap-3 max-w-sm">
<div class="rounded-full bg-blue" style="width:48px;height:48px;flex:0 0 48px;"></div>
<div class="flex-1">
<p class="font-semibold mb-1">Andrei</p>
<p class="text-sm text-muted leading-snug">
Utilities keep the avatar fixed while this text takes the rest of the row.
</p>
</div>
</div>
</body>
</html>

One honest note: the avatar uses an inline style for its exact size because our small library has no width utilities for 48px. In a real atomic system you would add .w-12 .h-12 classes rather than reaching for inline styles. This is the moment you feel the trade-off: a utility only exists if you defined it.

7. Alerts and badges with colour utilities

Status messages are a great fit because they differ only by colour. The structure is shared; you swap the background and text classes to change meaning.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4">
<div class="p-3 mb-2 rounded text-white bg-green">Success: your changes were saved.</div>
<div class="p-3 mb-2 rounded text-white bg-amber">Warning: check your input.</div>
<div class="p-3 mb-2 rounded text-white bg-blue">Info: a new article is live.</div>
<span class="inline-block px-3 py-1 text-sm rounded-full text-white bg-dark">New</span>
</body>
</html>

Three alerts, one set of structural classes, three colour swaps. This is where the “small stylesheet despite a growing interface” benefit shows: adding a fourth alert costs zero new CSS.

8. A pricing tile

A slightly bigger composition to prove the approach scales to real components. Everything here is a utility you already have.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4 bg-light">
<div class="max-w-sm mx-auto bg-white shadow rounded-lg p-4 text-center">
<p class="text-muted text-sm mb-1">Pro plan</p>
<p class="text-2xl font-bold mb-1">£29</p>
<p class="text-sm text-muted mb-3">per month</p>
<a href="#" class="inline-block w-full no-underline py-2 bg-blue text-white font-semibold rounded">
Choose Pro
</a>
</div>
</body>
</html>

The full-width button is just w-full added to the same button recipe from example 3. Composability like this is why prototyping with utilities feels so fast: new components are mostly recombinations of classes you have already written.

9. Responsive variants

Real utility frameworks add breakpoint-prefixed classes like md:flex-row. Add these few rules to your styles.css, then the layout can change at a breakpoint without any new component CSS.

/* Add to styles.css: responsive variants */
.flex-col { flex-direction: column; }
@media (min-width: 600px) {
.md\:flex-row { flex-direction: row; }
.md\:text-left { text-align: left; }
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4">
<div class="flex flex-col md:flex-row gap-3">
<div class="flex-1 p-4 bg-light rounded text-center md:text-left">Column on mobile</div>
<div class="flex-1 p-4 bg-light rounded text-center md:text-left">Row on desktop</div>
</div>
</body>
</html>

The backslash in .md\:flex-row escapes the colon so it is a valid class name. Below 600px the boxes stack; above it they sit side by side. This breakpoint-prefix pattern is exactly how Tailwind CSS, the best known utility-first framework, expresses responsive design.

10. Knowing when to extract a component

The article’s key piece of advice is balance: when the same long class list repeats everywhere, promote it to a single component class. Here the button recipe becomes .btn, keeping the markup clean while the rest of your interface stays atomic.

/* Add to styles.css: extracted component */
.btn {
display: inline-block;
padding: 0.5rem 1rem;
font-weight: 600;
border-radius: 6px;
text-decoration: none;
background: #1f4e78;
color: #ffffff;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4">
<!-- Verbose: the atomic way -->
<a href="#" class="inline-block px-4 py-2 bg-blue text-white font-semibold rounded no-underline">Atomic button</a>
<!-- Clean: extracted once the pattern repeats -->
<a href="#" class="btn">Component button</a>
</body>
</html>

Both buttons look the same. The atomic version is honest about every style but verbose; the extracted version is tidy but hides its details in the stylesheet. A healthy codebase uses utilities for one-off layout and extracts a component class the moment a pattern appears for the third or fourth time. In production you would also run a tool like PurgeCSS to strip any utility classes you never actually used, which is what keeps the shipped stylesheet tiny.

Work through these and you will have built a miniature utility framework and assembled real components from it, while feeling both sides of the trade-off: rapid, consistent, highly reusable styling on one hand, and verbose markup that depends on a well-named class library on the other. Once it clicks, frameworks like Tailwind stop looking like magic and start looking like a very thorough version of the styles.css you just wrote.

See you soon.

View Comments (1)

Leave a Reply

Prev Next

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading