Style pages with selectors, the box model, flexbox, and more.
A selector decides which elements a rule applies to. A tag name (p { }) selects every matching element on the page, .card { } selects every element with class="card", and #header { } selects the one element with id="header". A rule is written as selector { property: value; }.
color sets text color. CSS accepts color in several equivalent formats: named colors (teal, coral), hex codes (#3498db), rgb(52, 152, 219), and hsl(204, 70%, 53%) — hex and named colors are the most common for a fixed color, hsl is often easier to reason about when you want to programmatically lighten/darken a shade.
Selectors can combine: .card p { } selects only <p> elements *inside* an element with class="card" (a descendant selector), while .card.featured { } (no space) selects an element that has *both* classes at once.
p {
color: teal;
}
.warning {
color: red;
}
#site-header {
color: navy;
}.card p { } /* a <p> inside .card */
.card.featured { } /* has both classes */Every element is a rectangular box made of four layers, from the inside out: content (the actual text/image), padding (space between the content and the border), border (a visible or invisible edge), and margin (space outside the border, separating this box from its neighbors).
By default, width and height set only the *content* box's size — padding and border are added on top of that, so a 200px-wide box with 20px of padding and a 1px border actually occupies 242px total. This trips up almost everyone at least once.
box-sizing: border-box changes width/height to mean the *total* size instead, padding and border included — most modern CSS resets set this globally (* { box-sizing: border-box; }) specifically because it matches how most people intuitively expect sizing to work.
.card {
padding: 20px;
border: 1px solid black;
}* {
box-sizing: border-box;
}display: flex turns an element into a flex container, and its direct children automatically line up in a row (by default) as flex items. It's the standard modern tool for laying out a row or column of elements, replacing older float-based layout tricks.
justify-content aligns children along the main axis (horizontally, by default) — flex-start, center, flex-end, space-between (pushes items to the edges, even gaps between), and space-around are the common values. align-items does the equivalent alignment along the *cross* axis (vertically, by default).
flex-direction: column switches the main axis to vertical, which also swaps what justify-content and align-items each control. gap adds consistent spacing between flex items without needing margin on each one individually.
.row {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
}.stack {
display: flex;
flex-direction: column;
gap: 8px;
}display: grid turns an element into a grid container, and grid-template-columns defines the columns — repeat(3, 1fr) creates three equal-width columns, 1fr meaning "one fraction of the available space." Grid is two-dimensional (rows *and* columns at once), where Flexbox is fundamentally one-dimensional.
gap works exactly the same way it does in Flexbox — consistent spacing between grid cells without manual margins. minmax(220px, 1fr) inside repeat() is a common pattern for a responsive card grid: each column is at least 220px, but grows to fill extra space evenly.
auto-fit combined with repeat() and minmax() (repeat(auto-fit, minmax(220px, 1fr))) creates a grid that automatically adds or removes columns as the container resizes — a genuinely responsive grid with zero media queries needed for the common "cards that wrap" case.
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}font-size sets text size (commonly in px, rem, or em — see the Units section), font-weight controls boldness (normal, bold, or a number like 400/700), and text-align: center centers text horizontally within its container.
line-height sets the vertical space a line of text occupies — a unitless value like 1.5 (meaning "1.5× the font size") is usually the right choice over a fixed px value, since it scales automatically if the font size ever changes.
font-family lists fonts in order of preference, ending in a generic fallback: font-family: "Inter", Arial, sans-serif — the browser uses the first font in the list it actually has available, falling all the way back to *some* sans-serif font if none of the named ones load.
h1 {
font-size: 32px;
font-weight: 700;
text-align: center;
}body {
font-family: "Inter", Arial, sans-serif;
line-height: 1.5;
}background-color sets a solid fill color. background-image: url(...) adds an image instead (or on top, layered), and background-size: cover scales it to fill the element completely, cropping as needed rather than stretching or leaving gaps.
border-radius rounds an element's corners — a small value (border-radius: 8px) gives a subtle rounded-card look, while border-radius: 50% on a square element turns it into a perfect circle.
background is also a shorthand property that can set color, image, position, and size all in one declaration — convenient, but if you only remember one longhand form, background-color and background-image separately are easier to read back later.
.button {
background-color: teal;
border-radius: 8px;
}.hero {
background-image: url("hero.jpg");
background-size: cover;
}position defaults to static — an element just flows normally in the document, and top/left/right/bottom do nothing. relative keeps an element in its normal flow position but lets top/left/etc. nudge it visually from where it would otherwise sit, without affecting other elements around it.
absolute removes an element from the normal flow entirely and positions it relative to its nearest ancestor that has position: relative (or absolute/fixed) set — this is why position: relative is so often added to a parent purely to give an absolutely-positioned child something to anchor to.
fixed positions relative to the browser viewport itself, staying in place even as the page scrolls — the standard technique for a sticky header or a floating "back to top" button. z-index controls stacking order when positioned elements overlap: a higher number renders on top.
.card {
position: relative;
}
.badge {
position: absolute;
top: 8px;
right: 8px;
}.header {
position: fixed;
top: 0;
width: 100%;
z-index: 10;
}px is an absolute, fixed-size unit — 16px is always 16px regardless of context. % is relative to the parent element's corresponding size (width: 50% means half the parent's width).
em is relative to the *current* element's font-size, which means em compounds unpredictably when nested (a font-size: 1.5em inside another 1.5em element becomes 2.25× the base, not 1.5×). rem solves this by always being relative to the root <html> element's font-size instead, regardless of nesting — which is why rem is generally the safer default for font sizes and spacing.
Viewport units (vw, vh) are relative to the browser window itself — 100vw is exactly the full viewport width, 50vh is half the viewport height — commonly used for a hero section sized to the screen rather than to its content.
html {
font-size: 16px;
}
.text {
font-size: 1.5rem; /* always 24px, regardless of nesting */
}.hero {
min-height: 100vh;
}A media query applies a block of CSS only when a condition is met — most commonly a viewport width: @media (max-width: 600px) { } applies its rules only when the browser window is 600px wide or narrower, the standard way to adjust layout for phones vs. desktops.
Mobile-first is the common modern approach: write your base (un-queried) CSS for the smallest/narrowest layout first, then use @media (min-width: ...) queries to progressively add complexity for larger screens — rather than designing for desktop and cramming a phone layout in afterward as an exception.
Media queries aren't the only responsive tool — CSS Grid's auto-fit/minmax() pattern (see the Grid section) and Flexbox's flex-wrap can each handle real responsive behavior with zero media queries at all, and are often simpler for exactly the "cards that reflow" case.
.nav {
flex-direction: column;
}
@media (min-width: 768px) {
.nav {
flex-direction: row;
}
}:hover applies a style only while the mouse is over the element — .link:hover { color: orange; } changes color only during hover, reverting the instant the mouse moves away. Pair it with transition for a smooth animated change instead of an instant snap.
:focus applies while an element has keyboard focus (tabbed to, or clicked into) — critical for accessibility, since a keyboard-only user relies entirely on a visible focus style to know where they are on the page. Never remove focus styles (outline: none) without providing a real replacement.
:active applies during the actual click/press (a brief, real-time state), and :nth-child(2) (or :nth-child(odd), :nth-child(even)) selects an element based on its position among its siblings — commonly used for zebra-striping table rows without adding a class to every single one.
.link {
color: teal;
transition: color 0.15s;
}
.link:hover {
color: orange;
}tr:nth-child(even) {
background: #f5f5f5;
}