Designing Without CSS Frameworks
Every new project starts with the same question: Tailwind or not? For my portfolio, I chose neither Tailwind nor any other CSS framework. Just plain CSS with some Sass nesting. It was the most enjoyable styling experience I’ve had in years.
The Case Against Utility Classes
Utility-first CSS is genuinely productive. I’ve shipped production apps with Tailwind and appreciated the speed. But for a portfolio — a project where the design is the product — utility classes felt like the wrong abstraction. I didn’t want to compose visual designs from a predefined vocabulary. I wanted to write exactly the CSS that each component needed, with full control over every property and value.
There’s also a readability argument. A button with twelve utility classes tells you what styles are applied, but not why. A well-written CSS rule with a semantic class name communicates intent. When I come back to this code in six months, .nav-link.active tells me more than text-blue-600 font-bold border-b-2 border-blue-600.
Custom Properties as a Design System
CSS custom properties replaced what I used to need Sass variables for, and they’re strictly more powerful because they cascade and can be scoped to components. My entire color palette lives in :root as custom properties, organized by purpose rather than value.
This organization means I never have to remember if the background is #ededed or #f5f5f5. I just use var(--color-bg) or var(--color-bg-light) and the naming carries the semantic meaning. When I want to adjust the overall tone of the site, I change values in one place.
The real power shows up in component-level theming. Each page header accepts color props that become scoped custom properties, letting the same component render in different color schemes without conditional CSS. The projects page uses chartreuse accents while the blog uses cyan — same component, different properties.
Scoped Styles Solve the Cascade Problem
The traditional argument against vanilla CSS is specificity conflicts. Component A styles leak into Component B, you add !important to fix it, and eventually the stylesheet becomes an unmanageable mess. Astro’s scoped styles eliminate this entirely. Each component’s styles are automatically scoped with a unique attribute, so .button in one component never conflicts with .button in another.
This is the key enabler that makes framework-free CSS viable for component-based architectures. You get the isolation of CSS Modules or styled-components without the tooling overhead or runtime cost.
What I Missed
Responsive design required more manual work. Tailwind’s responsive prefixes like md:flex are genuinely convenient, and writing explicit media queries for every breakpoint is more verbose. I settled on three breakpoints (1000px, 700px, 480px) and used Sass nesting to keep the media queries close to the properties they modify.
I also missed the consistency that a framework enforces. With Tailwind, spacing is always a multiple of 4px and colors always come from the palette. With custom CSS, discipline is the only thing preventing you from using padding: 13px in one place and padding: 1rem in another. I had to be my own design police.
The Verdict
For personal projects where the design matters and you want full creative control, writing your own CSS is not only viable — it’s preferable. The modern CSS feature set (custom properties, nesting, scoped styles, container queries, clamp()) makes it a genuinely pleasant experience. The output is smaller, faster, and more maintainable than any framework could produce, because every line of CSS exists for a reason you chose.