How Hirenix teaches
One chapter. 90 minutes.
Interview-ready.
Every concept starts with a real-world problem — the kind that actually shows up in production code. Nothing to cram; it just clicks. Every question comes with a model answer: exactly what to say in the room, and why. Then an AI mock interview on the same chapter.
- 📖Concept in 5 minutesNo jargon — straight to the point
- 🛠️Real-world problemThe kind production code throws at you
- 💬Model answerExactly what to say in the room
- 🧠FlashcardsRevise in 10 minutes
- 🤖AI mock interviewIt asks follow-ups too
- 📊Weak topicsSee exactly where you're stuck

The difference isn’t the content — it’s the filter. Only what’s actually used in production and actually asked in interviews. Textbook topics the industry never touches don’t make the cut.
What you’ll learn
- ●What is CSS?
- ●Selectors and specificity
- Box modelFree account
- Colors, typography, unitsFree account
- FlexboxFree account
- GridFree account
- PositioningFree account
- Responsive designFree account
- Transitions and animationsFree account
- Pseudo-classes and elementsFree account
- CSS variables and architectureFree account
- RecapFree account
- ●Project: Profile Card
- Project: Responsive GalleryFree account
- Project: Animated LandingFree account
What is CSS?
Imagine a house's skeleton — the walls, doors, windows — built first out of bricks and beams. Then someone comes in and paints the walls, picks the curtains, arranges the furniture. The skeleton is the exact same house whether it's painted white or blue, whether the curtains are silk or cotton — the structure doesn't change, only the look.
In CSS, HTML builds the skeleton (headings, paragraphs, buttons — the structure) and CSS is the layer that decides how that skeleton looks: colours, spacing, fonts, layout. Same HTML markup, different CSS = a completely different-looking page.
CSS can be attached three ways: inline (a style attribute right on one tag, e.g. <p style="color: red;">), internal (a <style> block inside the HTML <head>, styles that one page), and external (a separate .css file linked in with <link rel="stylesheet" href="styles.css">). External is best practice — one file can style every page on the site, the browser caches it so repeat visits load faster, and HTML (structure) stays cleanly separated from CSS (presentation).
Every CSS rule has two parts: a selector (which element(s) to target, e.g. p) and a declaration block in { } — one or more property: value; pairs, e.g. color: red;.
🌍 Real-world example: two sites both use the plain HTML
<button>tag, but one renders a small blue-outline button and the other a big rounded gradient button — identical structure (HTML), different presentation (CSS).
💡 Cascade = when more than one rule could apply to the same element, the browser doesn't just pick one at random — it "cascades" all matching rules together and picks a winner, in one line: more specific selectors and later rules win (unless
!importantoverrides).
Standard definition: CSS (Cascading Style Sheets) is the language that describes how HTML elements are presented — layout, colours, fonts, spacing — and it can be added inline, internally, or via an external stylesheet, with an external stylesheet being standard practice.
<!-- index.html -->
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<p class="lead">Hello CSS</p>
<p>A normal paragraph.</p>
</body>
/* styles.css */
p.lead {
color: blue;
font-size: 20px;
}Selectors and specificity
Imagine three memos land on your desk about the office dress code: a company-wide policy, a department memo, and a note pinned to your desk with your name on it. If they conflict, you follow the note with YOUR name on it — it's the most specific. If your manager then emails you directly, that beats even the named note. And if the CEO stamps a memo "MANDATORY — no exceptions", that overrides everything, no matter how specific the other memos are.
In CSS, when multiple rules target the same element and set the same property, the browser must pick ONE winner — this is the cascade. It resolves conflicts in this order: (1) !important wins over everything (avoid it — it breaks the normal order and is hard to undo later), (2) then specificity — how precisely a selector targets the element, (3) then source order — if specificity ties, whichever rule is written LAST in the CSS wins.
🌍 Real-world example: a global stylesheet sets
p { color: black; }, a component file sets.card p { color: gray; }, and a designer patches one paragraph with#hero-text { color: blue; }. Even if the id rule is written FIRST in the file,#hero-textstill wins — because ID beats class beats element, no matter the order.
💡 specificity = a 4-part score (inline styles, IDs, classes/attributes/pseudo-classes, elements/pseudo-elements) that ranks how targeted a selector is. Compare part by part, left to right — the higher part wins. 💡 selector types =
div(type/element),.card(class),#hero(id),[type="text"](attribute),:hover(pseudo-class),::before(pseudo-element), plus combinators (div pdescendant,div > pchild,div + padjacent sibling,div ~ pgeneral sibling) that combine selectors — combinators themselves add ZERO specificity, only the selectors they connect count. 💡 inheritance = some properties (likecolor,font-family) automatically pass down from parent to child unless overridden; box/layout properties (likemargin,border,width) do NOT inherit.inheritforces a property to take the parent's computed value,initialresets it to the CSS spec's default, andunsetacts likeinheritfor naturally-inheriting properties and likeinitialfor the rest.
Standard definition: When CSS rules conflict, the browser resolves the winner using, in order: importance (!important), specificity (inline styles > ID > class/attribute/pseudo-class > element/pseudo-element, with the universal selector * counting zero specificity), and finally source order, where the last rule written wins ties.
<!-- HTML -->
<p class="text" id="hero">Hello, CSS!</p>
/* CSS, in source order */
* { color: black; }
p { color: gray; }
.text { color: green; }
#hero { color: blue; }
p { color: orange; }
p { color: red !important; }Project: Profile Card
What we're building: A responsive profile card — an avatar, a name, a role, a short bio, and two link buttons, all inside one card. The card lifts and gets a shadow on hover, and on wider screens the avatar and padding grow a little. This is the CSS chapter's entry project — it ties together everything from the first two topics (what-is-css, selectors-and-specificity) and previews what's coming: the box model, flexbox, transitions, and media queries — the four things almost every real UI is built from.
We'll build it one small piece at a time: write the CSS, refresh the browser, see it change, THEN move to the next piece. Here's the tiny HTML we're styling — it doesn't change for the rest of the project:
<div class="card">
<img class="card__avatar" src="avatar.jpg" alt="Profile photo of Aisha Khan" />
<h2 class="card__name">Aisha Khan</h2>
<p class="card__role">Frontend Developer</p>
<p class="card__bio">Building accessible, fast web UIs. Open to remote roles.</p>
<div class="card__links">
<a class="card__link" href="#">GitHub</a>
<a class="card__link" href="#">LinkedIn</a>
</div>
</div>
Notice the BEM-ish naming: card, card__avatar, card__name — one class per element, each scoped to the card, so nothing here fights with the rest of the page.
Step 1 — The universal box-sizing reset
*,
*::before,
*::after {
box-sizing: border-box;
}
What's happening: Before any real styling, almost every serious project starts with this. Recall the fact from the box model: box-sizing defaults to content-box — meaning width only sets the content area, and any padding/border you add gets ADDED on top, so a width: 300px box with 20px padding actually renders 340px wide. That's a classic surprise, especially once we add padding to the card in Step 2.
border-box flips this: width now means the TOTAL box (content + padding + border), so a 300px card with 20px padding still renders 300px wide — padding eats into the content area instead of adding to it. We apply it to * (the universal selector — zero specificity, so anything more specific still wins) plus ::before/::after. We list those pseudo-elements explicitly because box-sizing is not an inherited property — a generated ::before/::after box is its own box and won't pick up border-box from its parent automatically, so it needs to be matched directly. This one reset is why the rest of our sizing math in this project "just works."
Step 2 — The card container (box model)
.card {
width: 100%;
max-width: 320px;
margin: 2rem auto;
padding: 1.5rem;
border: 1px solid #e2e2e2;
border-radius: 12px;
background-color: #ffffff;
text-align: center;
}
What's happening: width: 100% + max-width: 320px is the classic "flexible but capped" pattern — on a narrow phone the card fills the available width, but it never grows past 320px on a big screen. margin: 2rem auto does double duty: 2rem for top/bottom spacing, and the auto for left/right is the well-known trick that horizontally centers a block-level element with a set width — the browser splits the remaining horizontal space equally on both sides.
padding: 1.5rem is the breathing room inside the card, between the border and the content — and because of Step 1's border-box, this padding no longer pushes the card wider than 320px. border: 1px solid #e2e2e2 draws a thin border (shorthand for width/style/colour), and border-radius: 12px rounds its corners. background-color and text-align: center are self-explanatory — but notice text-align is one of the properties that inherits, so every child text element centers unless it overrides it.
Step 3 — Typography (the text hierarchy)
.card__name {
font-family: system-ui, sans-serif;
font-size: 1.25rem;
font-weight: 600;
margin: 0.75rem 0 0.25rem;
color: #1a1a1a;
}
.card__role {
font-size: 0.9rem;
color: #666666;
margin: 0 0 0.75rem;
}
.card__bio {
font-size: 0.95rem;
line-height: 1.5;
color: #333333;
margin: 0 0 1rem;
}
What's happening: Three text roles, three sizes — this is visual hierarchy: the name is biggest and boldest (1.25rem, font-weight: 600) so your eye lands there first, the role is smaller and greyer (0.9rem, #666666) since it's secondary info, and the bio uses a comfortable reading size (0.95rem) with line-height: 1.5 — 1.5× the font-size as the line height, a widely-used comfortable-reading value (tight lines under 1.2 feel cramped; much above 1.6 starts feeling loose).
We use rem everywhere, not px or em. Recall the unit rule: rem is relative to the ROOT (<html>) font-size, so every size here scales predictably together if a user bumps their browser's base font size for accessibility — em would have compounded unpredictably here since .card__bio sits nested inside .card, and px wouldn't scale with user preference at all. font-family: system-ui, sans-serif is a font stack: try the OS's native UI font first, fall back to any generic sans-serif if it's unavailable.
Step 4 — Flexbox: stacking everything in a column
.card {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
What's happening: We add these four lines to the same .card rule from Step 2. display: flex turns .card into a flex container, and every direct child (the img, the h2, two ps, the links div) becomes a flex item. By default flex-direction is row, but we set it to column — now items stack top-to-bottom instead of side-by-side.
Here's the axis gotcha this chapter warns about: justify-content always targets the main axis and align-items the cross axis — and flipping flex-direction to column flips which physical direction each one controls. In row mode the main axis is horizontal (so justify-content would space items left-right); in our column mode the main axis is now VERTICAL, so it's align-items that controls the horizontal centering here — that's why align-items: center is what centers the avatar/name/bio horizontally, not justify-content. gap: 0.5rem adds even spacing between every flex item without needing margins on each child (and, unlike margin, doesn't add extra space before the first or after the last item).
Step 5 — The links row (nested flexbox) + the avatar
.card__links {
display: flex;
gap: 0.75rem;
justify-content: center;
}
.card__link {
padding: 0.4rem 0.9rem;
border-radius: 6px;
background-color: #f0f0f0;
color: #1a1a1a;
text-decoration: none;
font-size: 0.85rem;
}
.card__avatar {
width: 96px;
height: 96px;
border-radius: 50%;
object-fit: cover;
}
What's happening: .card__links is a second, independent flex container, nested inside the outer one — flexbox nests fine, each container only manages its own direct children. This one keeps the default flex-direction: row, so the two link "buttons" sit side by side; justify-content: center now correctly centers them on the (horizontal) main axis, and gap: 0.75rem spaces the two links apart.
Each .card__link gets padding + border-radius: 6px to look like a small pill button, and text-decoration: none removes the default underline anchor tags get. For the avatar: width/height: 96px gives it a fixed square size, border-radius: 50% turns any square box into a perfect circle, and object-fit: cover tells the <img> to fill that 96px square without stretching — it crops the source photo instead of squishing it.
Step 6 — Hover transition (the card lifts, links invert)
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
}
.card__link {
transition: background-color 0.2s ease, color 0.2s ease;
}
.card__link:hover {
background-color: #1a1a1a;
color: #ffffff;
}
What's happening: transition doesn't animate anything by itself — it just tells the browser "when this property's VALUE changes, animate between the old and new value instead of snapping." We put it on the base .card and .card__link rules (not inside :hover), because the transition needs to be armed before the change happens, so it also plays smoothly on hover-OUT, not just hover-in — if we only wrote it inside :hover, leaving the hover state would snap back instantly.
.card:hover is a pseudo-class — it only applies while the mouse is over the card. transform: translateY(-4px) moves the card 4px up (a negative Y-translate); box-shadow: 0 8px 20px rgba(0,0,0,0.12) (x-offset, y-offset, blur, colour) adds a soft shadow that reads as "lifted off the page." We animate transform and box-shadow, NOT margin-top or width — this chapter's rule of thumb is transform/opacity are the cheap, GPU-friendly properties to animate, while animating layout properties (width, top, margin) forces the browser to recompute the layout of surrounding elements on every frame, which is slower.
Same idea on the links: .card__link:hover swaps the pill from light-grey to dark with white text, and because transition: background-color 0.2s ease, color 0.2s ease is already armed on the base rule, that colour swap fades in over 0.2s instead of flashing instantly.
Step 7 — One media query (mobile-first responsiveness)
@media (min-width: 480px) {
.card {
padding: 2rem;
}
.card__avatar {
width: 112px;
height: 112px;
}
}
What's happening: Everything we wrote in Steps 1–6 is the mobile/base styling — it already works fine on a small phone screen, width: 100% fills whatever space there is. That's mobile-first: write the small-screen styles as your DEFAULT, then use @media (min-width: …) to ADD more (bigger padding, bigger avatar) once screen space becomes available — not the other way round.
@media (min-width: 480px) means "only apply the rules inside this block when the viewport is at least 480px wide." Above that width we bump the card's padding from 1.5rem to 2rem and the avatar from 96px to 112px — small, deliberate upgrades once there's room to spare, not a redesign. This 480px isn't a "correct" breakpoint handed down by a spec — breakpoints are chosen by where YOUR content starts looking cramped or sparse, not by a fixed device-width table. One honest caveat: for this to behave correctly on an actual phone, the HTML <head> needs <meta name="viewport" content="width=device-width, initial-scale=1"> (from the HTML chapter) — without it, mobile browsers fake a wide desktop viewport and media queries stop matching the way you'd expect.
🔎 The full flow (recap the wiring)
- The browser paints the card at
max-width: 320px, box-sizing alreadyborder-boxfrom Step 1, so padding never pushes it wider. - Flexbox (
display: flex; flex-direction: column) stacks avatar → name → role → bio → links top-to-bottom,align-items: centerkeeping them horizontally centred (the CROSS axis in column mode). - The nested
.card__linksflex container lays its two<a>pills out in a row, centred withgapbetween them. - Move your mouse over the card →
:hovermatches → becausetransitionwas already armed on the base rule,transform/box-shadowanimate smoothly to the "lifted" state over0.2s; move it away and the same transition reverses. - Hover a link → its
background-color/colorfade to the dark/white pair, independently of the card's own hover state. - Resize the browser past
480px→ the@media (min-width: 480px)block's extra rules kick in → padding and avatar size step up slightly. Resize back down → they step back to the base values.
✅ What you just learned
- The universal box-sizing reset —
*, *::before, *::after { box-sizing: border-box }and whycontent-box(the real default) would have broken our padding math. - The box model in practice —
padding/border/border-radius/margin: autocentring, and howborder-boxchanges whatwidthmeans. - Typography hierarchy —
remsizing (vs the em-compounding trap),line-height,font-familystacks, colour contrast for primary/secondary text. - Flexbox, twice — an outer
columncontainer (align-itemscontrols the cross/horizontal axis here) and an inner nestedrowcontainer (justify-contentcentres it) — and why direction flips which property does what. - Hover transitions — arm
transitionon the BASE rule (not inside:hover) so both directions animate; animatetransform/box-shadow/background-color, not layout properties. - One real media query — mobile-first: base styles are the small-screen default,
@media (min-width: 480px)adds refinements for more space, and why the viewport<meta>tag is a hard dependency.
Resume framing: "Built a responsive profile card component with plain CSS — a box-sizing reset, box-model layout, flexbox (nested containers on both axes), hover micro-interactions using transition/transform, and a mobile-first media query."
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Profile Card</title>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
font-family: system-ui, sans-serif;
background-color: #f5f5f5;
}
.card {
width: 100%;
max-width: 320px;
margin: 2rem auto;
padding: 1.5rem;
border: 1px solid #e2e2e2;
border-radius: 12px;
background-color: #ffffff;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
}
.card__avatar {
width: 96px;
height: 96px;
border-radius: 50%;
object-fit: cover;
}
.card__name {
font-size: 1.25rem;
font-weight: 600;
margin: 0.75rem 0 0.25rem;
color: #1a1a1a;
}
.card__role {
font-size: 0.9rem;
color: #666666;
margin: 0 0 0.75rem;
}
.card__bio {
font-size: 0.95rem;
line-height: 1.5;
color: #333333;
margin: 0 0 1rem;
}
.card__links {
display: flex;
gap: 0.75rem;
justify-content: center;
}
.card__link {
padding: 0.4rem 0.9rem;
border-radius: 6px;
background-color: #f0f0f0;
color: #1a1a1a;
text-decoration: none;
font-size: 0.85rem;
transition: background-color 0.2s ease, color 0.2s ease;
}
.card__link:hover {
background-color: #1a1a1a;
color: #ffffff;
}
@media (min-width: 480px) {
.card {
padding: 2rem;
}
.card__avatar {
width: 112px;
height: 112px;
}
}
</style>
</head>
<body>
<div class="card">
<img class="card__avatar" src="avatar.jpg" alt="Profile photo of Aisha Khan" />
<h2 class="card__name">Aisha Khan</h2>
<p class="card__role">Frontend Developer</p>
<p class="card__bio">Building accessible, fast web UIs. Open to remote roles.</p>
<div class="card__links">
<a class="card__link" href="#">GitHub</a>
<a class="card__link" href="#">LinkedIn</a>
</div>
</div>
</body>
</html>CSSinterview questions & answers
10 sample questions below — 183+ in the full bank inside.
What are the `vw` and `vh` units relative to?
`vw` is 1% of the viewport's width and `vh` is 1% of the viewport's height — the current browser window, not any parent element. So `5vw` is always 5% of the browser window's width, regardless of nesting.
In simple terms: Think of it as a percentage of the whole table, not of whatever plate you're standing on — it ignores parents entirely and only cares about the window. Example: `.hero-title { font-size: 5vw; }` makes the heading's text size grow and shrink as the user resizes the browser window.
What does a hex colour code like `#ff0000` actually represent?
A hex code is a packed RGB value — each pair of hex digits (00–ff) is one channel: red, green, then blue. `#ff0000` is full red, zero green, zero blue, i.e. pure red. It's compact because it fits red/green/blue into six characters instead of writing `rgb(255, 0, 0)`.
In simple terms: Think of it as three two-digit dials, one per colour channel, each going from `00` (off) to `ff` (full, which is 255 in decimal). Example: `#00ff00` is full green because only the middle pair is maxed out.
What are the common ways to specify a colour in CSS?
Named colours like `red` or `steelblue` (readable but limited), hex codes like `#ff0000` (compact, the most common), `rgb()` which takes red/green/blue channel values, and `hsl()` which takes hue/saturation/lightness — the easiest to tweak by hand since you can slide just the lightness without recomputing the whole value.
In simple terms: Think of a paint shop: you can ask for paint by name ("sky blue"), by its exact recipe code (hex), by how much red/green/blue light to mix (rgb), or by turning hue/saturation/lightness dials (hsl). Example: `hsl(210, 80%, 40%)` is a mid-toned blue — change just the last number to `70%` and it gets lighter without touching the hue.
Why should a `font-family` value be a stack ending in a generic like `sans-serif`, not just one font name?
The browser tries each font in the stack left to right and uses the first one it has available. Ending with a generic family (`sans-serif`, `serif`, `monospace`) guarantees a reasonable fallback exists on every system, even if the custom font fails to load or isn't installed.
In simple terms: It's like giving someone a list of preferred restaurants in order — if the first is closed, try the next, and the last option is always "any restaurant that's open." Example: `font-family: "Inter", "Segoe UI", sans-serif;` tries Inter, then Segoe UI, then whatever generic sans-serif font the browser has.
What is margin collapse?
When two block-level elements sit directly above/below each other in normal flow, their touching top/bottom margins don't add together — they collapse into a single margin equal to the LARGER of the two, not the sum. Padding and border never collapse, only margins, and only vertically.
In simple terms: Think of two people each stepping back 2 feet to give space — they don't end up 4 feet apart, because their 'personal space bubbles' overlap into a single 2-foot gap. Example: two stacked `<p>` elements each with `margin: 16px 0;` end up with a 16px gap between them, not 32px.
What are the four parts of the CSS box model?
Every element renders as four nested layers, from the inside out: content (the actual text/image), padding (cushioning inside the border), border (the box's edge), and margin (empty space outside the border that separates it from other boxes).
In simple terms: Think of a mithai box: the sweets are the content, the tissue paper cushioning them is the padding, the cardboard box itself is the border, and the gap you leave between this box and the next one on the shelf is the margin. Example: a `<div>` with `padding: 10px; border: 2px solid; margin: 20px;` has all four layers stacked around its text.
What is the default value of box-sizing in CSS?
The default is `content-box`. This means `width` and `height` set the content area's size only — padding and border are added on top of that, which is why a beginner's box often renders wider than the width they set.
In simple terms: It's like ordering a 'medium' pizza but the box it comes in is bigger than the pizza itself — the box size isn't what you ordered. Example: `div { width: 100px; padding: 10px; }` with no `box-sizing` set renders at 120px total width, not 100px.
What is the difference between content-box and border-box?
They're the two values of `box-sizing`. `content-box` (the default) makes `width`/`height` measure ONLY the content, so padding and border get ADDED on top, making the rendered box bigger than the width you set. `border-box` makes `width`/`height` include padding and border, so the rendered size matches the width you set exactly.
In simple terms: Content-box is like buying an 8x10 photo frame and the frame's wood adds extra inches around it — the wall space it takes is more than 8x10. Border-box is like a picture frame kit where the total frame is guaranteed 8x10 no matter how thick the wood is. Example: `width: 200px; padding: 20px; border: 5px solid;` renders at 250px with content-box, but exactly 200px with `box-sizing: border-box`.
What's the difference between padding and margin?
Padding is space INSIDE the border, between the border and the content — it's part of the element and takes the element's background colour with it. Margin is space OUTSIDE the border, separating the element from its siblings — it's always transparent and never shows a background.
In simple terms: Padding is like the cushioning inside a shipping box around the item; margin is the gap you leave between that box and the next box on the truck. Example: a `<button>` with `background: blue; padding: 10px;` shows blue right up to the border, but `margin: 10px;` just pushes neighbouring elements away with no colour there.
What values does `font-weight` accept, and what do `400` and `700` mean?
`font-weight` accepts numeric values (typically in steps of 100, like `100`–`900`) or keywords like `normal` and `bold`. `400` is the `normal` weight and `700` is `bold` — the keywords are just aliases for those two common numeric values.
In simple terms: Think of it as a dial from thin to thick, with `normal` and `bold` being two well-known stops on that dial rather than the only two options. Example: `font-weight: 700;` and `font-weight: bold;` render the same way, but a font that supports finer weights could also use `600` for something between normal and bold.
173+ more CSS questions inside
Create a free account to read the full question bank, learn every topic, and practise with an AI mock interview.
Unlock all questions — freeReady to practise CSS?
Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.