Skip to content

Portal | Level: L0: Entry | Topics: CSS | Domain: DevOps & Tooling

CSS Fundamentals - Primer

Why This Matters

As a DevOps or SRE engineer, you will encounter CSS more often than you expect. Internal dashboards, status pages, documentation sites, Grafana panels with custom HTML, and CI/CD pipeline UIs all involve CSS. You do not need to be a frontend developer, but you need enough fluency to fix a broken layout, adjust a status page during an incident, or maintain an internal tool without calling in a specialist.

CSS is deceptively simple to start with — you change a color, move a margin, and it works. The difficulty emerges when layout modes, specificity rules, and browser defaults interact in ways you did not anticipate. Understanding the box model, display modes, and the cascade eliminates most of that confusion.

The browser DevTools inspector is your primary debugging tool. If you are guessing at why a layout looks wrong instead of inspecting computed styles, you are wasting time.

Core Concepts

1. The Box Model

Every HTML element is a rectangular box with four layers:

+--------------------------------------------+
|                  margin                     |
|  +--------------------------------------+  |
|  |              border                   |  |
|  |  +--------------------------------+  |  |
|  |  |            padding             |  |  |
|  |  |  +--------------------------+  |  |  |
|  |  |  |         content          |  |  |  |
|  |  |  +--------------------------+  |  |  |
|  |  +--------------------------------+  |  |
|  +--------------------------------------+  |
+--------------------------------------------+

By default (box-sizing: content-box), width and height apply only to the content area. Padding and border are added on top, making the element larger than the width you specified. This is almost always surprising.

/* Fix this globally — nearly every modern project does this */
*, *::before, *::after {
    box-sizing: border-box;
}

With border-box, width includes content + padding + border. Margin is always outside.

Name origin: CSS stands for "Cascading Style Sheets." The "cascade" refers to the algorithm that determines which style rule wins when multiple rules apply to the same element. CSS was proposed by Hakon Wium Lie in 1994 while working at CERN with Tim Berners-Lee. The first browser to fully support CSS1 was Internet Explorer 5 for Mac (2000).

2. Display Modes

The display property controls how an element participates in layout.

Value Behavior
inline Flows with text. Width/height ignored.
block Takes full width. Stacks vertically.
inline-block Flows inline but respects width/height.
flex Children laid out in a flex container.
grid Children laid out in a grid container.
none Removed from layout entirely.
/* Flexbox — single-axis layout (row or column) */
.navbar {
    display: flex;
    justify-content: space-between;  /* main axis */
    align-items: center;             /* cross axis */
    gap: 1rem;
}

/* Grid — two-axis layout */
.dashboard {
    display: grid;
    grid-template-columns: 250px 1fr 1fr;
    grid-template-rows: auto 1fr auto;
    gap: 1rem;
}

Rule of thumb: use flexbox for one-dimensional layouts (navbars, card rows, centered content). Use grid for two-dimensional layouts (dashboards, page scaffolding).

Remember: Mnemonic: "Flex = one direction, Grid = two directions." If you're laying things out along a single axis (a row of buttons, a column of cards), use flexbox. If you need rows AND columns simultaneously (a dashboard layout), use grid.

3. Selectors and Specificity

CSS rules are applied based on selector specificity. When multiple rules target the same element, the more specific rule wins.

Specificity is calculated as (a, b, c):
  a = inline styles (style="...")
  b = ID selectors (#header)
  c = classes, attributes, pseudo-classes (.active, [type="text"], :hover)
  (element selectors and pseudo-elements have lowest priority)

Examples:
  p                     (0, 0, 1)
  .warning              (0, 1, 0)
  #header              → (1, 0, 0)
  #header .nav a       → (1, 1, 1)
  style="color: red"    wins over all non-!important rules
/* Low specificity — easy to override */
p { color: black; }

/* Higher specificity — overrides the above for .error paragraphs */
p.error { color: red; }

/* !important — nuclear option, avoid in production CSS */
p { color: blue !important; }

The cascade resolution order: 1. Origin and importance (!important > normal) 2. Specificity (higher wins) 3. Source order (later rule wins if specificity is equal)

4. Common Layout Patterns

/* Center something horizontally and vertically */
.centered {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
}

/* Sticky footer (page content pushes footer down) */
body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
main { flex: 1; }
footer { /* stays at bottom */ }

/* Responsive sidebar layout */
.layout {
    display: grid;
    grid-template-columns: minmax(200px, 300px) 1fr;
}
@media (max-width: 768px) {
    .layout {
        grid-template-columns: 1fr;
    }
}

5. Media Queries

Media queries let you apply CSS rules conditionally based on viewport size, device capabilities, or user preferences. The most common use is responsive layout — making a page work on both desktop and mobile.

Mobile-first approach: write default styles for the smallest screen, then add complexity for wider viewports. This is simpler than the reverse because mobile styles are usually the simplest case.

/* Default: single-column, sidebar hidden */
.sidebar { display: none; }
.content { padding: 1rem; }

/* 768px+: show sidebar, two-column layout */
@media (min-width: 768px) {
    .layout {
        display: grid;
        grid-template-columns: 250px 1fr;
        gap: 1rem;
    }
    .sidebar { display: block; }
}

/* 1200px+: wider sidebar, more padding */
@media (min-width: 1200px) {
    .layout {
        grid-template-columns: 300px 1fr;
        padding: 2rem;
    }
}

Why mobile-first works better: if you start with desktop styles and override for mobile, you end up with heavy CSS that mobile devices download but mostly discard. Starting small and adding is simpler to maintain and faster to load.

Common breakpoints: 768px (tablet), 1024px (small desktop), 1200px (large desktop). These are conventions, not rules — test your actual layout and break where it breaks.

Other useful media queries:

/* Print: strip navigation, backgrounds, ads — leave content */
@media print {
    nav, .sidebar, .no-print { display: none; }
    body { font-size: 12pt; color: black; background: white; }
    a { text-decoration: underline; color: black; }
    /* Show URLs after links so printed page is useful */
    a[href]::after { content: " (" attr(href) ")"; }
}

/* Dark mode: respect OS/browser preference */
@media (prefers-color-scheme: dark) {
    :root {
        --bg: #1a1a1a;
        --text: #e0e0e0;
        --border: #333;
    }
    body { background: var(--bg); color: var(--text); }
}

/* Reduced motion: disable animations for accessibility */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

6. CSS Variables (Custom Properties)

CSS variables (custom properties) let you define values once and reference them everywhere. They cascade like any other CSS property and can be overridden in child scopes.

:root {
    --color-ok: #22c55e;
    --color-warn: #eab308;
    --color-critical: #ef4444;
    --spacing: 1rem;
    --radius: 4px;
}

.status-ok { color: var(--color-ok); }
.status-warn { color: var(--color-warn); }
.status-critical { color: var(--color-critical); }

Why they matter for internal tools: when you need to change a brand color or adjust spacing across 20 components, you change one variable instead of hunting through 50 rules. They also enable theming:

/* Override variables for a dark section */
.dark-panel {
    --bg: #1a1a1a;
    --text: #e0e0e0;
}

/* All children of .dark-panel inherit the overrides */
.card {
    background: var(--bg, white);     /* fallback: white */
    color: var(--text, black);        /* fallback: black */
    padding: var(--spacing);
    border-radius: var(--radius);
}

The var(--name, fallback) syntax provides a default when the variable is not set — defensive coding for CSS.

Gotcha: CSS custom properties (variables) are resolved at runtime, not at build time like Sass/LESS variables. This means they can be changed dynamically via JavaScript (element.style.setProperty('--bg', '#333')) and they cascade through the DOM. This is powerful for theming but means typos in variable names silently fall back to the default or produce invisible failures.

7. Position and Stacking

The position property controls how an element is placed relative to its normal position, its parent, or the viewport.

Value Behavior Use Case
static Default flow. top/left ignored.
relative Offset from normal pos. Space preserved. Nudging, positioning anchor
absolute Removed from flow. Vs nearest positioned ancestor. Tooltips, dropdowns, badges
fixed Removed from flow. Positioned vs viewport. Persistent headers, floating buttons
sticky Normal flow until scroll threshold, then fixed. Table headers, section headers

The most common confusion: absolute positions relative to the nearest ancestor that has position set to anything except static. If no ancestor is positioned, it positions relative to the document body — which is almost never what you want.

/* Parent must be positioned for child absolute to work */
.dropdown-wrapper {
    position: relative;     /* creates the positioning context */
}
.dropdown-menu {
    position: absolute;     /* relative to .dropdown-wrapper */
    top: 100%;              /* just below the wrapper */
    left: 0;
    z-index: 10;
}

/* Sticky header — stays at top when scrolled past */
.page-header {
    position: sticky;
    top: 0;
    z-index: 100;
    background: white;      /* opaque or content shows through */
}

z-index only works on positioned elements (anything except static). But z-index is not global — each element with opacity, transform, filter, or will-change creates a new stacking context. An element with z-index: 9999 inside a stacking context with z-index: 1 will still appear behind an element with z-index: 2 in a sibling context.

8. Debugging CSS

The browser DevTools inspector is the single most important CSS debugging tool. If you are changing CSS in a file and refreshing to see what happened, you are working too slowly.

Core debugging workflow:

  1. Right-click element → Inspect
  2. Check the Computed tab — shows final resolved values
  3. Check the Styles panel — shows which rules apply and which are overridden (struck-through)
  4. Toggle rules on/off with checkboxes to isolate the cause
  5. Edit values live in the panel to test fixes before changing code

Common debugging patterns:

/* Make element boundaries visible (outline, not border —
   outlines do not affect layout or trigger reflows) */
* { outline: 1px solid red; }

/* Or target specific sections */
.layout > * { outline: 1px solid blue; }

Common layout problems and causes:

Symptom Likely Cause
Element wider than viewport Missing box-sizing: border-box or fixed width
Unexpected gap between elements Margin collapse (vertical margins merge, larger wins)
z-index not working Element is position: static or stacking context issue
Flex children not wrapping Missing flex-wrap: wrap on container
Grid item in wrong cell Implicit grid created extra tracks — check grid-template-*
absolute element in wrong place Nearest positioned ancestor is not what you expected
Text overflowing container Missing overflow-wrap: break-word or min-width: 0 on flex child

What Experienced People Know

  • Always set box-sizing: border-box globally. The content-box default has caused more layout bugs than any other single CSS behavior.
  • The browser inspector's computed styles panel tells you exactly what values are active. Use it instead of guessing.
  • Margin collapse is real: adjacent vertical margins do not add — the larger one wins. This is by design but catches people off guard.
  • Flexbox gap is now well-supported everywhere. Stop using margin hacks for spacing between flex children.
  • z-index only works on positioned elements (anything except static). Stacking contexts are created by several properties including opacity, transform, and filter — not just z-index.
  • When debugging layout, add outline: 1px solid red (not border — outlines do not affect layout) to see element boundaries.
  • CSS specificity wars are a sign of structural problems. If you are reaching for !important, reconsider your selector strategy.
  • For internal tools, a CSS reset or normalize.css eliminates browser default inconsistencies. This saves significant debugging time.
  • rem units (relative to root font size) are more predictable than em (relative to parent) for spacing and sizing.

Debug clue: If a layout looks correct in Chrome but broken in Firefox or Safari, the most common culprit is implicit min-width: auto on flex items. Flex items default to not shrinking below their content size, and browsers interpret this slightly differently. Adding min-width: 0 or overflow: hidden to the flex child usually fixes cross-browser flex layout issues.


Wiki Navigation

  • CSS Flashcards (CLI) (flashcard_deck, L1) — CSS