Introduction to CSS Presentation Part 1
Updated: March 27, 2026
TL;DR
Modern CSS (2026) is about Container Queries, the :has() selector, Cascade Layers, and native nesting. Grid and Flexbox remain the layout foundations. CSS custom properties manage design tokens. Tailwind CSS 4 adds native nesting and performance improvements, but vanilla CSS can now do much of what Tailwind does.
If your last deep dive into CSS was a few years ago, prepare for surprises. CSS has evolved dramatically. The language that once felt limited—"just styling"—now handles layout complexity, logic (:has()), design tokens, and responsive adaptation that used to require JavaScript or build tools.
This guide covers modern CSS essentials for 2026: the core layout tools you've always needed (Grid, Flexbox), the revolutionary features (Container Queries), and the syntax improvements (native nesting) that make CSS more enjoyable to write. Whether you're starting fresh or catching up, understanding these fundamentals unlocks the rest.
CSS Grid and Flexbox: The Layout Foundation
Grid and Flexbox are not advanced—they're essential. Every modern layout uses one or both.
Flexbox: One-Dimensional Layout
Flexbox arranges items in a single direction (row or column).
/* Navigation bar: items in a row */
.nav {
display: flex;
gap: 1rem;
justify-content: space-between; /* Space between nav items and logo */
align-items: center; /* Vertically centered */
}
/* Form: items in a column */
.form {
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Responsive: row on desktop, column on mobile */
@media (max-width: 768px) {
.nav {
flex-direction: column;
}
}
Key properties:
flex-direction: row | columnjustify-content: Controls spacing along the main axis (row/column direction)align-items: Controls alignment perpendicular to main axisflex-wrap: Wrap items to multiple linesgap: Space between items
CSS Grid: Two-Dimensional Layout
Grid arranges items in rows and columns simultaneously.
/* Dashboard layout */
.dashboard {
display: grid;
grid-template-columns: 250px 1fr 300px; /* Sidebar, main, aside */
grid-template-rows: auto 1fr auto; /* Header, content, footer */
gap: 1rem;
height: 100vh;
}
.header {
grid-column: 1 / -1; /* Span all columns */
}
.sidebar {
grid-column: 1;
grid-row: 2;
}
.main {
grid-column: 2;
grid-row: 2;
}
.footer {
grid-column: 1 / -1;
}
The Difference: When to Use Each
| Layout | Flexbox | Grid |
|---|---|---|
| Navigation bar | ✓ | |
| Product list (1-column) | ✓ | |
| Form inputs | ✓ | |
| Dashboard (2D) | ✓ | |
| Card grid with subgrid | ✓ | |
| Sidebar + main content | ✓ |
Most complex layouts use both:
.dashboard {
display: grid;
grid-template-columns: 250px 1fr;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.card {
display: flex;
flex-direction: column;
}
Grid handles the page structure, Flexbox handles individual component layouts.
CSS Custom Properties (Variables)
Custom properties store reusable values. They're dynamic (can change at runtime) unlike SASS/LESS variables.
Basic Usage
:root {
--color-primary: #0066ff;
--color-text: #333;
--spacing-base: 1rem;
--radius: 0.5rem;
}
button {
background: var(--color-primary);
padding: var(--spacing-base);
border-radius: var(--radius);
color: white;
}
Dynamic Theming
/* Light theme (default) */
:root {
--bg: #ffffff;
--text: #000000;
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #ffffff;
}
}
/* User-selected dark mode */
body.dark-mode {
--bg: #1a1a1a;
--text: #ffffff;
}
body {
background: var(--bg);
color: var(--text);
}
Scoped Variables
.card {
--card-bg: #f5f5f5;
--card-padding: 1.5rem;
}
.card-header {
background: var(--card-bg);
padding: var(--card-padding);
}
/* Different card style */
.card.featured {
--card-bg: #ffd700;
--card-padding: 2rem;
/* Header automatically uses new values */
}
Container Queries: Components Adapt to Context
Previously, only media queries existed—they measure the viewport. Container Queries measure the component's container.
/* Define a container context */
.card-container {
container-type: inline-size;
}
/* Query the container's width */
@container (min-width: 400px) {
.card-content {
display: grid;
grid-template-columns: 1fr 1fr;
}
}
@container (max-width: 300px) {
.card-content {
display: block;
}
}
A component inside a 250px sidebar displays as a single column. The same component in a 800px main area displays as two columns. Same code, different contexts.
This is revolutionary because components are now truly reusable and context-aware.
The :has() Selector
:has() selects elements based on their children or siblings. It's "parent selection"—something CSS couldn't do before.
/* Select card if it contains an image */
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
/* Select form if it has an error */
.form:has(.error) {
border: 2px solid red;
}
/* Select heading followed by text */
h2:has(+ p) {
margin-bottom: 0.5rem;
}
/* Style based on input state */
form:has(input:invalid) {
background: #ffe0e0;
}
/* Dark mode: if user prefers dark, select background */
@media (prefers-color-scheme: dark) {
body:has(.dark-mode-toggle:checked) {
--bg: #1a1a1a;
}
}
:has() enables CSS-only responsive behavior without media queries.
Cascade Layers and CSS Nesting
Cascade Layers
Control specificity conflicts with layers:
/* Define layer order */
@layer reset, base, theme, components, utilities;
@layer reset {
* { margin: 0; padding: 0; }
}
@layer base {
body { font-family: system-ui; }
}
@layer theme {
--color-primary: blue;
}
@layer components {
.button { background: var(--color-primary); }
}
@layer utilities {
.text-center { text-align: center; }
}
/* Utilities override components override theme—determined by layer order */
Layers prevent specificity wars. A utility class in the utilities layer always overrides a component in the components layer, regardless of specificity.
Native CSS Nesting
No more repeating selectors:
/* Before: repetition */
.card { border: 1px solid #ccc; }
.card-header { background: #f5f5f5; }
.card-body { padding: 1rem; }
/* After: native nesting */
.card {
border: 1px solid #ccc;
& .header {
background: #f5f5f5;
}
& .body {
padding: 1rem;
}
/* Hover state */
&:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
/* Media query inside */
@media (max-width: 600px) {
flex-direction: column;
}
}
The & selector refers to the parent. This syntax is now native CSS (no Sass required).
View Transitions API
Animate between page states:
.modal {
view-transition-name: modal;
}
@supports (view-transition-name: modal) {
::view-transition-new(modal) {
animation: slideUp 0.5s;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(50px);
}
}
}
When you transition to a new modal state, the browser automatically animates between the old and new states.
document.startViewTransition(() => {
updateDOM(); // Change the DOM
// Browser automatically animates the transition
});
This creates smooth transitions without manual JavaScript animation logic.
Tailwind CSS 4 vs. Vanilla CSS
Tailwind CSS 4 (released January 22, 2025) now supports:
- Native nesting
- CSS variables for customization
- Reduced output size with better tree-shaking
When to use Tailwind:
- Rapid prototyping (pre-built responsive utilities)
- Team standardization (consistent spacing, colors)
- Complex responsive needs (with Container Queries support)
When vanilla CSS is sufficient:
- Simple, static sites
- Design systems that rarely change
- When build tools feel excessive
- Teams comfortable with CSS
Example: Same component in Tailwind vs. vanilla:
Tailwind:
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 p-4">
<div className="border rounded-lg p-4 hover:shadow-lg">
<h3 className="text-lg font-bold mb-2">Title</h3>
<p>Content</p>
</div>
</div>
Vanilla CSS with modern features:
<div className="card-grid">
<article className="card">
<h3>Title</h3>
<p>Content</p>
</article>
</div>
<style>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
padding: 1rem;
}
.card {
border: 1px solid #ccc;
border-radius: 0.5rem;
padding: 1rem;
&:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
}
</style>
Both are valid. Choose based on project needs, not hype.
@starting-style for Transitions
Animate elements when they first appear:
.toast {
opacity: 0;
transform: translateX(-100%);
transition: all 0.3s;
}
@starting-style {
.toast {
opacity: 0;
transform: translateX(-100%);
}
}
.toast.show {
opacity: 1;
transform: translateX(0);
}
When a toast appears, it transitions from the @starting-style values to the normal values.
Key Takeaways
Modern CSS (2026) handles what used to require JavaScript or build tools:
- Grid and Flexbox: Master these for any layout
- Custom properties: Dynamic, scoped design tokens
- Container Queries: Context-aware component styles
:has()selector: CSS-only logic based on child elements- Native nesting: Cleaner, DRY syntax
- Cascade Layers: Managed specificity
- View Transitions API: Smooth state transitions
- @starting-style: Entry animations
CSS is no longer just styling. It's a powerful language for layout, logic, and interaction. The more you learn modern CSS, the less JavaScript you need for presentation.