This page builds on core CSS concepts — selectors, colors, text styling, and the box model (padding, border, margin). If those terms sound unfamiliar, it's worth checking out CSS Beginner first before continuing here.
From here, we move into how CSS actually controls layout — arranging elements on a page, not just styling them individually.
display, position, and FlexboxHere's what's ahead on this page.
Whenever you set a size in CSS — width, font size, margin, whatever — you need to attach a unit to the number. There are a handful you'll run into constantly, and picking the right one actually matters more than it seems at first.
A fixed, absolute unit — 1px is always 1px, regardless of screen size or anything else. Simple and predictable, which is exactly why beginners default to it, but it doesn't scale well for responsive design.
h1 {
font-size: 32px;
}
Relative to the parent element's size. If a parent is 400px wide and
a child has width: 50%, that child will be 200px.
div {
width: 50%;
}
Relative to the font size of the parent element. If the parent's
font size is 16px, then 2em equals 32px. This can get
confusing fast, since em values compound as you nest elements deeper.
p {
font-size: 1.5em;
}
Relative to the root element's font size (<html>),
not the immediate parent — which avoids the compounding problem em
has. Most developers prefer rem for this exact reason.
p {
font-size: 1.5rem;
}
Relative to the browser window's size. 1vh is 1% of the
viewport height, 1vw is 1% of the viewport width —
useful for making something take up a specific portion of the screen
regardless of device.
div {
height: 100vh;
}
px — fixed, predictable, doesn't scale% — relative to the parent elementem — relative to the parent's font size, can compoundrem — relative to the root font size, generally the safer choicevh/vw — relative to the browser window itselfIn CSS Beginner, you learned tag, class, and ID selectors. Those get you pretty far, but sooner or later you'll want to target something more specific — like "only the links inside this navbar" or "every input with type text." That's where these come in.
Targets an element nested anywhere inside another, no matter how deep. Written as two selectors with a space between them.
nav a {
color: white;
}
This targets every <a> inside a
<nav>, even if it's nested several levels down.
Targets only direct children, not deeper descendants — more specific than the descendant selector above.
nav > a {
color: white;
}
+ targets the element immediately after another.
~ targets all matching elements that come after it, not
just the first.
h2 + p {
margin-top: 0;
}
This targets a paragraph that comes right after an h2 — commonly used to tighten spacing.
Targets elements based on an attribute, or a specific attribute value.
input[type="text"] {
border: 1px solid grey;
}
If several selectors share the same styling, you can combine them with a comma instead of repeating the same rule.
h1, h2, h3 {
font-family: Arial, sans-serif;
}
A B — descendant selector, targets B anywhere inside AA > B — child selector, targets only direct childrenA + B — targets the element immediately after A[attribute="value"] — targets based on an element's attribute
Every HTML element has a default display value that
decides how it behaves on the page — whether it takes up its own
line, sits next to other elements, or something else entirely.
You've actually been relying on this the whole time without setting
it explicitly.
Things like <div>, <p>, and
<h1> are block by default — they take up the full
width available and push the next element onto a new line, even if
there's plenty of space beside them.
div {
display: block;
}
Things like <span>, <a>, and
<strong> are inline by default — they only take up
as much width as their content needs, and sit right next to
whatever's around them.
span {
display: inline;
}
Inline elements ignore width, height, and vertical margin — so if
you try to set a specific size on a <span>, it
just won't apply. This trips up a lot of beginners.
This sits next to other elements like inline does, but still lets you set width and height like block does.
span {
display: inline-block;
width: 100px;
height: 40px;
}
div {
display: none;
}
This completely removes the element from the page — not just invisible, it doesn't even take up space anymore.
inline-block gives you sizing control while still sitting inlinedisplay: none removes an element from the page entirely
By default, elements just flow down the page one after another. The
position property lets you break out of that normal flow
and place an element exactly where you want — but it comes with a few
different modes that all behave differently, and mixing them up is a
common source of confusion.
Every element starts as static unless you say otherwise.
It just sits in the normal flow of the page — you can't use
top, left, etc. to move it.
Still takes up its normal spot in the page, but you can now nudge it
from that spot using top, left,
right, or bottom — the space it originally
occupied stays reserved, even after it moves.
div {
position: relative;
top: 10px;
left: 20px;
}
Removes the element from the normal flow entirely and positions it
relative to the nearest parent that has position: relative
set on it. If no parent has that, it positions relative to the whole page.
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
right: 0;
}
This combo — a relative parent with an absolute child — is genuinely one of the most common patterns in CSS, and you've actually already used it on EDUhub for things like a close button sitting in the corner of a box.
Positions relative to the browser window itself, and stays exactly there even as the page scrolls — useful for things like a sticky navbar or a floating button.
.sidebar {
position: fixed;
top: 0;
left: 0;
}
A mix of relative and fixed — it scrolls normally with the page until it reaches a specified position, then locks in place, like a heading that sticks to the top once you scroll past it.
h2 {
position: sticky;
top: 0;
}
static — default, no positioning appliedrelative — nudges from its original spot, space stays reservedabsolute — positions relative to the nearest positioned parentfixed — stays locked to the browser window, ignores scrollingsticky — scrolls normally, then sticks once it hits a set positionFlexbox is a layout system built for arranging elements in a single row or column, handling spacing, alignment, and sizing automatically. It's the tool most real websites use for things like navbars, card grids, and centering content — genuinely one of the most useful things you'll learn in CSS.
Set display: flex on a parent element. Its direct
children automatically become "flex items" and line up in a row.
.container {
display: flex;
}
By default, flex items line up horizontally. Switch to
column to stack them vertically instead.
.container {
display: flex;
flex-direction: column;
}
Controls spacing along the main direction (horizontal by default).
.container {
display: flex;
justify-content: center;
}
flex-start — items packed to the start (default)center — items centeredspace-between — items spread out, first and last touch the edgesspace-around — items spread out with even space around eachControls alignment along the opposite direction (vertical by default).
.container {
display: flex;
align-items: center;
}
By default, flex items try to squeeze onto one line.
flex-wrap: wrap lets them drop onto a new line instead
of overflowing.
.container {
display: flex;
flex-wrap: wrap;
}
.container {
display: flex;
gap: 20px;
}
Centering something both horizontally and vertically used to be annoying in CSS — flexbox made it almost trivial.
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
display: flex turns a container into a flexboxjustify-content controls horizontal spacing (by default)align-items controls vertical alignment (by default)flex-wrap lets items drop to a new line instead of overflowinggap adds spacing between items without needing marginA pseudo-class lets you style an element based on a certain state or condition — like when it's being hovered over, clicked, or when it's the first item in a list. Written with a colon after the selector.
Applies styling while the mouse is over an element.
button:hover {
background-color: #1d4ed8;
}
This is how buttons and links change appearance without needing any JavaScript — pure CSS reacting to a mouse action.
Applies styling while an element is focused — like an input field someone just clicked into.
input:focus {
border-color: blue;
}
Applies styling during the moment an element is being clicked.
button:active {
transform: scale(0.98);
}
Targets an element based on its position among its siblings.
li:first-child {
font-weight: bold;
}
li:last-child {
color: red;
}
Targets elements based on a pattern — useful for things like striped table rows.
tr:nth-child(even) {
background-color: #f5f5f5;
}
Excludes elements that match a certain selector.
p:not(.highlight) {
color: grey;
}
This styles every paragraph except ones with the class "highlight."
:hover, :focus, and :active respond to user interaction:first-child, :last-child, and :nth-child() target based on position:not() excludes elements matching a given selectorA transition makes a CSS change happen smoothly over time instead of instantly — like a button that gently fades to a darker color on hover, rather than snapping to it. It's a small touch that makes a page feel a lot more polished.
button {
background-color: #2563eb;
transition: background-color 0.3s;
}
button:hover {
background-color: #1d4ed8;
}
Here, transition is set on the button itself (not the
hover state) — it tells the browser "whenever background-color
changes on this element, animate it over 0.3 seconds instead of
switching instantly."
button {
background-color: #2563eb;
transform: scale(1);
transition: background-color 0.3s, transform 0.3s;
}
button:hover {
background-color: #1d4ed8;
transform: scale(1.05);
}
Instead of listing each property, you can use all to
transition anything that changes.
button {
transition: all 0.3s;
}
Controls the pacing of the animation — whether it moves at a constant speed, starts slow, or eases in and out.
button {
transition: all 0.3s ease-in-out;
}
linear — constant speed throughoutease — starts slow, speeds up, then slows down (default)ease-in — starts slow, ends fastease-out — starts fast, ends slowtransition property goes on the element's normal state, not the hover stateall for everythingWhile flexbox is great for arranging things in a single row or column, Grid is built for laying things out in both directions at once — rows and columns together. Think of it like designing a spreadsheet layout for your page.
.container {
display: grid;
}
grid-template-columns sets how many columns exist and
how wide each one is.
.container {
display: grid;
grid-template-columns: 200px 200px 200px;
}
This creates three columns, each 200px wide.
fr stands for "fraction" — it splits up the available
space proportionally, and adjusts automatically as the screen size
changes.
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
This creates three equal columns that share the space evenly, no matter how wide the container is.
.container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 100px 100px;
}
Same idea as flexbox's gap — adds spacing between grid
items without needing margin.
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
.gallery {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
<div class="gallery">
<img src="1.jpg">
<img src="2.jpg">
<img src="3.jpg">
</div>
This lays out three images side by side in equal columns — a common pattern for photo galleries or card layouts.
display: grid turns a container into a gridgrid-template-columns defines column widthsfr splits space proportionally and adapts to screen sizegap adds spacing between grid itemsThink you've got CSS down? Test yourself!
Start Quiz →