A reading-circle page should remain readable when its window becomes narrow, its text grows, or its content changes. Styling therefore involves relationships: which elements a rule selects, which declaration supplies a property, and how the resulting boxes share the available space. These relationships explain a page more reliably than adjusting values until one screenshot looks right.
CSS supplies the styling rules. Bootstrap provides an organised set of CSS rules, layout conventions, utilities and components. Understanding the underlying CSS lets us use those conventions deliberately and diagnose conflicts when several rules apply.
Rules select elements and assign properties
Definition
CSS rule and declaration
A CSS rule contains a selector and a declaration block. The selector identifies the elements to which the rule applies. Each declaration pairs a property with a value.
.reading-card {
color: #20354d;
line-height: 1.6;
padding: 1rem;
}
The selector .reading-card matches elements whose class list contains reading-card. The three declarations set text colour, line height and inner spacing. The property is spelled color in CSS, even when the surrounding prose uses “colour”. Semicolons separate declarations; braces delimit the block.
CSS can be written in an external stylesheet linked by link, in a style element, or in an element's style attribute. An external stylesheet is convenient when several documents share a design. A style element is useful for a small independent example. An inline declaration applies directly to one element and has its own place in the cascade.
Selectors describe different relationships:
| Selector | What it matches |
|---|---|
p | Paragraph elements |
.note | Elements with the class note |
#next-session | The element with that ID |
.reading-card p | Paragraph descendants of a reading card |
.reading-card > h2 | Headings that are direct children of a reading card |
.note.highlight | Elements that have both classes |
a:hover | Links in the hovered state |
a:focus-visible | Links for which the browser indicates a visible focus state |
li:nth-child(2) | List items that are the second child of their parent |
p::first-letter | The first-letter portion of a paragraph, where applicable |
A space describes ancestry; no space between .note.highlight requires both classes on the same element. A comma groups independent selectors: h1, h2 selects either heading type. A pseudo-class such as :hover selects a state or relationship; a pseudo-element such as ::first-letter identifies a part of an element's rendered content.
The cascade and inheritance answer different questions
The cascade chooses between competing declarations for a property on an element. Inheritance supplies values for inherited properties when the element has no value of its own from the cascade. Text colour and font family normally inherit; padding, border and margin do not normally inherit.
For the ordinary, unlayered author stylesheet rules used in the examples, compare selector specificity first. Count IDs, then classes/attributes/pseudo-classes, then element names/pseudo-elements. Compare those counts in that order rather than adding them as interchangeable points. When specificity ties, the later declaration wins. Normal inline author styles take precedence over normal stylesheet rules.
Common mistake
Treating stylesheet location as a priority rule
External stylesheets and style elements are both author stylesheets: being inside the HTML does not automatically give a rule greater priority. Their selectors and source order still matter under the ordinary, unlayered conditions above.
!important changes the cascade's importance level; it does not create a universal rule that beats every possible declaration. Origins, layers, important declarations, animations and transitions have additional ordering rules.
Worked example
Predict the colour before opening the browser
<section class="reading-card">
<p class="note">Bring a question about the article.</p>
</section>
body { color: #20354d; font-family: system-ui, sans-serif; }
.note { color: navy; }
.reading-card p { color: teal; }
.reading-card .note { color: maroon; }
.note { color: purple; }
All four colour selectors match the paragraph. Their specificity counts are (0,1,0), (0,1,1), (0,2,0) and (0,1,0). The paragraph is maroon because (0,2,0) wins before source order is considered. The last .note rule would beat the earlier .note rule if those were the strongest competing declarations, but it does not beat the more specific selector.
The paragraph inherits the body's font family. It does not use the body's colour because a declaration selects its colour directly. An ancestor's more specific selector does not make an inherited value outrank a declaration on the child.
Use the browser's Styles and Computed panels to distinguish a matched declaration that lost the cascade from a property whose value was inherited. They explain the selected value rather than merely showing the final appearance.
Values describe typography, colour and size
font-family supplies an ordered list of font choices. system-ui, sans-serif uses available system fonts without downloading a font file. font-size, font-weight, line-height and text-align control different aspects of text. A unitless line height such as 1.6 scales with the element's own font size.
A local web font can be declared through @font-face and then named in font-family; the font file must actually be available at its declared local URL. A CSS preprocessor can compile a separate source language into regular CSS. Native custom properties, such as --accent, already belong to CSS and can be read with var(--accent) without a preprocessor.
| Value or unit | Interpretation |
|---|---|
16px | Sixteen CSS pixels; CSS pixels need not equal physical screen pixels |
1rem | The root element's font size |
1em | The element's font size; on font-size itself, the parent's font size |
50% for width | A proportion of the applicable containing block's width |
50vw | Half the viewport width |
50vh | Half the viewport height |
#245a92 | A colour expressed through hexadecimal red, green and blue channels |
rgb(36 90 146) | The same RGB channel values in functional notation |
hsl() provides another colour representation. Percentages depend on the property being set; their meaning should be checked rather than assumed to be “a percentage of the screen”. Maximum and minimum sizes can constrain a flexible size without making every element fixed-width.
The box model makes width calculations explicit
Definition
Content, padding, border and margin
The content box contains an element's content. Padding lies between content and border. The border surrounds the padding. Margin is spacing outside the border. The background normally paints through the padding and beneath the border; margin is transparent.
Worked example
Calculate a card's horizontal size
.reading-card {
box-sizing: content-box;
width: 240px;
padding: 12px;
border: 2px solid #60758b;
margin: 16px;
}
With content-box, width describes the content width. The border-box width is therefore CSS pixels. Including both horizontal margins gives CSS pixels of horizontal extent.
If only box-sizing changes to border-box, the declared 240px includes padding and border. The content width becomes CSS pixels, and the horizontal extent including margins is CSS pixels. Margin is outside the declared width in both cases.
The four-value shorthand padding: 8px 12px 16px 20px means top, right, bottom, left. Two values mean vertical then horizontal spacing. A single value applies to all four sides. The same ordering applies to margin.
Horizontal arithmetic should not be copied mechanically to every vertical case: vertical margins of ordinary block boxes can collapse. Padding, borders and layout gaps have different behaviour. Inspect the box model before explaining a spacing difference as missing content.
Display and flow determine how boxes participate
In normal flow, a block box begins on a new line and, with automatic width, usually fills the available inline space. Inline content participates in lines of text. Non-replaced inline elements such as span do not accept width and height in the same way as block boxes; an inline image is a replaced element and can have dimensions.
inline-block places a box within a line while allowing explicit width and height. display: flex makes direct children flex items; their arrangement is then controlled by the flex container. Changing display does not change the underlying HTML meaning: navigation can remain a list of links while its items are arranged horizontally.
.reading-nav {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
list-style: none;
padding: 0;
}
Here the list's direct children form a wrapping row with consistent gaps. A float serves a different purpose: adjacent inline content can wrap around the floated box. clear controls which floats a later box must move below, and display: flow-root can establish a container that encloses its floated contents.
display: none removes an element's generated box from layout. visibility: hidden hides the box while retaining its layout space. The choice affects nearby content, so it should follow the intended layout behaviour.
Positioning needs a reference box
position value | Basic behaviour |
|---|---|
static | Uses normal positioning; inset offsets do not reposition it |
relative | Applies an offset while preserving the original layout slot |
absolute | Removes the box from normal flow and positions it in its containing block |
fixed | In a simple untransformed document, positions against the viewport |
sticky | Moves within its scrolling and containing-block limits after reaching a specified inset |
.session-card { position: relative; padding: 1rem; }
.session-label { position: absolute; top: 0.5rem; right: 0.5rem; }
In this example, the relatively positioned card establishes the absolutely positioned label's containing block. The label's position does not reserve a normal-flow slot; the card's content needs enough room for the label. In more complex documents, other features such as transforms can also establish containing blocks. Determine the actual reference box before interpreting top or left.
A sticky heading needs an inset such as top: 0 and sufficient scrolling room. Positioning is useful for local relationships, but a page's main columns are more directly expressed with a layout system such as flex or a grid.
Begin with a narrow layout and add space when available
A responsive design uses one content structure and adapts its presentation to available space. Include the viewport declaration explicitly. Let text wrap, constrain reading width, and let images scale within their containers.
Worked example
A complete responsive page using only local CSS
This document contains its own stylesheet and can be saved as reading.html. At narrow widths the two regions stack; from 48rem upward, they share a row.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reading circle: next session</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font: 1rem/1.6 system-ui, sans-serif;
color: #20354d;
background: #f4f6fa;
}
header, main, footer {
width: calc(100% - 2rem);
max-width: 64rem;
margin-inline: auto;
}
.session-layout { display: flex; flex-direction: column; gap: 1rem; }
.reading-card {
padding: 1rem;
border: 1px solid #b8c7d8;
border-radius: 0.5rem;
background: white;
}
.reading-card h2 { line-height: 1.2; }
@media (min-width: 48rem) {
.session-layout { flex-direction: row; }
.main-topic { flex: 2; }
.side-note { flex: 1; }
}
</style>
</head>
<body>
<header><h1>Reading circle</h1></header>
<main class="session-layout">
<section class="reading-card main-topic">
<h2>Next discussion</h2>
<p>Compare two ways to organise a short technical article.</p>
</section>
<aside class="reading-card side-note">
<h2>Preparation</h2>
<p>Bring one passage and one question.</p>
</aside>
</main>
<footer><p>The discussion begins at 16:00.</p></footer>
</body>
</html>
The base rule establishes the narrow layout. The media query adds a wider layout when its condition matches; it does not create a second copy of the content. flex: 2 and flex: 1 distribute free space with different growth factors. Padding and borders also contribute to the final visible boxes.
For a raster image, max-width: 100%; height: auto lets the image shrink to fit its container while preserving its intrinsic aspect ratio. A picture element can additionally choose a different composition for a wider viewport. Test the widths just below and above each layout change, and test longer text and larger browser text settings.
Bootstrap names reusable layout rules
The following class examples use Bootstrap 5.3 conventions. For a local document, load a local copy of its stylesheet before your own stylesheet:
<link rel="stylesheet" href="vendor/bootstrap.min.css">
<link rel="stylesheet" href="reading.css">
The files must exist at those local paths. Bootstrap's CSS includes baseline styles, commonly called Reboot, plus its grid, utilities and component styles. The CSS rules supply the appearance; adding a class name without the corresponding stylesheet does not produce that appearance.
Bootstrap's default grid uses 12 sizing units per row. A container sets the content region, a row organises columns and gutters, and column classes assign widths. This does not require twelve content elements, and a container alone does not create a row of columns.
<main class="container py-4">
<div class="row g-3">
<section class="col-12 col-md-8">
<div class="border rounded p-3">
<h2>Next discussion</h2>
<p>Read the opening section and identify its main claim.</p>
</div>
</section>
<aside class="col-12 col-md-4">
<div class="border rounded p-3">
<h2>Preparation</h2>
<p>Bring one question.</p>
</div>
</aside>
</div>
</main>
Below the md breakpoint, each region uses 12/12 of the row and they stack. From md upward, they use 8/12 and 4/12. The g-3 class provides gutters; the inner boxes carry borders and padding so their visible surfaces remain separated by those gutters. A responsive class continues to apply at larger widths until a later applicable breakpoint rule changes it.
| Default tier | Minimum viewport width | Example |
|---|---|---|
| Extra small | No minimum | col-12 |
sm | 576 CSS pixels | col-sm-6 |
md | 768 CSS pixels | col-md-8 |
lg | 992 CSS pixels | col-lg-4 |
xl | 1200 CSS pixels | col-xl-3 |
xxl | 1400 CSS pixels | col-xxl-2 |
There is no xs infix in these class names. .container has responsive maximum widths, whereas .container-fluid stays full-width. The plain .col shares available width with equal-sized sibling columns; .col-auto sizes according to its content. When explicit column spans exceed twelve in a wrapping row, later columns move to another line.
Utilities and components still need semantic HTML
Utilities make small rules reusable. With Bootstrap's default spacing scale, p-3 gives padding of 1rem, mb-3 gives bottom margin of 1rem, and gap-3 gives a 1rem gap in an appropriate layout. p-2 p-md-4 changes padding from the base tier at md. In Bootstrap 5, ms-* and me-* refer to start and end spacing; fst-italic selects italic text.
Class order in the HTML is not a priority list: class="p-2 p-3" does not mean “apply the last class”. The stylesheet's declarations determine the result. Many utilities use !important, so later custom CSS alone may not override them. Prefer one deliberate utility per active setting and use component custom properties or suitably scoped rules for customisation.
<nav class="nav nav-pills" aria-label="Reading sections">
<a class="nav-link active" aria-current="location" href="#plan">Plan</a>
<a class="nav-link" href="#preparation">Preparation</a>
</nav>
<section id="plan"><h2>Plan</h2></section>
<section id="preparation"><h2>Preparation</h2></section>
<button type="button" class="btn btn-primary">Mark for reading</button>
The button classes style a real button; an action still needs its appropriate behaviour. table table-striped table-hover styles a semantic table, list-group and list-group-item style a list, and form-control styles a form control. Styling a control does not supply its label or validation rules. Classes such as table-dark belong on the intended table region while header cells still use th.
Some components, including interactive collapse, modal and dropdown behaviour, require Bootstrap's JavaScript. A local bundle can provide the required plugins and Popper support. A static layout does not need a script merely to use columns, borders or button styles. A responsive collapsing navbar needs the matching collapse markup and behaviour as well as a class such as navbar-expand-lg.
Bootstrap Icons is a separate icon collection. Its font-based classes require the local icon stylesheet and font files; alternatively, an icon can be included as SVG. When accompanying visible text, a decorative icon can use aria-hidden="true". An icon-only control needs an accessible name that describes its action. Framework choices vary, but selectors, the cascade and the box model remain the same foundations.
Motion describes a change over time
A transform changes how an element is drawn without reallocating its normal-flow slot. translate, rotate, scale and skew describe different transformations. A transition interpolates an animatable property when its value changes; keyframe animation specifies a sequence of values over a duration.
.session-link {
display: inline-block;
transition: transform 180ms ease;
}
.session-link:hover,
.session-link:focus-visible {
transform: translateY(-2px);
}
.session-indicator { animation: breathe 2s ease-in-out 2; }
@keyframes breathe {
0%, 100% { opacity: 0.65; }
50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.session-link { transition: none; }
.session-indicator { animation: none; }
}
The transition names the property it animates and also supports a keyboard focus state. The keyframe animation lasts two seconds per iteration and runs twice. The reduced-motion rule removes the animation; the content and control labels remain available. A transformed box may overlap nearby content because its original layout space has not changed.
Exercises
Checkpoint
Exercise 1: explain a winning declaration
A paragraph has class note inside an element with class reading-card. The stylesheet contains .reading-card p { color: teal; } followed by .note { color: purple; }. Both are normal, unlayered author rules. Which colour wins, and why would moving the second rule into a later style element not change that reasoning?
Solution · Exercise 1 solution
Teal wins. .reading-card p has specificity (0,1,1), while .note has (0,1,0). Source order only breaks a tie at this stage of the cascade. Moving the second rule into a later style element changes its order but not its specificity or origin, so it still loses under the stated conditions.
Checkpoint
Exercise 2: calculate a border box
A box has width: 300px, horizontal padding of 20px on each side, a 3px border on each side and horizontal margins of 10px each. Calculate the border-box width and total horizontal extent for content-box. Then calculate the content width for border-box.
Solution · Exercise 2 solution
For content-box, the border box is CSS pixels, and the horizontal extent is CSS pixels. For border-box, the border box is the declared CSS pixels and the content width is CSS pixels. The margins remain outside that width.
Checkpoint
Exercise 3: predict responsive columns
Three sibling columns each have col-12 col-md-6 col-xl-4. Using the default Bootstrap grid, explain their arrangement at viewport widths of 600, 800 and 1250 CSS pixels.
Solution · Exercise 3 solution
At 600 pixels, md has not been reached, so each column spans twelve units and all three stack. At 800 pixels, md applies: each spans six units, so two fit on the first row and the third wraps. At 1250 pixels, xl applies: each spans four units, so all three fit in one row. The breakpoint names refer to viewport widths, not the width of the individual column.
Checkpoint
Exercise 4: locate an overlaid label
A session card contains an absolutely positioned label with top: 0.5rem; right: 0.5rem. Explain the purpose of position: relative on the card and why the card's text may need padding around the label.
Solution · Exercise 4 solution
The relative card establishes the label's containing block in this example, so the insets are measured from that reference box. The absolute label is outside normal flow and reserves no slot beside the card's text. Suitable content spacing prevents overlap. Without that containing block, another ancestor or the initial containing block may become the reference, depending on the document.
Checkpoint
Exercise 5: distinguish a transition from an animation
In the motion example, what starts the link transition, how many seconds does the indicator animation run in total, and what changes when the reduced-motion media query matches?
Solution · Exercise 5 solution
The link's transform value changes when it enters or leaves the matched hover or visible-focus state, starting the transition. The indicator runs two iterations of two seconds, for four seconds in total. Under the reduced-motion rule, the link transition and indicator animation are disabled; the link's state styling and the document content remain available.
The layout rules in this unit act on the document structure developed in Semantic HTML documents.