Fall 2026
  • Discord
  • Gradescope
  • Syllabus

On this page

  • A Rule
  • Selectors
  • Combinators and the Document Tree
  • Counting Matches
  • The Cascade
  • Spacing and Appearance

CSS

Last class our pages worked but were plain: black text, white background, the browser’s default font. HTML says what each piece of content is rather than how it should look. Colors, fonts, spacing, and layout are specified in a separate language called CSS, short for Cascading Style Sheets.

For most of the web, appearance is part of the product, and the people who dismiss front-end work tend to be the ones who cannot do it.

CommitStrip comic: a developer insists nobody needs a front-end developer, then produces an unstyled mess of div and br tags.

A Rule

A CSS rule has two parts: a selector that chooses which elements the rule applies to, and a declaration block in curly braces that says what to do to them. One simple rule turns the text of every paragraph teal:

p {
    color: teal;
}

p is the selector, and it chooses every <p> element on the page. Inside the braces, color: teal; is one declaration, setting the color property to the value teal. Every declaration is a property: value; pair, and a block can contain several declarations, each ended by a semicolon.

CSS can be written inline on a single element, in a <style> block in the page’s <head>, or in a separate file shared by every page. Best practice is to use a separate file; put your rules in a file called style.css and link it from the <head> of each HTML page:

<head>
    <title>My First Page</title>
    <link rel="stylesheet" href="style.css">
</head>

The <link> element pulls style.css into the page, so one edit to that file restyles every page that links it. Your first project will require exactly this setup.

Keep style.css beside index.html for this example, because href="style.css" names a file in the same folder. Save both files, then reload the HTML page to see a stylesheet change. If nothing changes, check that the stylesheet loaded and that its selector matches an element on this page.

Selectors

A type selector, such as p or h1, selects every element of that type. The class and id attributes let you select a smaller set of elements.

A class is a reusable label you can put on any number of elements. You add it in the HTML with the class attribute, and you select it in CSS with a dot:

<p class="bold">This paragraph is bold.</p>
<span class="bold">So is this span.</span>

Select that class in CSS:

.bold {
    font-weight: bold;
}

The .bold selector matches every element whose class is bold, regardless of its tag, so the paragraph and the span both go bold. Use classes whenever a style applies to a group of elements.

The class name itself has no built-in appearance: class="bold" does nothing until a rule supplies the style. An element can have several classes separated by spaces, such as class="bold warning"; .bold and .warning both match it. The dot belongs in the CSS selector, not in the HTML attribute value.

An id names one element and must be unique on the page. You add it with the id attribute and select it with a hash:

<h1 id="top">Welcome</h1>

Select that id in CSS:

#top {
    color: teal;
}

The #top selector matches the single element with that id. A class marks a category (“all the warnings”), while an id names one element (“the site header”).

Combinators and the Document Tree

Suppose you’re making a page for a campus night market. The page has announcements for the whole event and a food stall with its own description, menu, and closing-time notice. You want the event announcements to be teal and the stall’s introduction to be italic. A selector such as p would style all four paragraphs; their positions in the document let us be more selective.

Here is the content inside <body>. <main> holds the page’s main content, and <section> groups the information about one stall:

<main id="night-market">
    <h1>Midnight Market</h1>
    <p class="notice">Bring a mug. The chai is on us.</p>
    <section class="stall">
        <h2>Moonrise Dumplings</h2>
        <p>Hand-folded dumplings, chili crisp optional.</p>
        <ul>
            <li>Sweet potato <span class="notice">sold out</span></li>
            <li>Mushroom and ginger</li>
        </ul>
        <p class="notice">Last batch at 10:30.</p>
    </section>
    <p>Next market: Friday after finals.</p>
</main>

The browser represents the document as a Document Object Model (DOM), which includes a tree of its elements. This diagram starts at <main> and shows every element in our fragment; the short descriptions identify their content, while text nodes are omitted:

Element tree for Midnight Market, with sibling order read left to right. Main has four children: h1, a mug-reminder paragraph, a stall section, and a next-market paragraph. The section has h2, a description paragraph, ul, and a last-batch paragraph. The ul has two li children, and the first li contains a sold-out span. Four filled teal dots mark the paragraphs: two direct children of main and two inside section.

<main> is the parent of four elements: the <h1>, the mug reminder, the stall <section>, and the next-market paragraph. Those four children are siblings because they share a parent. The stall’s <h2> and paragraphs have a different parent, <section>, so they are not siblings of the page’s <h1>.

An element’s descendants include its children, their children, and every deeper level. The sold-out <span> is a child of the first <li> and a descendant of <ul>, <section>, and <main>. Each branch in the diagram connects a parent to a direct child; siblings appear from left to right in their HTML order.

A combinator joins selectors by specifying one of these relationships:

Selector Relationship Matches in the market page
main p Descendant: inside main at any depth All 4 paragraphs
main > p Child: directly inside main 2: the mug reminder and next-market announcement
h2 + p Adjacent sibling: the next element after h2, with the same parent 1: the dumpling description
h2 ~ p General sibling: any later p with the same parent as h2 2: the description and last-batch notice

The space in main p can cross several levels of nesting; > crosses exactly one. For the sibling selectors, the intervening <ul> keeps the last-batch notice from matching h2 + p, but it still matches h2 ~ p. The next-market announcement matches neither sibling selector because it belongs to <main>, outside the heading’s <section>. Blank lines and indentation between tags do not interrupt element adjacency.

We can now give the event announcements and stall introduction their intended styles:

main > p {
    color: teal;
}
h2 + p {
    font-style: italic;
}

Read a combined selector by first finding candidates for its rightmost part, then checking their relatives. For h2 ~ p, look at each <p> and ask whether an earlier sibling is an <h2>. The match is the paragraph; the heading supplies the condition and does not receive the rule’s declarations.

You can group selectors with a comma to give several the same rule: h1, h2, p { color: teal; } styles all three, and you can select by any attribute in square brackets, like [src="cat.jpg"] for the image with that source. In the market page, p.notice matches the two paragraphs labeled notice: the mug reminder and last-batch notice. p .notice, with a space, matches nothing: none of the paragraphs contains an element labeled notice; the sold-out span is inside a list item. p, .notice, with a comma, matches all four paragraphs plus the sold-out span, for five elements total. An element that matches both grouped selectors is counted only once.

Counting Matches

Put the market fragment inside <body> of a practice HTML page, save it, and open it in a browser. Open that page’s developer tools with F12 and go to the Console. document.querySelectorAll returns the matching elements, and .length counts them; these expressions give the following results on our example:

document.querySelectorAll('main p').length     // 4
document.querySelectorAll('main > p').length   // 2
document.querySelectorAll('h2 + p').length     // 1
document.querySelectorAll('h2 ~ p').length     // 2

Omit .length to inspect which elements matched. Try predicting ul .notice, ul > .notice, and li > .notice before running them. For each match, trace the relationship in the tree; for a missing match, find the level or parent that rules it out.

The Cascade

When two rules set the same property on the same element, the cascade determines which one applies.

CommitStrip comic: a developer explains that 'cascading' means rules have a priority order and weight, while a colleague ignores specificity and overrides everything with !important.

For the ordinary rules in our single stylesheet, the cascade resolves conflicts by specificity: a more specific selector takes precedence over a less specific one. An id selector takes precedence over a class selector, and a class selector takes precedence over a type selector, because naming one element is more specific than naming a category, which is more specific than naming every tag. When two selectors tie on specificity, the one written later in the file takes precedence. For <p class="bold">, both p { color: black; } and .bold { color: teal; } match; the class rule makes its text teal even if the type rule appears later.

Conflicts are resolved one property at a time. The earlier .bold rule still supplies font-weight: bold because neither color declaration replaces it. An element can therefore get its final appearance from several rules. Some properties, including text color, can also be inherited from a parent when the element has no declaration of its own.

The !important annotation gives a declaration priority over ordinary declarations, but competing important declarations still need to be resolved. Prefer to fix the matching rules; MDN’s cascade guide covers the additional rules for inline styles, layers, and other sources of styles.

Spacing and Appearance

CSS draws each element in a box: its content is surrounded by padding, then a border, then margin separating it from nearby boxes. For a paragraph, this rule adds space inside and outside its border:

p {
    padding: 12px;
    border: 1px solid teal;
    margin: 20px;
}

The text sits 12 pixels inside the border, and the margin provides space outside it. Change the padding and margin separately to see which gap moves. An element’s background extends through its padding but not into its margin.

Set text and background colors with color and background-color, and react to the mouse with the :hover pseudo-class, which selects an element only while the pointer is over it:

a {
    color: teal;
}
a:hover {
    background-color: #f2d6d9;
}

Links are teal and get a pale red background while the pointer is over them. The link remains the same HTML element; moving the pointer changes whether the second selector matches. A @media rule makes a page adapt to phones by applying nested rules only when a condition holds, such as a narrow screen:

@media (max-width: 600px) {
    body {
        font-size: 14px;
    }
}

In a browser window 600 CSS pixels wide or narrower, the body text becomes 14 pixels; on anything wider, this block does nothing. Resize the window to test both cases without changing the HTML. This is a basic example of responsive design, and your project rubric asks for one such rule. Look up other properties in the CSS cheatsheet or Shay Howe’s Getting to Know CSS.