How CSS Actually Works
CSS stands for Cascading Style Sheets. It controls how HTML elements look — colors, sizes, spacing, layout, animation, everything visual.
Here's the mental model: HTML is the structure (walls, rooms, floors). CSS is the interior design (paint, furniture, lighting).
h1 {
color: navy;
font-size: 2rem;
}This says: "Find every <h1> on the page and make its text navy-colored and 2rem tall."
Every CSS rule has two parts:
- Selector — what to style (
h1) - Declaration block — how to style it (
{ color: navy; })
Selectors
Selectors are how you point at elements. Master these and you can target anything.
Basic Selectors
/* Element selector — targets all <p> tags */
p { color: gray; }
/* Class selector — targets elements with class="card" */
.card { border: 1px solid #ddd; }
/* ID selector — targets the one element with id="header" */
#header { background: black; }
/* Universal selector — targets everything */
* { box-sizing: border-box; }Combinators
/* Descendant — any <p> inside .card, at any depth */
.card p { margin: 0; }
/* Child — only direct <p> children of .card */
.card > p { font-weight: bold; }
/* Adjacent sibling — the <p> immediately after an <h2> */
h2 + p { margin-top: 0; }
/* General sibling — all <p> elements after an <h2> at the same level */
h2 ~ p { color: gray; }Pseudo-classes
These target elements in a specific state.
a:hover { color: tomato; } /* mouse is over it */
input:focus { outline: 2px solid blue; } /* it's focused */
li:first-child { font-weight: bold; } /* first item in list */
li:last-child { border-bottom: none; } /* last item */
li:nth-child(odd) { background: #f9f9f9; } /* odd rows */
button:disabled { opacity: 0.5; } /* disabled state */Pseudo-elements
These style a part of an element.
p::first-line { font-weight: bold; } /* only the first line */
p::first-letter { font-size: 2em; } /* drop cap effect */
.tooltip::before { content: "💡 "; } /* insert content before */
.required::after { content: " *"; color: red; } /* insert after */Attribute Selectors
input[type="text"] { border: 1px solid #ccc; }
a[href^="https"] { color: green; } /* starts with https */
a[href$=".pdf"] { color: red; } /* ends with .pdf */
img[alt] { border: none; } /* has an alt attribute */The Cascade, Specificity, and Inheritance
These three concepts explain why one style wins over another. Interviewers love asking about this.
The Cascade
When multiple rules target the same element, the browser follows this order (last wins):
- Browser default styles
- Your stylesheets (in order they appear)
- Rules later in the same file override earlier ones
p { color: blue; }
p { color: red; } /* red wins — it comes later */Specificity
When two rules target the same element, the more specific one wins, regardless of order.
Specificity is calculated as a score with three buckets: (ID, Class, Element)
| Selector | Score | Why |
|---|---|---|
p | 0-0-1 | 1 element |
.card | 0-1-0 | 1 class |
#header | 1-0-0 | 1 ID |
.card p | 0-1-1 | 1 class + 1 element |
#header .nav a | 1-1-1 | 1 ID + 1 class + 1 element |
Higher score wins. IDs beat classes. Classes beat elements.
.card p { color: blue; } /* specificity: 0-1-1 */
p { color: red; } /* specificity: 0-0-1 */
/* blue wins because 0-1-1 > 0-0-1 */!important overrides everything. Avoid it. If you're using !important, you probably have a specificity problem you should fix instead.
Inheritance
Some properties are inherited by child elements automatically. Others are not.
Inherited (flow down to children): color, font-family, font-size, line-height, text-align, visibility, cursor
Not inherited (stay on the element): margin, padding, border, background, width, height, display, position
body {
font-family: system-ui; /* every child inherits this */
color: #333; /* every child inherits this */
}
.card {
border: 1px solid #ddd; /* children do NOT inherit this */
}You can force inheritance with inherit or reset with initial:
.child { border: inherit; } /* now it inherits parent's border */
.reset { color: initial; } /* back to browser default */The Box Model
Every element on a page is a rectangular box. The box model describes what makes up that rectangle.
┌─────────────────────────── margin ──────────────────────────┐
│ ┌──────────────────────── border ──────────────────────┐ │
│ │ ┌───────────────────── padding ─────────────────┐ │ │
│ │ │ │ │ │
│ │ │ content area │ │ │
│ │ │ (width × height) │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
- Content — the text, image, or whatever is inside
- Padding — space between the content and the border
- Border — a line around the padding
- Margin — space outside the border, pushing other elements away
box-sizing
By default, width only sets the content width. Padding and border are added on top of it. This makes math annoying.
Fix it with box-sizing: border-box — now width includes padding and border.
/* Always do this at the top of every project */
*, *::before, *::after {
box-sizing: border-box;
}This is the single most important CSS reset. Every framework, library, and design system uses it.
Margin Collapse
When two vertical margins touch, they collapse into one (the larger wins). This only happens vertically, never horizontally.
h2 { margin-bottom: 20px; }
p { margin-top: 30px; }
/* The gap between h2 and p is 30px, not 50px */This catches everyone off guard. If spacing looks wrong, margin collapse is usually why.
Units
| Unit | What It Means | When to Use |
|---|---|---|
px | Absolute pixels | Borders, shadows, small fixed sizes |
rem | Relative to root font-size (usually 16px) | Font sizes, spacing, most things |
em | Relative to parent's font-size | Component-scoped scaling |
% | Percentage of parent | Widths, responsive containers |
vw / vh | % of viewport width / height | Full-screen sections, hero banners |
fr | Fraction of available space (grid only) | Grid column/row sizing |
ch | Width of the "0" character | Max-width for readable text |
Best practice: Use rem for most things. It scales with user font preferences and makes your site accessible.
html { font-size: 16px; } /* 1rem = 16px */
h1 { font-size: 2rem; } /* 32px */
p { font-size: 1rem; } /* 16px */
.container { max-width: 65ch; } /* ~65 characters wide — ideal reading width */Display and Positioning
Display
The display property controls how an element behaves in the layout.
.block { display: block; } /* takes full width, stacks vertically */
.inline { display: inline; } /* sits in line with text, ignores width/height */
.ib { display: inline-block; } /* inline but respects width/height */
.none { display: none; } /* completely removed from layout */
.flex { display: flex; } /* flexbox container */
.grid { display: grid; } /* grid container */Position
.static { position: static; } /* default — normal flow */
.relative { position: relative; } /* stays in flow, can be nudged with top/left */
.absolute { position: absolute; } /* removed from flow, positioned relative to nearest positioned ancestor */
.fixed { position: fixed; } /* removed from flow, positioned relative to viewport */
.sticky { position: sticky; top: 0; } /* normal flow until scroll threshold, then sticks */Key concept: absolute positions relative to the nearest ancestor that has position: relative (or absolute, fixed, sticky). If there's no positioned ancestor, it positions relative to the <body>.
/* Common pattern: position a badge inside a card */
.card {
position: relative; /* establishes positioning context */
}
.badge {
position: absolute;
top: 8px;
right: 8px;
}Stacking with z-index
z-index controls which element appears on top when they overlap. It only works on positioned elements (not static).
.modal-backdrop { position: fixed; z-index: 100; }
.modal { position: fixed; z-index: 101; } /* on top of backdrop */
.tooltip { position: absolute; z-index: 200; } /* on top of modal */Flexbox
Flexbox is for one-dimensional layout — laying things out in a row or a column.
The Container
.container {
display: flex;
flex-direction: row; /* row (default) | column */
justify-content: center; /* alignment along the main axis */
align-items: center; /* alignment along the cross axis */
gap: 16px; /* space between items */
flex-wrap: wrap; /* allow items to wrap to next line */
}Main Axis vs Cross Axis
flex-direction: row→ main axis is horizontal, cross axis is verticalflex-direction: column→ main axis is vertical, cross axis is horizontal
justify-content always controls the main axis. align-items always controls the cross axis.
justify-content Values
justify-content: flex-start; /* pack items to the start */
justify-content: flex-end; /* pack items to the end */
justify-content: center; /* center items */
justify-content: space-between; /* equal space between, none at edges */
justify-content: space-around; /* equal space around each item */
justify-content: space-evenly; /* perfectly equal gaps everywhere */align-items Values
align-items: flex-start; /* align to the top (or left in column) */
align-items: flex-end; /* align to the bottom */
align-items: center; /* vertically center */
align-items: stretch; /* stretch to fill (default) */
align-items: baseline; /* align text baselines */The Children
.item {
flex-grow: 1; /* how much this item should grow relative to others */
flex-shrink: 0; /* don't shrink smaller than your content */
flex-basis: 200px; /* ideal starting width before grow/shrink */
}
/* Shorthand: flex: grow shrink basis */
.item { flex: 1 0 200px; }
/* Common shortcuts */
.item { flex: 1; } /* grow equally, shrink equally, basis 0 */
.item { flex: none; } /* don't grow, don't shrink, use content size */Centering Anything
The most common interview question: "How do you center a div?"
.parent {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh; /* if you want it centered on the full page */
}That's it. Four lines.
CSS Grid
Grid is for two-dimensional layout — rows AND columns at the same time.
Defining a Grid
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr; /* 3 equal columns */
grid-template-rows: auto auto; /* rows size to content */
gap: 16px; /* space between cells */
}The fr unit means "fraction of available space." 1fr 2fr 1fr gives you three columns where the middle one is twice as wide.
Handy Functions
/* repeat: avoid writing 1fr twelve times */
grid-template-columns: repeat(12, 1fr);
/* minmax: columns are at least 250px, at most 1fr */
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
/* auto-fill creates as many columns as fit — fully responsive, no media queries */That repeat(auto-fill, minmax(250px, 1fr)) line is one of the most powerful patterns in CSS. It creates a responsive grid that adapts to any screen width without a single media query.
Placing Items
.item {
grid-column: 1 / 3; /* span from column line 1 to line 3 (2 columns wide) */
grid-row: 1 / 2; /* span row 1 */
}
/* Shorthand: span N */
.wide { grid-column: span 2; } /* span 2 columns from wherever it lands */
.tall { grid-row: span 3; } /* span 3 rows */Named Grid Areas
For full page layouts, named areas are incredibly readable.
.layout {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 60px 1fr 40px;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }Grid vs Flexbox — When to Use Which
| Use Case | Use |
|---|---|
| Navigation bar | Flexbox |
| Card grid | Grid |
| Centering one thing | Flexbox |
| Full page layout | Grid |
| Items in a single row/column | Flexbox |
| Items in rows AND columns | Grid |
| Don't know how many items | Flexbox (with wrap) or Grid (with auto-fill) |
Use both together. Grid for the page layout, flexbox for components inside the grid cells.
Responsive Design
Responsive design means your site works on any screen size.
Media Queries
/* Mobile-first: default styles apply to small screens */
.container {
padding: 16px;
}
/* Then enhance for larger screens */
@media (min-width: 768px) {
.container {
padding: 32px;
max-width: 720px;
margin: 0 auto;
}
}
@media (min-width: 1024px) {
.container {
max-width: 960px;
}
}Mobile-first means writing your base CSS for small screens and using min-width queries to add complexity for larger ones. This approach leads to simpler, more maintainable code.
Common Breakpoints
/* Small phones */ @media (min-width: 480px) { }
/* Tablets */ @media (min-width: 768px) { }
/* Laptops */ @media (min-width: 1024px) { }
/* Desktops */ @media (min-width: 1280px) { }Container Queries
Container queries let a component respond to its parent's size instead of the viewport. This is a game-changer for reusable components.
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
flex-direction: row; /* horizontal layout when there's room */
}
}CSS Custom Properties (Variables)
Variables let you define values once and reuse them everywhere. They're the backbone of design systems.
:root {
--color-primary: #6366f1;
--color-primary-light: #818cf8;
--color-text: #1f2937;
--color-muted: #6b7280;
--color-bg: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--radius-full: 9999px;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}Use them anywhere:
.button {
background: var(--color-primary);
color: white;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-md);
font-family: var(--font-sans);
}Dark Mode with Variables
Override the variables in a dark context — every component updates automatically.
@media (prefers-color-scheme: dark) {
:root {
--color-text: #f9fafb;
--color-muted: #9ca3af;
--color-bg: #111827;
--color-surface: #1f2937;
--color-border: #374151;
}
}Zero component changes needed. Every button, card, and text element switches automatically.
Transitions and Animations
Transitions
Transitions smoothly animate a property change.
.button {
background: var(--color-primary);
transition: background 200ms ease, transform 200ms ease;
}
.button:hover {
background: var(--color-primary-light);
transform: translateY(-1px);
}Format: transition: property duration timing-function delay
Common timing functions:
ease— starts fast, slows down (best default)ease-in-out— slow start, fast middle, slow endlinear— constant speed
Keyframe Animations
For more complex, multi-step animations.
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeIn 300ms ease forwards;
}prefers-reduced-motion
Some users get motion sickness from animations. Always respect their preference.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}Typography
Good typography makes or breaks a design.
body {
font-family: var(--font-sans);
font-size: 1rem; /* 16px base */
line-height: 1.6; /* comfortable reading */
color: var(--color-text);
-webkit-font-smoothing: antialiased;
}
h1 { font-size: 2.25rem; line-height: 1.2; font-weight: 700; }
h2 { font-size: 1.75rem; line-height: 1.3; font-weight: 600; }
h3 { font-size: 1.375rem; line-height: 1.4; font-weight: 600; }
p + p { margin-top: 1em; } /* space between paragraphs */
.prose { max-width: 65ch; } /* ideal reading width */A Type Scale
A type scale gives your headings consistent, harmonious sizes. Multiply by a ratio (1.25 is a good start).
base: 1rem (16px)
h4: 1.25rem (20px) × 1.25
h3: 1.563rem (25px) × 1.25
h2: 1.953rem (31px) × 1.25
h1: 2.441rem (39px) × 1.25
Building a Component Library
Now let's use everything to build real, reusable components. These are the building blocks of a design system.
The Reset
Every component library starts with a CSS reset to eliminate browser inconsistencies.
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
-webkit-text-size-adjust: 100%;
}
body {
font-family: var(--font-sans);
font-size: 1rem;
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
-webkit-font-smoothing: antialiased;
}
img, video, svg {
display: block;
max-width: 100%;
}
button, input, select, textarea {
font: inherit;
color: inherit;
}
a {
color: inherit;
text-decoration: none;
}
ul, ol {
list-style: none;
}Button Component
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
font-size: 0.875rem;
font-weight: 500;
line-height: 1;
border: 1px solid transparent;
border-radius: var(--radius-md);
cursor: pointer;
transition: background 150ms ease, transform 150ms ease, box-shadow 150ms ease;
white-space: nowrap;
user-select: none;
}
.btn:active {
transform: scale(0.98);
}
.btn:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* Variants */
.btn-primary {
background: var(--color-primary);
color: white;
}
.btn-primary:hover { background: var(--color-primary-light); }
.btn-secondary {
background: var(--color-surface);
color: var(--color-text);
border-color: var(--color-border);
}
.btn-secondary:hover { background: var(--color-border); }
.btn-ghost {
background: transparent;
color: var(--color-text);
}
.btn-ghost:hover { background: var(--color-surface); }
/* Sizes */
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
.btn-lg { padding: 0.75rem 1.5rem; font-size: 1rem; }
/* Disabled */
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}<button class="btn btn-primary">Save</button>
<button class="btn btn-secondary">Cancel</button>
<button class="btn btn-ghost btn-sm">Learn more</button>Card Component
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
transition: transform 200ms ease, box-shadow 200ms ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.card-img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.card-body {
padding: var(--spacing-lg);
}
.card-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: var(--spacing-xs);
}
.card-text {
color: var(--color-muted);
font-size: 0.875rem;
line-height: 1.5;
}
.card-footer {
padding: var(--spacing-sm) var(--spacing-lg);
border-top: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
}<div class="card">
<img class="card-img" src="photo.jpg" alt="Project screenshot" />
<div class="card-body">
<h3 class="card-title">Project Alpha</h3>
<p class="card-text">A brief description of the project and what it does.</p>
</div>
<div class="card-footer">
<span class="text-muted">Mar 2026</span>
<button class="btn btn-ghost btn-sm">View</button>
</div>
</div>Input Component
.input-group {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.label {
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text);
}
.input {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-bg);
font-size: 1rem;
transition: border-color 150ms ease, box-shadow 150ms ease;
}
.input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
}
.input::placeholder {
color: var(--color-muted);
}
.input-error {
border-color: #ef4444;
}
.input-error:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15);
}
.helper-text {
font-size: 0.75rem;
color: var(--color-muted);
}
.error-text {
font-size: 0.75rem;
color: #ef4444;
}<div class="input-group">
<label class="label" for="email">Email</label>
<input class="input" type="email" id="email" placeholder="you@example.com" />
<span class="helper-text">We'll never share your email.</span>
</div>Badge Component
.badge {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: var(--radius-full);
line-height: 1.4;
}
.badge-primary { background: rgba(99, 102, 241, 0.1); color: #6366f1; }
.badge-success { background: rgba(34, 197, 94, 0.1); color: #16a34a; }
.badge-warning { background: rgba(234, 179, 8, 0.1); color: #ca8a04; }
.badge-error { background: rgba(239, 68, 68, 0.1); color: #dc2626; }<span class="badge badge-success">Active</span>
<span class="badge badge-warning">Pending</span>
<span class="badge badge-error">Failed</span>Avatar Component
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-full);
overflow: hidden;
background: var(--color-primary);
color: white;
font-weight: 600;
flex-shrink: 0;
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-sm { width: 32px; height: 32px; font-size: 0.75rem; }
.avatar-md { width: 40px; height: 40px; font-size: 0.875rem; }
.avatar-lg { width: 56px; height: 56px; font-size: 1.125rem; }
.avatar-group {
display: flex;
}
.avatar-group .avatar {
border: 2px solid var(--color-bg);
margin-left: -8px;
}
.avatar-group .avatar:first-child {
margin-left: 0;
}<div class="avatar avatar-md">
<img src="user.jpg" alt="Kevin" />
</div>
<div class="avatar avatar-md">K</div>
<div class="avatar-group">
<div class="avatar avatar-sm">A</div>
<div class="avatar avatar-sm">B</div>
<div class="avatar avatar-sm">C</div>
</div>Modal Component
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
animation: fadeIn 150ms ease;
}
.modal {
background: var(--color-bg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
width: 90%;
max-width: 480px;
max-height: 85vh;
overflow-y: auto;
animation: slideUp 200ms ease;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-lg);
border-bottom: 1px solid var(--color-border);
}
.modal-title {
font-size: 1.125rem;
font-weight: 600;
}
.modal-close {
background: none;
border: none;
font-size: 1.25rem;
cursor: pointer;
color: var(--color-muted);
padding: var(--spacing-xs);
border-radius: var(--radius-sm);
}
.modal-close:hover { background: var(--color-surface); }
.modal-body {
padding: var(--spacing-lg);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-sm);
padding: var(--spacing-lg);
border-top: 1px solid var(--color-border);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(16px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}<div class="modal-backdrop">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">Confirm Action</h2>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
<p>Are you sure you want to proceed?</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary">Cancel</button>
<button class="btn btn-primary">Confirm</button>
</div>
</div>
</div>Design System Principles
A design system is more than a component library. It's a set of rules and constraints that keep your UI consistent.
1. Use a Spacing Scale
Don't use random pixel values. Pick a scale and stick to it.
4px — 8px — 12px — 16px — 24px — 32px — 48px — 64px
That's 0.25rem to 4rem in multiples of 4. Every margin, padding, and gap in your app should use one of these values.
2. Limit Your Colors
A good palette has:
- 1 primary color (and 1-2 lighter/darker shades)
- 1 text color + 1 muted text color
- 1 background + 1 surface (slightly different background for cards)
- 1 border color
- 3 semantic colors: success (green), warning (yellow), error (red)
That's it. 10-12 colors total. More than that and your UI starts to look inconsistent.
3. Limit Your Font Sizes
Use 5-7 sizes maximum:
--text-xs: 0.75rem; /* 12px — captions, labels */
--text-sm: 0.875rem; /* 14px — secondary text */
--text-base: 1rem; /* 16px — body text */
--text-lg: 1.125rem; /* 18px — emphasized text */
--text-xl: 1.25rem; /* 20px — small headings */
--text-2xl: 1.5rem; /* 24px — section headings */
--text-3xl: 2rem; /* 32px — page headings */4. Consistent Border Radius
Pick 3-4 radius values:
--radius-sm: 4px; /* small elements like badges */
--radius-md: 8px; /* buttons, inputs */
--radius-lg: 16px; /* cards, modals */
--radius-full: 9999px; /* circles, pills */5. Elevation with Shadows
Shadows create visual hierarchy. Use 3 levels:
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); /* cards at rest */
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07); /* cards on hover, dropdowns */
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); /* modals, popovers */Common Interview Questions
What is the difference between em and rem?
em is relative to the parent element's font-size. rem is relative to the root (<html>) font-size. rem is more predictable because it doesn't compound — if you nest elements using em, the size multiplies with each level.
What is BEM?
BEM (Block Element Modifier) is a naming convention: .block__element--modifier.
.card { } /* Block */
.card__title { } /* Element (part of card) */
.card__title--large { } /* Modifier (variation) */It prevents naming collisions and makes your CSS predictable and easy to read. BEM is widely used in design systems.
What's the difference between display: none and visibility: hidden?
display: none— removes the element from the layout entirely. It takes up no space.visibility: hidden— hides the element visually but it still takes up space in the layout.
How does position: sticky work?
The element behaves like relative until the user scrolls past a threshold (set by top, bottom, etc.), then it behaves like fixed within its containing block.
.nav { position: sticky; top: 0; }What is a stacking context?
A stacking context is a three-dimensional layer for rendering. Elements within a stacking context are ordered by z-index, but they can't escape their parent's context. New stacking contexts are created by:
positionwith az-indexopacityless than 1transform,filter, orwill-changeisolation: isolate
What is contain and how does it help performance?
contain tells the browser that an element's internals won't affect the rest of the page, enabling rendering optimizations.
.card { contain: layout style paint; }
/* or the shorthand: */
.card { contain: content; }How do you handle specificity conflicts in a large codebase?
- Use flat selectors (single class names) to keep specificity low and equal
- Use a consistent methodology like BEM
- Layer your CSS: reset → tokens → base → components → utilities
- Use CSS Layers (
@layer) for explicit ordering
@layer reset, tokens, base, components, utilities;
@layer components {
.btn { background: var(--color-primary); }
}
@layer utilities {
.mt-4 { margin-top: 1rem; } /* utilities always win */
}What is @supports used for?
Feature detection in CSS. Only apply styles if the browser supports a property.
@supports (display: grid) {
.layout { display: grid; }
}
@supports not (container-type: inline-size) {
/* fallback for browsers without container queries */
.card { width: 100%; }
}Full Design System Demo
Here's a complete, single-file page that uses every concept and component from this guide. Save it as design-system.html and open it in your browser.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mini Design System</title>
<style>
/* ============ TOKENS ============ */
:root {
--color-primary: #6366f1;
--color-primary-light: #818cf8;
--color-text: #1f2937;
--color-muted: #6b7280;
--color-bg: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
--color-success: #16a34a;
--color-warning: #ca8a04;
--color-error: #dc2626;
--font-sans: system-ui, -apple-system, sans-serif;
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--radius-full: 9999px;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px rgba(0,0,0,0.07);
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #f9fafb;
--color-muted: #9ca3af;
--color-bg: #111827;
--color-surface: #1f2937;
--color-border: #374151;
}
}
/* ============ RESET ============ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 16px; }
body {
font-family: var(--font-sans);
color: var(--color-text);
background: var(--color-bg);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
img { display: block; max-width: 100%; }
button, input { font: inherit; color: inherit; }
ul { list-style: none; }
a { color: inherit; text-decoration: none; }
/* ============ LAYOUT ============ */
.page {
display: grid;
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"footer";
min-height: 100vh;
}
.page-header {
grid-area: header;
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md) var(--spacing-xl);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 0;
background: var(--color-bg);
z-index: 10;
}
.logo {
font-size: 1.25rem;
font-weight: 700;
color: var(--color-primary);
}
.nav-links { display: flex; gap: var(--spacing-lg); }
.nav-links a {
font-size: 0.875rem;
color: var(--color-muted);
transition: color 150ms;
}
.nav-links a:hover { color: var(--color-text); }
.page-main {
grid-area: main;
padding: var(--spacing-2xl) var(--spacing-xl);
max-width: 960px;
width: 100%;
margin: 0 auto;
}
.page-footer {
grid-area: footer;
text-align: center;
padding: var(--spacing-lg);
font-size: 0.75rem;
color: var(--color-muted);
border-top: 1px solid var(--color-border);
}
/* ============ TYPOGRAPHY ============ */
.hero-title {
font-size: 2.25rem;
font-weight: 700;
line-height: 1.2;
margin-bottom: var(--spacing-sm);
}
.hero-subtitle {
font-size: 1.125rem;
color: var(--color-muted);
max-width: 50ch;
margin-bottom: var(--spacing-xl);
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: var(--spacing-lg);
}
/* ============ BUTTONS ============ */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
font-size: 0.875rem;
font-weight: 500;
border: 1px solid transparent;
border-radius: var(--radius-md);
cursor: pointer;
transition: background 150ms, transform 150ms;
white-space: nowrap;
}
.btn:active { transform: scale(0.98); }
.btn-primary { background: var(--color-primary); color: white; }
.btn-primary:hover { background: var(--color-primary-light); }
.btn-secondary {
background: var(--color-surface);
color: var(--color-text);
border-color: var(--color-border);
}
.btn-secondary:hover { background: var(--color-border); }
.btn-sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
/* ============ BADGES ============ */
.badge {
display: inline-flex;
padding: 0.125rem 0.5rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: var(--radius-full);
}
.badge-primary { background: rgba(99,102,241,0.1); color: #6366f1; }
.badge-success { background: rgba(34,197,94,0.1); color: var(--color-success); }
.badge-warning { background: rgba(234,179,8,0.1); color: var(--color-warning); }
.badge-error { background: rgba(239,68,68,0.1); color: var(--color-error); }
/* ============ CARDS ============ */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
}
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
transition: transform 200ms, box-shadow 200ms;
}
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.card-body { padding: var(--spacing-lg); }
.card-title { font-size: 1.125rem; font-weight: 600; margin-bottom: var(--spacing-xs); }
.card-text { color: var(--color-muted); font-size: 0.875rem; line-height: 1.5; }
.card-footer {
padding: var(--spacing-sm) var(--spacing-lg);
border-top: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.75rem;
color: var(--color-muted);
}
/* ============ INPUTS ============ */
.input-group { display: flex; flex-direction: column; gap: var(--spacing-xs); }
.label { font-size: 0.875rem; font-weight: 500; }
.input {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-bg);
font-size: 1rem;
transition: border-color 150ms, box-shadow 150ms;
}
.input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(99,102,241,0.15);
}
.input::placeholder { color: var(--color-muted); }
/* ============ AVATARS ============ */
.avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px; height: 36px;
border-radius: var(--radius-full);
background: var(--color-primary);
color: white;
font-weight: 600;
font-size: 0.875rem;
flex-shrink: 0;
}
.avatar-group { display: flex; }
.avatar-group .avatar {
border: 2px solid var(--color-bg);
margin-left: -8px;
}
.avatar-group .avatar:first-child { margin-left: 0; }
/* ============ DEMO SECTIONS ============ */
.demo-section {
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-2xl);
border-bottom: 1px solid var(--color-border);
}
.demo-section:last-child { border-bottom: none; }
.demo-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-md);
margin-top: var(--spacing-md);
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-lg);
max-width: 480px;
margin-top: var(--spacing-md);
}
.form-full { grid-column: span 2; }
/* ============ ANIMATIONS ============ */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in { animation: fadeIn 400ms ease forwards; }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* ============ RESPONSIVE ============ */
@media (max-width: 640px) {
.hero-title { font-size: 1.75rem; }
.form-grid { grid-template-columns: 1fr; }
.form-full { grid-column: span 1; }
.nav-links { display: none; }
}
</style>
</head>
<body>
<div class="page">
<header class="page-header">
<span class="logo">DesignKit</span>
<nav class="nav-links">
<a href="#">Components</a>
<a href="#">Tokens</a>
<a href="#">Docs</a>
</nav>
</header>
<main class="page-main fade-in">
<!-- Hero -->
<div class="demo-section">
<h1 class="hero-title">Mini Design System</h1>
<p class="hero-subtitle">
A complete design system built with vanilla CSS, custom properties, and zero dependencies.
</p>
<div class="demo-row">
<button class="btn btn-primary">Get Started</button>
<button class="btn btn-secondary">View on GitHub</button>
</div>
</div>
<!-- Buttons -->
<div class="demo-section">
<h2 class="section-title">Buttons</h2>
<div class="demo-row">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary" disabled>Disabled</button>
</div>
</div>
<!-- Badges -->
<div class="demo-section">
<h2 class="section-title">Badges</h2>
<div class="demo-row">
<span class="badge badge-primary">New</span>
<span class="badge badge-success">Active</span>
<span class="badge badge-warning">Pending</span>
<span class="badge badge-error">Failed</span>
</div>
</div>
<!-- Avatars -->
<div class="demo-section">
<h2 class="section-title">Avatars</h2>
<div class="demo-row">
<div class="avatar">K</div>
<div class="avatar" style="background: #ec4899;">A</div>
<div class="avatar" style="background: #14b8a6;">M</div>
<div class="avatar-group">
<div class="avatar">K</div>
<div class="avatar" style="background: #ec4899;">A</div>
<div class="avatar" style="background: #14b8a6;">M</div>
<div class="avatar" style="background: #f59e0b;">+2</div>
</div>
</div>
</div>
<!-- Form -->
<div class="demo-section">
<h2 class="section-title">Form Inputs</h2>
<div class="form-grid">
<div class="input-group">
<label class="label" for="first">First name</label>
<input class="input" type="text" id="first" placeholder="Kevin" />
</div>
<div class="input-group">
<label class="label" for="last">Last name</label>
<input class="input" type="text" id="last" placeholder="De Asis" />
</div>
<div class="input-group form-full">
<label class="label" for="demo-email">Email</label>
<input class="input" type="email" id="demo-email" placeholder="kevin@example.com" />
</div>
<div class="form-full">
<button class="btn btn-primary">Submit</button>
</div>
</div>
</div>
<!-- Cards -->
<div class="demo-section">
<h2 class="section-title">Cards</h2>
<div class="card-grid">
<div class="card">
<div class="card-body">
<h3 class="card-title">Authentication</h3>
<p class="card-text">Secure login flow with OAuth, MFA, and session management baked in.</p>
<div class="demo-row">
<span class="badge badge-success">Stable</span>
</div>
</div>
<div class="card-footer">
<span>v2.1.0</span>
<button class="btn btn-secondary btn-sm">Docs</button>
</div>
</div>
<div class="card">
<div class="card-body">
<h3 class="card-title">Data Grid</h3>
<p class="card-text">Sortable, filterable tables with pagination and virtual scrolling.</p>
<div class="demo-row">
<span class="badge badge-warning">Beta</span>
</div>
</div>
<div class="card-footer">
<span>v0.9.3</span>
<button class="btn btn-secondary btn-sm">Docs</button>
</div>
</div>
<div class="card">
<div class="card-body">
<h3 class="card-title">Charts</h3>
<p class="card-text">Responsive, accessible charts built on SVG with smooth animations.</p>
<div class="demo-row">
<span class="badge badge-primary">New</span>
</div>
</div>
<div class="card-footer">
<span>v1.0.0</span>
<button class="btn btn-secondary btn-sm">Docs</button>
</div>
</div>
</div>
</div>
</main>
<footer class="page-footer">
Built with vanilla CSS. No frameworks, no build tools, just the platform.
</footer>
</div>
</body>
</html>Try It
Save that file as design-system.html, open it in your browser, and resize the window. The layout adapts, cards reflow, and if your OS is in dark mode, the colors switch automatically. All with zero JavaScript and zero dependencies.
Summary
| Topic | Key Takeaway |
|---|---|
| Selectors | querySelector + class selectors cover 95% of cases |
| Specificity | Keep it flat. One class per rule. Avoid IDs and !important |
| Box Model | Always use box-sizing: border-box |
| Units | Use rem for sizing, px for borders/shadows |
| Flexbox | One-dimensional. justify-content = main axis, align-items = cross axis |
| Grid | Two-dimensional. repeat(auto-fill, minmax(250px, 1fr)) is magic |
| Responsive | Mobile-first with min-width media queries |
| Variables | Define tokens in :root, override for dark mode |
| Components | Build with flat classes, CSS variables, and BEM naming |
| Design System | Constrain spacing, colors, radii, and shadows to fixed scales |
CSS is not hard. It's just wide. There are many properties, but the concepts underneath are few: the box model, the cascade, layout modes, and the idea that constraints create consistency. Master these and you can build anything.
You might also like
Build Connect Four from Scratch with Vanilla JS
A complete guide to building Connect Four using only HTML, CSS, and vanilla JavaScript. Covers gravity-based piece dropping, win detection across rows/columns/diagonals, and AI opponent. Interview-ready implementation.
FrontendBuild a Hierarchical Checkbox from Scratch with Vanilla JS
A complete guide to building a hierarchical (tri-state) checkbox tree using only HTML, CSS, and vanilla JavaScript. Covers parent-child propagation, indeterminate state, dynamic trees, and accessibility. Interview-ready implementation.
FrontendJavaScript Promises — From Zero to Interview Ready
A beginner-friendly deep dive into Promises. Understand the concept, master the API, and learn to implement custom Promise utilities that interviewers love to ask.