Topic

CSS

CSS is what makes a page look like something. HTML says what is on the page, CSS says how it looks: colors, fonts, spacing and where everything sits. This topic covers the rules you write every day and the two layout tools, Flexbox and Grid, that you will use for almost every page.

Key focus points

01

How CSS gets into a page

  • A rule has a selector and a block of declarations: selector { property: value; }. The selector says which elements, the declarations say what changes.
  • Three ways to add CSS: a style attribute on one element, a <style> block in the <head>, or a separate file linked with <link rel="stylesheet" href="style.css">.
  • Use the separate file. One stylesheet can style every page of the site, and the HTML stays clean.
  • Comments go between /* and */. Properties end with a semicolon, even the last one.
02

Selectors

  • Type selector: p matches every paragraph. Class selector: .note matches every element with class="note". Id selector: #intro matches the one element with id="intro".
  • Combine them: ul li matches every li inside a ul. ul > li matches only the direct children. h1, h2 applies one rule to both.
  • Attribute selectors: input[type="email"] matches only email inputs.
  • Pseudo-classes describe a state: a:hover when the mouse is over it, input:focus when it is selected, li:first-child and li:nth-child(2) for position in a list.
  • Pseudo-elements add a part: p::first-line, a::before and a::after with content: "" insert something before or after the element.
  • Prefer classes. Ids are unique, so a rule on an id can be used only once. Type selectors hit too much.

Try it: type a selector, or click one of the examples. Every element it matches lights up on the right. The HTML never changes, only what you point at.

The HTML
<header>
  <h1 id="title">My profile</h1>
  <nav>
    <a href="#">Home</a>
    <a href="#" class="active">About</a>
  </nav>
</header>
<main>
  <section class="intro">
    <h2>About me</h2>
    <p>I build web apps.</p>
    <p class="note">Based in Tirana.</p>
  </section>
  <section>
    <h2>Skills</h2>
    <ul>
      <li>HTML</li>
      <li class="fav">CSS</li>
      <li>JavaScript</li>
    </ul>
  </section>
  <form>
    <label>Email <input type="email"></label>
    <button type="button">Send</button>
  </form>
</main>
<footer>
  <p>Made by hand</p>
</footer>
The page3 matches
{ ... }

My profile

About me

I build web apps.

Based in Tirana.

Skills

  • HTML
  • CSS
  • JavaScript

Made by hand

A class can be on many elements. An id is on exactly one. ul li means any li anywhere inside a ul, section > h2 only a direct child.
03

The cascade: which rule wins

  • When two rules set the same property on the same element, the browser has to pick one. This is the cascade.
  • More specific selectors win: an id beats a class, a class beats a type. #intro beats .note beats p, no matter where they are in the file.
  • Same specificity: the rule that comes later in the stylesheet wins.
  • A style attribute on the element beats every rule in the stylesheet. !important beats everything, which is why you should almost never use it.
  • Some properties are inherited by children: color, font-family, font-size, line-height. Others are not: margin, padding, border, background.

Try it: one paragraph, <p id="intro" class="note">, and five rules that all set its color. Switch rules on and off and watch which one the browser picks.

The stylesheet
  • 0,0,1
  • 0,1,0
  • 1,0,0
  • 0,0,1
  • inline
Specificity is counted as ids, classes, types. Compare left to right: more ids wins, then more classes, then more types. A tie goes to the rule that is later in the file.
The paragraphblue wins

The quick brown fox jumps over the lazy dog.

class selector wins: specificity 0,1,0 beats 0,0,1. Order in the file does not matter when specificity differs.
04

Colors and units

  • Colors: a name (red), hex (#0094d8), rgb(0, 148, 216) or hsl(199, 100%, 42%). Add a fourth value for transparency: rgb(0 148 216 / 0.5).
  • px is a fixed size. % is relative to the parent. em is relative to the font size of the element, rem to the font size of the root (16px unless you change it).
  • Use rem for font sizes and spacing so everything scales together when the user changes their browser font size. Use px for borders and small details.
  • vw and vh are a percentage of the viewport width and height. 100vh is the full height of the browser window.
  • Custom properties hold a value you reuse: --brand: #0094d8; on :root, then color: var(--brand) anywhere.
One color, four ways to write it
Aa
  • hex#0094d8
  • rgbrgb(0, 148, 216)
  • hslhsl(199, 100%, 42%)
  • with alphargb(0 148 216 / 1)
All of these are the same color. hsl is the easiest to tweak by hand: the first number is the hue, the second how vivid, the third how light. The / 1 at the end is transparency, 0 is invisible and 1 is solid.
What a unit is relative to
parent
160px
fixed, never changes
50%
half of the parent
10rem
10 × root 16px
10em
10 × this element's 24px
Change the root font-size: only the rem bar moves. Change the element font-size: only the em bar moves. Change the parent width: only the % bar moves. The px bar never moves, which is exactly the problem with px.
05

The box model

  • Every element is a box with four layers, inside out: content, padding, border, margin.
  • padding is space inside the border, between the border and the content. margin is space outside the border, between this box and its neighbours.
  • By default width sets only the content. Padding and border are added on top, so a 200px box with 20px padding is 240px wide. box-sizing: border-box makes width include padding and border. Set it on every element with * { box-sizing: border-box; }.
  • Vertical margins between two blocks collapse: a 20px bottom margin next to a 30px top margin gives 30px, not 50px.
  • Shorthand goes clockwise from the top: margin: 10px 20px 30px 40px. Two values mean top-and-bottom then left-and-right.

Try it: Drag the sliders and watch the box grow. Then switch to border-box and see why almost every stylesheet starts with it.

The rule
.box {
  box-sizing: content-box;
  width: 160px;
  padding: 16px;
  border: 4px solid #16214d;
  margin: 20px;
}
The box200px wide, 240px with margin
margin
border
padding
content
160px
content-box: width 160px is only the content. Add padding twice and border twice and the box is really 200px wide.
06

Display and the flow

  • Block elements (div, p, h1, section) take the full width and start on a new line. Inline elements (a, span, strong) sit inside text and take only the space they need.
  • width, height and vertical margins do not work on inline elements. display: inline-block keeps the element in the line but lets you size it.
  • display: none removes the element from the page completely. visibility: hidden hides it but keeps its space.
  • Setting display: flex or display: grid on a parent changes how its children are laid out. That is the subject of the next two groups.
07

Typography

  • font-family takes a list: "Inter", Arial, sans-serif. The browser uses the first one it has. End with a generic family (sans-serif, serif, monospace) as the fallback.
  • Web fonts come from a <link> in the head. Google Fonts gives you the link and the font-family line to copy.
  • font-size, font-weight (400 normal, 700 bold) and font-style: italic. Keep weights to two or three per site.
  • line-height without a unit, like 1.5, is relative to the font size. Body text reads best between 1.4 and 1.7.
  • text-align, text-decoration: none to remove the underline from links, text-transform: uppercase, letter-spacing for headings and labels.
08

Layout with Flexbox

  • display: flex on a parent lays its children out in a line. The parent is the container, the children are the items.
  • flex-direction picks the main axis: row (default, left to right) or column (top to bottom).
  • justify-content places items along the main axis: flex-start, center, flex-end, space-between, space-around, space-evenly.
  • align-items places them across the other axis: stretch (default), flex-start, center, flex-end.
  • gap puts space between the items. Use it instead of margins on the items.
  • flex-wrap: wrap lets items move to a new line when they do not fit. flex: 1 on an item makes it grow to fill the free space.
  • Flexbox is for one direction at a time: a navigation bar, a row of buttons, a card with an icon next to text, centering something.

Try it: the grey box is the container, the blue boxes are its items. Change one property at a time. justify-content moves items along the direction, align-items across it.

The rule
.container {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: stretch;
  flex-wrap: nowrap;
  gap: 12px;
}
The result
1
2
3
To center something both ways: justify-content: center and align-items: center. Try it.
09

Layout with Grid

  • display: grid on a parent gives you rows and columns at the same time. Children are placed into the cells.
  • grid-template-columns: 1fr 1fr 1fr makes three equal columns. fr is a share of the free space. repeat(3, 1fr) is the same thing. 200px 1fr is a fixed sidebar next to a flexible main area.
  • gap works the same as in Flexbox. row-gap and column-gap set them separately.
  • An item can span cells: grid-column: span 2 makes it two columns wide. grid-column: 1 / -1 makes it stretch across the whole row.
  • repeat(auto-fit, minmax(240px, 1fr)) fits as many 240px columns as the width allows and grows them to fill. That one line is a responsive card grid.
  • Grid is for the page: header, sidebar, main, footer, or a gallery of cards. Flexbox is for what goes inside each of those areas.

Try it: pick a column pattern, or type your own. Click an item to make it span 2 columns. Try the auto-fit one and then make your browser window narrower.

The rule
.container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
}
The result
fr is a share of the free space. 1fr 2fr gives the second column twice the width of the first. Fixed values like 200px are taken first, the fr columns share what is left.
10

Positioning

  • position: static is the default. The element sits in the normal flow and top, left, right, bottom do nothing.
  • position: relative moves the element from where it would have been, and its original space stays reserved. It also becomes the reference point for absolute children.
  • position: absolute takes the element out of the flow and places it relative to the nearest positioned ancestor. A badge in the corner of a card: card is relative, badge is absolute.
  • position: fixed is relative to the browser window and stays put while the page scrolls. position: sticky scrolls with the page until it reaches an offset, then sticks.
  • z-index decides which positioned element is on top when they overlap. Higher wins. It only works on positioned elements.
11

Responsive design

  • The viewport meta tag from the HTML topic is what makes any of this work on a phone. Without it the phone pretends to be a desktop.
  • A media query applies rules only when a condition holds: @media (min-width: 768px) { ... } for screens at least 768px wide.
  • Write mobile first: the plain rules are for small screens, then media queries with min-width add the wider layouts.
  • Avoid fixed widths. Use max-width: 1100px with margin: 0 auto for the page container, and percentages or fr for columns.
  • img { max-width: 100%; height: auto; } keeps images inside their container on every screen.
  • Test by dragging the browser window narrower, or with the device toolbar in DevTools.

Try it: drag the slider to resize the pretend browser. The plain rules are for phones. The media query only switches on when the width reaches 600px.

The stylesheetmedia query is off
/* mobile first: these rules always apply */
.cards { display: grid; gap: 12px; }
nav { display: flex; flex-direction: column; gap: 6px; }
@media (min-width: 600px) {
  /* only when the screen is at least 600px wide */
  .cards { grid-template-columns: 1fr 1fr; }
  nav { flex-direction: row; gap: 16px; }
}
The browser360px
Card oneBelow 600px cards stack.
Card twoAt 600px they sit side by side.
Write the small layout first, then add min-width media queries for bigger screens. Real phones are 360 to 430px wide. Tablets start around 768px.
12

Small touches

  • border-radius rounds corners. box-shadow: 0 2px 8px rgb(0 0 0 / 0.1) lifts a card off the page.
  • transition: background-color 0.2s animates a change instead of snapping. Put it on the element, not on :hover, so it runs both ways.
  • transform: translateY(-2px) or scale(1.02) on hover for buttons and cards. cursor: pointer on anything clickable that is not a link or button.
  • :focus-visible styles the keyboard focus ring. Never remove it without replacing it.
  • Keep it subtle. Motion should confirm an action, not decorate the page.
13

Good habits

  • Start every stylesheet with * { box-sizing: border-box; margin: 0; padding: 0; } and set the body font and line-height once.
  • Name classes by what the thing is, not what it looks like: .card, .nav-link, .price. A .red-text class breaks the day the design changes.
  • Keep selectors short. A class is almost always enough. Deep chains like main section div ul li a are fragile and hard to override.
  • Use the Styles panel in DevTools. It shows every rule that hits an element, which one won, and lets you edit values live.
  • Do not fight the browser with !important. If a rule is not applying, check specificity and order first.

Practice

  1. Take the profile page from the HTML topic and add a style.css file. Set a web font, a background color and a readable line-height on the body.
  2. Make the nav a horizontal Flexbox row with a gap, and the skills list a Grid of cards with repeat(auto-fit, minmax(160px, 1fr)).
  3. Add a media query so the page is one column below 768px and two columns, content and a sidebar, above it.
  4. Style the contact form: full-width inputs, padding, a focus style, and a button with a hover transition.
  5. Open DevTools, pick an element and change its padding and color live. Then write the change into style.css.

Resources

Next up: JavaScript (Introduction): making the page do something.

Courses published on
Between lectures

Want to know when the next topic is up?

Leave your email and you get a short note when a new topic on this path is published. That is it.

  • New topics on this path, as soon as they are ready
  • One practical C# or ASP.NET Core tip every two weeks
  • No sales pitches. One click to unsubscribe