IAnswerable

How Far Modern CSS Gets You Without a Framework

30 Oct 2026

You can ship a responsive, themeable site with zero CSS framework and less code than a Tailwind config file. Grid, custom properties, and clamp() cover the three things frameworks used to earn their keep on: layout, spacing scales, and fluid type. Here’s how far that actually gets you.

Layout: Grid replaces most of your layout utilities

.page {
  display: grid;
  grid-template-columns: 1fr min(65ch, 100%) 1fr;
}
.page > * {
  grid-column: 2;
}
.full-bleed {
  grid-column: 1 / -1;
}

That’s a centered content column with a readable max width, and any child can break out full-width by adding one class. No twelve-column grid system, no .col-md-6 — you’re describing the layout you actually want instead of composing it from generic fractions.

For component-level layout, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) gives you a responsive card grid that reflows without a single media query:

.cards {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

Spacing and color: custom properties as your design tokens

:root {
  --space-2: 0.5rem;
  --space-4: 1rem;
  --space-8: 2rem;
  --color-text: #1a1a1a;
  --color-bg: #fff;
  --color-accent: #2b6cb0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-text: #eee;
    --color-bg: #111;
  }
}

This is what a framework’s theme config does, minus the build step and the utility class explosion. Reference var(--space-4) in your CSS instead of p-4 in your markup. You lose nothing — you still get a constrained scale — but the values live in one place and you can override them per-component with normal CSS cascade instead of fighting specificity on utility classes.

Fluid type and spacing: clamp() replaces breakpoint-based sizing

h1 {
  font-size: clamp(1.75rem, 4vw + 1rem, 3rem);
}

That scales smoothly between 1.75rem on small screens and 3rem on large ones, with no @media breakpoints for type at all. clamp(min, preferred, max) — the preferred value does the scaling, the min and max cap it. Do the same for section padding: padding-block: clamp(2rem, 5vw, 6rem).

What you actually still need a media query for

Structural layout changes — a sidebar that becomes a bottom nav, a table that becomes stacked cards — are still discrete jumps, not fluid ones. Don’t try to clamp() your way out of an actual layout restructure. Use a media query (or a container query for a reusable component) there, and use fluid values for everything continuous: type, spacing, gaps.

Where a framework still earns its place

Component behavior — an accordion, a date picker, a combobox with correct ARIA — isn’t a CSS problem, and no amount of Grid and custom properties gets you there. If your site needs a dozen interactive widgets, you want a component library or Alpine.js for the JS side, on top of your own CSS. What you don’t need a framework for anymore is layout and theming — that’s core CSS now, and it’s less code than the utility-class version.

Verdict

Start with a :root token block, grid-template-columns: 1fr min(65ch, 100%) 1fr for your page shell, and clamp() for anything that should scale continuously. Add a framework only for the interactive widgets that actually need one.