The “It’s Just Markup” Misconception
I still hear people say that HTML and CSS aren’t “real” programming. It drives me up the wall. I’ve spent more hours debugging a z-index issue or a collapsing margin than I have fixing complex JavaScript logic. If you treat the frontend foundation as an afterthought, you end up with a fragile house of cards that collapses the moment someone opens your site on a foldable phone or an older tablet.
In 2025, the browser capabilities are insane. We aren’t just styling documents anymore; we are building complex, interactive applications. Yet, I see so many developers jumping straight into React or Svelte without understanding how the browser actually renders a pixel. They rely on heavy libraries to do simple things because they don’t know that CSS can now handle animations, math, and logic natively.
I want to walk you through my mental model for HTML and CSS right now. This isn’t a dictionary of tags—you have documentation for that. This is about how I actually approach building interfaces, the features I use daily, and the bad habits I’ve tried to unlearn over the last few years.
HTML: It’s About Meaning, Not Just Display
When I start a new project, I don’t touch CSS for the first hour. I focus entirely on the HTML structure. If the HTML doesn’t make sense without styles, it won’t make sense to a screen reader or a search engine bot.
Semantic HTML is Non-Negotiable
I used to be guilty of “div soup”—nesting fifteen <div> tags just to create a sidebar. It works visually, but it’s a nightmare for accessibility. Now, I force myself to use the right tag for the job. If it’s a navigation bar, I use <nav>. If it’s a standalone piece of content, I use <article>. If it’s a sidebar, <aside>.
Here is why this matters to me: I once built a dashboard that looked great but was unusable for a client relying on keyboard navigation. I had used <div onClick="..."> for buttons. A <div> is not focusable by default. I had to go back and rewrite weeks of code. Now, I use <button> for actions and <a> for links. It sounds basic, but you’d be surprised how often this gets messed up in production code.
Accessibility and ARIA
Web Accessibility isn’t just a “nice to have.” I treat it as a core requirement. I use ARIA labels only when strictly necessary. The first rule of ARIA is: don’t use ARIA if a native HTML element does the job. For example, instead of making a custom toggle switch with divs and ARIA states, I’ll often start with a native <input type="checkbox"> and style it. It handles the state and keyboard interaction for me.
CSS Layouts: Flexbox vs. Grid
For the longest time, I was confused about when to use Flexbox and when to use CSS Grid. They seemed to do the same things. Over time, I developed a simple heuristic that saves me a lot of headaches.
I use Flexbox for components. If I have a button with an icon and text, or a navigation bar where items sit in a row, Flexbox is my go-to. It excels at one-dimensional layouts—either a row or a column.
I use Grid for page layouts. If I need to define the skeleton of a page (header, sidebar, main content, footer) or a gallery of cards that needs to line up in two dimensions, CSS Grid is superior. It lets me visualize the whitespace and the tracks explicitly.
Here is a pattern I use constantly for responsive card grids without media queries:
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
This snippet is magic. It tells the browser: “Fit as many columns as you can, but don’t let them get smaller than 300px. If there’s extra space, share it equally.” I don’t have to write a single breakpoint for mobile, tablet, or desktop. It just flows.
Modern CSS Features I Can’t Live Without
If you haven’t looked at CSS documentation since 2022, you are missing out on features that drastically reduce the amount of JavaScript you need to write.
CSS Variables (Custom Properties)
I don’t use preprocessors like SASS as much as I used to because CSS variables are native and dynamic. I define my theme at the top level:
:root {
--primary-color: #3b82f6;
--spacing-unit: 8px;
--font-stack: system-ui, -apple-system, sans-serif;
}
The best part? I can change these variables with JavaScript or inside a media query, and the browser updates the UI instantly. I use this for dark mode implementations. Instead of writing new CSS for every element, I just swap the variable values on the body tag.
Container Queries
This is the feature I waited years for. Media queries allow you to style based on the viewport size. But often, I want a component to change based on the size of its parent container. A card in a sidebar should look different than the same card in the main content area.
With container queries, I can make components truly modular. I write the component styles once, and they adapt to wherever I drop them. It looks something like this:
.card-container {
container-type: inline-size;
}
@container (min-width: 500px) {
.card {
display: flex; /* Switch to horizontal layout if container is wide enough */
}
}
I use this on almost every complex UI component now. It makes the design system robust and reusable across different projects.
CSS Nesting
Native CSS nesting is widely supported now. I used to rely on build tools for this syntax, but now I can write cleaner CSS directly in the browser. It keeps related styles grouped together, making the code much easier to read and maintain.
The Framework Debate: Tailwind vs. Vanilla
I have a love-hate relationship with CSS frameworks. I use Tailwind CSS on about half of my projects. It’s incredibly fast for prototyping and keeps consistency high because you are picking from a constrained set of values. You stop worrying about whether that margin should be 18px or 20px; you just use m-5.
However, I refuse to use Tailwind if I don’t understand the underlying CSS. I see developers use flex items-center justify-center without knowing what align-items or justify-content actually do. That’s dangerous. When the layout breaks (and it will), you need to know the raw CSS properties to fix it.
My advice? Learn Vanilla CSS first. Get comfortable with the cascade, specificity, and the box model. Once you feel confident fixing a layout bug in the inspector without looking up a cheat sheet, then grab a framework like Tailwind or Bootstrap to speed up your workflow.
My Project Setup and Workflow
When I start a frontend project, I have a specific ritual. It sets the stage for clean code later on.
1. The Reset
Browsers have different default styles. I always start with a modern CSS reset. I prefer a minimal one that sets box-sizing: border-box on everything. This property changes how width and height are calculated, including padding and borders in the total size. Without this, layout math is a nightmare.
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
2. Folder Structure
I keep my CSS modular. Even if I’m not using a component library like React, I try to separate my CSS into logical files. I usually have:
variables.css: Colors, fonts, spacing.global.css: Resets, typography defaults.layout.css: Grid systems, headers, footers.components/: Specific styles for buttons, cards, forms.
I then import these into a main stylesheet. This keeps things organized. I hate scrolling through a 3,000-line CSS file trying to find the style for a specific button hover state.
Debugging Strategies
I spend about 30% of my time writing code and 70% figuring out why it doesn’t work. The browser Developer Tools are my best friend. I don’t just use them to inspect elements; I use them to prototype.
If I’m struggling with a Grid layout, I turn on the Grid Inspector in Chrome or Firefox. It draws lines on the screen showing exactly where my columns and gaps are. It’s impossible to debug a complex grid blindly.
Another trick I use is the “outline method.” If a layout is shifting or overflowing and I can’t see why, I add this to my global CSS:
* {
outline: 1px solid red;
}
It looks ugly, but it instantly shows me the boundaries of every element. I can spot that rogue padding or invisible margin that’s pushing my content off the screen.
The Continuous Learning Path
The hardest part about web development is that the target keeps moving. What I wrote here is accurate for late 2025, but by 2026, we might have even newer viewport units or color functions. I keep up by building small things constantly.
I don’t just watch tutorials. I open a code editor and try to clone a landing page I like. I try to rebuild a complex UI element from Dribbble using only HTML and CSS. That is where the real learning happens. You hit a wall, you Google the specific problem, you read a specification on MDN or web.dev, and you solve it. That knowledge sticks way better than passively watching a video.
If you are stuck on where to start, pick a simple project. Build a personal portfolio. Don’t use a template. Write the HTML tag by tag. Style it until you hate it, then style it again. That struggle is the only way to get good.
Final Thoughts on the Craft
I view HTML and CSS as the most resilient parts of the web stack. JavaScript frameworks come and go. I’ve seen Angular 1 die, I’ve seen jQuery fade, and I’m sure React will eventually be replaced by something else. But the browser’s rendering engine remains. Understanding the fundamentals of the Document Object Model (DOM) and the Cascade is an investment that pays off for your entire career.
So, stop worrying about picking the “perfect” stack. Just open an index.html file and start typing. The best way to learn is to build something that breaks, and then fix it.




