How Far Modern CSS Gets You Without a Framework
30 Oct 2026You 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.