CSS interview questions
CSS Box Model
Every HTML element is a rectangular box composed of four layers from inside out.
text┌─────────────────────────────┐ │ margin │ │ ┌───────────────────────┐ │ │ │ border │ │ │ │ ┌─────────────────┐ │ │ │ │ │ padding │ │ │ │ │ │ ┌───────────┐ │ │ │ │ │ │ │ content │ │ │ │ │ │ │ └───────────┘ │ │ │ │ │ └─────────────────┘ │ │ │ └───────────────────────┘ │ └─────────────────────────────┘box-sizing
By default (
content-box) width/height apply only to the content area.css/* content-box - default, confusing */ .box { width: 200px; padding: 20px; border: 2px solid; /* actual rendered width: 200 + 40 + 4 = 244px */ } /* border-box - intuitive, use this */ .box { box-sizing: border-box; width: 200px; padding: 20px; border: 2px solid; /* actual rendered width: 200px - padding and border are inside */ }Global reset (recommended)
css*, *::before, *::after { box-sizing: border-box; }Property box-sizing: content-box box-sizing: border-box width includes padding No Yes width includes border No Yes Predictable sizing No Yes Always use
border-boxglobally - it makes layouts predictable. Every modern CSS reset includes this rule.updated
did you know it?Flexbox vs CSS Grid
Flexbox - one-dimensional layout
Designed for laying out items in a single row or column.
css.nav { display: flex; justify-content: space-between; /* main axis */ align-items: center; /* cross axis */ gap: 16px; }css/* centering - the classic use case */ .container { display: flex; justify-content: center; align-items: center; }CSS Grid - two-dimensional layout
Designed for laying out items in rows and columns simultaneously.
css.layout { display: grid; grid-template-columns: 240px 1fr; grid-template-rows: 60px 1fr 40px; grid-template-areas: 'sidebar header' 'sidebar main' 'sidebar footer'; gap: 16px; } .sidebar { grid-area: sidebar; } .header { grid-area: header; } .main { grid-area: main; }When to use which
Use case Flexbox Grid Navigation bar Yes - Centering content Yes Yes Card row Yes - Page layout - Yes Complex 2D layouts - Yes Item alignment in a line Yes - They work great together
css/* Grid for page structure */ .page { display: grid; grid-template-columns: 240px 1fr; } /* Flex for items inside a grid cell */ .header { display: flex; justify-content: space-between; }Rule of thumb: if you're thinking in rows OR columns - use Flexbox. If you're thinking in rows AND columns - use Grid.
updated
did you know it?CSS Specificity
Specificity determines which CSS rule wins when multiple rules target the same element. It's calculated as a score in three categories.
Specificity score: (A, B, C)
Category What counts Example A - IDs #id#header= (1,0,0)B - Classes/attributes/pseudo-classes .class,[attr],:hover.btn:hover= (0,2,0)C - Elements/pseudo-elements div,p,::beforediv p= (0,0,2)Calculation examples
cssp /* (0,0,1) */ .class /* (0,1,0) */ #id /* (1,0,0) */ div .class p /* (0,1,2) */ #id .class:hover /* (1,2,0) */Comparing scores
css/* which color wins? */ #header .nav a { color: red; } /* (1,1,1) */ .nav a:hover { color: blue; } /* (0,2,1) */ /* red wins - (1,1,1) beats (0,2,1) */ /* A column always beats any number of B or C */Special cases
css/* inline styles - highest specificity (beats everything except !important) */ <div style="color: red"> /* !important - overrides everything */ .btn { color: blue !important; } /* :is(), :not(), :has() - take specificity of their most specific argument */ :is(#id, .class) { } /* (1,0,0) */ /* :where() - always zero specificity, easy to override */ :where(#id, .class) { } /* (0,0,0) */Cascade layers
Specificity is compared only inside the same
@layer. A rule in a later layer wins over a more specific rule in an earlier one, and unlayered styles beat all layers:css@layer reset, components; @layer reset { #app a { color: gray; } } /* (1,0,1) but loses */ @layer components { .link { color: blue; } } /* wins - later layer */Avoid
!important- it breaks the cascade and makes debugging painful. Use:where()for low-specificity defaults and@layerto order groups of styles instead of fighting with selectors.updated
did you know it?CSS Positioning
static (default)
Normal document flow.
top,left,right,bottomhave no effect.relative
Offset from its own normal position. Still occupies original space.
css.box { position: relative; top: 20px; /* moves down 20px from where it would normally be */ left: 10px; /* original space is preserved - other elements don't move */ }absolute
Removed from flow. Positioned relative to the nearest positioned ancestor (any position other than static).
css.parent { position: relative; /* establishes positioning context */ } .child { position: absolute; top: 0; right: 0; /* top-right corner of .parent */ }fixed
Removed from flow. Positioned relative to the viewport - stays in place while scrolling.
css.navbar { position: fixed; top: 0; left: 0; width: 100%; /* stays at top of viewport always */ }sticky
Behaves like
relativeuntil a scroll threshold is reached, then acts likefixed.css.table-header { position: sticky; top: 0; /* sticks to the top of the scroll container once reached */ }Comparison
Stays in flow Positioned relative to Scrolls with page relativeYes Itself Yes absoluteNo Nearest positioned parent Yes fixedNo Viewport No stickyYes Scroll container Until threshold position: relativeon a parent is the most common pattern - it creates a positioning context for absolutely positioned children.updated
did you know it?CSS Custom Properties
CSS custom properties (variables) store reusable values and can be changed dynamically.
Syntax
css/* define - always start with -- */ :root { --color-primary: #3b82f6; --color-text: #1a1a1a; --spacing-md: 16px; --border-radius: 8px; } /* use */ .button { background-color: var(--color-primary); padding: var(--spacing-md); border-radius: var(--border-radius); } /* fallback value */ .card { color: var(--color-accent, #ff0000); /* uses #ff0000 if --color-accent is not defined */ }Scoping - variables are inherited
css:root { --color: blue; } .card { --color: red; /* overrides only inside .card */ } .card p { color: var(--color); } /* red */ p { color: var(--color); } /* blue */Dynamic theming
css:root { --bg: white; --text: black; } [data-theme='dark'] { --bg: #1a1a1a; --text: white; } body { background: var(--bg); color: var(--text); }js// change theme at runtime document.documentElement.setAttribute('data-theme', 'dark'); // update a variable directly document.documentElement.style.setProperty('--color-primary', '#ff0000');CSS variables are the foundation of modern theming systems. Unlike Sass variables, they are live in the browser and can be changed with JavaScript at runtime.
updated
did you know it?CSS Units
px - absolute pixels
Fixed size - not affected by user font size preferences.
css.border { border: 1px solid; } /* always 1 pixel */em - relative to parent font-size
css.parent { font-size: 16px; } .child { font-size: 1.5em; } /* 24px */ .nested { font-size: 1.5em; } /* 36px - compounds! */Compounding makes
emtricky for font sizes but useful for spacing that should scale with the component's text.rem - relative to root font-size
csshtml { font-size: 16px; } /* root */ .any-element { font-size: 1.5rem; } /* always 24px, no compounding */remis predictable - always relative to<html>, never compounds.vh / vw - viewport units
css.hero { height: 100vh; } /* full viewport height */ .sidebar { width: 25vw; } /* 25% of viewport width */dvh / svh / lvh - dynamic viewport units (modern)
css/* accounts for mobile browser chrome (address bar) */ .hero { height: 100dvh; } /* dynamic viewport height */When to use which
Unit Best for pxBorders, shadows, media queries remFont sizes, consistent spacing emPadding/margin that scales with component text vh/vwFull-screen sections, viewport-relative sizing %Fluid widths relative to parent Use
remfor font sizes and most spacing. Usepxonly for borders and very small fixed values. Avoidemfor font sizes due to compounding.updated
did you know it?transition vs animation
transition - triggered by state change
Animates between two states when a CSS property changes.
css.button { background: blue; transform: scale(1); transition: background 0.3s ease, transform 0.2s ease; } .button:hover { background: darkblue; transform: scale(1.05); /* transition animates the change automatically */ }transitionsyntax:property duration timing-function delayanimation - runs independently with keyframes
Plays automatically, can loop, pause, and run complex multi-step sequences.
css@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes bounce { 0% { transform: translateY(0); } 50% { transform: translateY(-20px); } 100% { transform: translateY(0); } } .spinner { animation: spin 1s linear infinite; } .loader { animation: bounce 0.6s ease-in-out infinite; }animationshorthand:name duration timing-function delay iteration-count direction fill-modeComparison
transitionanimationTrigger State change (hover, class) Automatic / on load Keyframes 2 states only Multiple keyframes Loop No Yes ( infinite)Pause/resume No Yes ( paused)Complexity Simple Complex sequences Performance tip
css/* animate only these - they run on GPU compositor thread */ .fast { transform: translateX(100px); /* yes */ opacity: 0.5; /* yes */ } /* avoid animating these - trigger layout recalculation */ .slow { width: 200px; /* no */ margin-top: 20px; /* no */ }Stick to animating
transformandopacityfor 60fps animations. Everything else triggers layout or paint.updated
did you know it?Pseudo-classes vs Pseudo-elements
Pseudo-classes - select existing elements in a state
Single colon
:. Target elements based on state or position.css/* user interaction states */ a:hover { color: blue; } a:focus { outline: 2px solid blue; } a:active { opacity: 0.7; } /* structural */ li:first-child { font-weight: bold; } li:last-child { border-bottom: none; } li:nth-child(2n) { background: #f0f0f0; } /* even rows */ /* form states */ input:disabled { opacity: 0.5; } input:checked { accent-color: blue; } input:invalid { border-color: red; } /* logical */ :not(.active) { opacity: 0.7; } :is(h1, h2, h3) { font-family: serif; } :has(img) { padding: 0; } /* parent has an img child */Pseudo-elements - create virtual elements
Double colon
::. Generate content that doesn't exist in the HTML.css/* before and after - most common */ .card::before { content: ''; display: block; width: 4px; background: blue; } .price::before { content: '$'; } /* text selection */ ::selection { background: yellow; color: black; } /* first line / first letter */ p::first-line { font-weight: bold; } p::first-letter { font-size: 2em; float: left; } /* placeholder text */ input::placeholder { color: rgba(0,0,0,0.4); } /* scrollbar styling */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; }Key difference
Pseudo-class Pseudo-element Syntax :hover::beforeTargets Existing element in a state Virtual element Creates DOM node No No (but renders like one) ::beforeand::afterrequirecontent: ''to render, even if the content is empty. They are absolutely positionable and extremely versatile for decorative effects.updated
did you know it?The CSS Cascade
The cascade is the algorithm that determines which CSS rule wins when multiple rules apply to the same element. It evaluates rules in order of priority.
Priority order (highest to lowest)
- Origin and importance - where the style comes from and whether it is
!important - Context - styles inside a shadow DOM vs outside
- Inline styles - the
styleattribute - Cascade layers - order of
@layerblocks (for!importantthe order is reversed) - Specificity - how specific the selector is
- Source order - which rule comes last
1 - Importance
css.btn { color: blue !important; } /* wins over everything */ #btn { color: red; } /* loses */1 - Origin
textBrowser !important > User !important > Author !important (your CSS) > Author styles > User preferences > Browser defaults2 - Cascade layers
css@layer base, components; @layer base { .btn { color: gray; } } @layer components { button { color: blue; } } /* wins despite lower specificity */3 - Specificity
css#id .class a { color: red; } /* (1,1,1) */ .nav a { color: blue; } /* (0,1,1) */ /* red wins */4 - Source order
css.btn { color: red; } .btn { color: blue; } /* wins - comes later */Inheritance
Some properties (text-related) are inherited from parent to child automatically.
cssbody { font-family: sans-serif; color: #333; } /* all text elements inherit these unless overridden */Non-inherited properties (box model, positioning) do not cascade down.
css/* force inherit or reset */ .child { color: inherit; } .child { color: initial; } /* browser default */ .child { color: unset; } /* inherited if inheritable, else initial */Understanding the cascade helps you avoid specificity wars. Modern best practices favor low-specificity selectors (class-only,
:where()) and@layerto order resets, libraries and components explicitly.updated
did you know it?- Origin and importance - where the style comes from and whether it is
Container Queries
Container queries let you style a component based on the size of its parent container rather than the viewport.
The problem with media queries
css/* media query - based on viewport */ @media (min-width: 768px) { .card { display: flex; } } /* problem: what if .card is in a narrow sidebar on a wide screen? */Container queries - component-aware
css/* 1 - define a containment context */ .card-wrapper { container-type: inline-size; container-name: card; /* optional */ } /* 2 - query the container, not the viewport */ @container card (min-width: 400px) { .card { display: flex; flex-direction: row; } } @container card (max-width: 399px) { .card { display: block; } }Why this matters
textSidebar (300px wide) Main content (800px wide) ┌──────────────────┐ ┌─────────────────────────────┐ │ .card - stacked │ │ .card - horizontal layout │ │ (container 300px)│ │ (container 800px) │ └──────────────────┘ └─────────────────────────────┘ /* same component, same CSS - adapts to its context */Container units
css/* units relative to the container size */ .card h2 { font-size: 5cqi; } /* 5% of container inline size */Unit Relative to cqwContainer width cqhContainer height cqiContainer inline size cqbContainer block size Comparison
Media Query Container Query Based on Viewport Parent container Component reuse Hard Easy Browser support All All modern browsers (Baseline since 2023) Container queries are the biggest layout improvement in CSS in years. They finally make truly reusable components possible without JavaScript resize observers.
updated
did you know it?