Common CSS Interview Questions

Difficulty: Advanced

CSS interview questions test your understanding of the rules that govern how styles are applied, calculated, and rendered. The most frequently asked topics include the box model, specificity, the cascade, positioning, display types, and pseudo-elements. These questions appear in interviews for all frontend roles because CSS knowledge directly impacts your ability to build layouts, debug rendering issues, and write maintainable stylesheets.

The CSS box model is the foundation of layout and is asked in nearly every frontend interview. Every element is represented as a rectangular box with four areas: content (the actual text or child elements), padding (space between content and border), border (the visible edge), and margin (space outside the border that separates elements from neighbors). By default (box-sizing: content-box), the width and height properties set the content area size, and padding and border add to the total rendered size. With box-sizing: border-box, width and height include padding and border, which is more intuitive and is used by virtually every modern CSS reset.

Specificity determines which CSS rule wins when multiple rules target the same element. It is calculated as a four-part value: inline styles (1,0,0,0), IDs (0,1,0,0), classes/attributes/pseudo-classes (0,0,1,0), and elements/pseudo-elements (0,0,0,1). The universal selector (*) has zero specificity. !important overrides all specificity calculations. When specificities are equal, the last rule in source order wins. Understanding specificity is critical for debugging why a style is not being applied and for writing CSS that does not require !important overrides.

The CSS cascade is the algorithm that determines the final value of every CSS property for every element. It considers three factors in order: importance (!important > normal), origin (author > user > user-agent), and specificity. Within the same origin and importance, specificity decides. Within the same specificity, source order (last declaration wins) decides. CSS Layers (@layer) add another dimension to the cascade, allowing authors to control the priority of different stylesheet groups regardless of specificity.

The position property controls how an element is placed in the document flow. static is the default (normal flow). relative positions the element relative to its normal position without removing it from flow. absolute positions the element relative to its nearest positioned ancestor and removes it from flow. fixed positions relative to the viewport and persists during scrolling. sticky switches between relative and fixed based on scroll position. Understanding stacking context (created by positioned elements with z-index, opacity < 1, transforms, and other properties) is essential for solving z-index-related layout problems.

Code examples

The CSS box model explained

<style>
  /* content-box (default): width = content only */
  .content-box {
    box-sizing: content-box;
    width: 200px;
    padding: 20px;
    border: 5px solid #333;
    margin: 10px;
    background: lightblue;
    /* Content width: 200px
       Total width:   200 + 20*2 + 5*2 = 250px
       Space occupied: 250 + 10*2 = 270px */
  }

  /* border-box: width = content + padding + border */
  .border-box {
    box-sizing: border-box;
    width: 200px;
    padding: 20px;
    border: 5px solid #333;
    margin: 10px;
    background: lightcoral;
    /* Content width: 200 - 20*2 - 5*2 = 150px
       Total width:   200px
       Space occupied: 200 + 10*2 = 220px */
  }

  /* The universal box-sizing reset */
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  /* Margin collapsing: vertical margins of adjacent blocks collapse */
  .block-a { margin-bottom: 30px; }
  .block-b { margin-top: 20px; }
  /* Gap between them: 30px (larger wins), NOT 50px */
</style>

<div class="content-box">Content Box: 250px total</div>
<div class="border-box">Border Box: 200px total</div>

<div class="block-a">Block A (margin-bottom: 30px)</div>
<div class="block-b">Block B (margin-top: 20px)</div>
<!-- Gap between blocks: 30px due to margin collapsing -->

In content-box, width only sizes the content area. In border-box, width includes padding and border. Margin collapsing occurs between adjacent vertical margins of block elements -- the larger margin wins rather than adding them together. This is a common source of unexpected spacing.

Specificity calculation and the cascade

<style>
  /* Specificity: (0, 0, 0, 1) - one element selector */
  p { color: black; }

  /* Specificity: (0, 0, 1, 0) - one class selector */
  .text { color: blue; }

  /* Specificity: (0, 0, 1, 1) - one class + one element */
  p.text { color: green; }

  /* Specificity: (0, 0, 2, 0) - two classes */
  .container .text { color: red; }

  /* Specificity: (0, 1, 0, 0) - one ID selector */
  #intro { color: purple; }

  /* Specificity: (0, 1, 1, 0) - one ID + one class */
  #intro.text { color: orange; }

  /* !important overrides everything (avoid using) */
  .override { color: pink !important; }

  /* Same specificity: last one wins (source order) */
  .first { color: red; }
  .first { color: blue; }  /* This wins */

  /* Inline style specificity: (1, 0, 0, 0) */
  /* <p style="color: gold;"> always wins over stylesheet rules */
</style>

<div class="container">
  <p id="intro" class="text">
    What color am I?
    <!-- #intro.text wins: orange (specificity 0,1,1,0) -->
  </p>
</div>

<!--
  Specificity comparison:
  p                = (0,0,0,1)
  .text            = (0,0,1,0)  > p
  p.text           = (0,0,1,1)  > .text
  .container .text = (0,0,2,0)  > p.text
  #intro           = (0,1,0,0)  > .container .text
  #intro.text      = (0,1,1,0)  > #intro
  style=""         = (1,0,0,0)  > #intro.text
  !important       = overrides all
-->

Specificity is compared from left to right: inline > IDs > classes > elements. One ID (0,1,0,0) always beats any number of classes (0,0,999,0). When specificities are equal, the last rule in source order wins. !important should be avoided as it breaks the natural cascade.

Position property: all five values

<style>
  .container {
    position: relative;
    height: 400px;
    border: 2px solid #333;
    overflow: auto;
    padding: 16px;
  }

  /* static: default, normal document flow */
  .static {
    position: static;
    /* top, right, bottom, left, z-index have NO effect */
  }

  /* relative: offset from normal position, stays in flow */
  .relative {
    position: relative;
    top: 20px;
    left: 30px;
    /* Element moves visually but original space is preserved */
    /* Other elements are NOT affected */
  }

  /* absolute: removed from flow, positioned to nearest
     positioned ancestor (or viewport if none) */
  .absolute {
    position: absolute;
    top: 10px;
    right: 10px;
    /* Positioned relative to .container (which is position: relative) */
    /* Does NOT take up space in normal flow */
  }

  /* fixed: removed from flow, positioned to viewport */
  .fixed {
    position: fixed;
    bottom: 20px;
    right: 20px;
    /* Stays in place when scrolling */
    /* Positioned relative to viewport, always */
  }

  /* sticky: hybrid of relative and fixed */
  .sticky {
    position: sticky;
    top: 0;
    /* Acts as relative until scroll reaches threshold */
    /* Then acts as fixed within its parent */
    /* Requires a scroll container and threshold (top/bottom) */
    background: #fff;
    z-index: 10;
  }
</style>

<div class="container">
  <div class="static">Static (default flow)</div>
  <div class="relative">Relative (offset 20px down, 30px right)</div>
  <div class="absolute">Absolute (top-right of container)</div>
  <div class="sticky">Sticky Header (sticks at top on scroll)</div>
  <p>Long scrollable content...</p>
</div>
<div class="fixed">Fixed Button (viewport bottom-right)</div>

Static is default flow. Relative offsets from normal position without affecting other elements. Absolute removes from flow and positions to nearest positioned ancestor. Fixed positions to viewport permanently. Sticky acts as relative until a scroll threshold, then becomes fixed within its parent container.

Display property and formatting contexts

<style>
  /* Block: full width, new line, respects all box properties */
  .block { display: block; }
  /* Examples: div, p, h1-h6, section, article, form */

  /* Inline: flows with text, no width/height, no vertical margin */
  .inline { display: inline; }
  /* Examples: span, a, strong, em, img (replaced inline) */

  /* Inline-block: flows inline but respects width/height/margin */
  .inline-block { display: inline-block; }

  /* None: removed from layout and accessibility tree */
  .none { display: none; }
  /* vs visibility: hidden - hidden but still occupies space */
  .invisible { visibility: hidden; }

  /* Flex: one-dimensional layout (row or column) */
  .flex {
    display: flex;
    gap: 12px;
    align-items: center;
    justify-content: space-between;
  }

  /* Grid: two-dimensional layout (rows AND columns) */
  .grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
  }

  /* Contents: element disappears but children remain in flow */
  .contents { display: contents; }
  /* Useful for wrapper divs that should not affect layout */
</style>

<!-- display: none vs visibility: hidden -->
<div>
  <span>Before</span>
  <span style="display: none;">Hidden (no space)</span>
  <span>After</span>
  <!-- Renders: "Before After" -->
</div>
<div>
  <span>Before</span>
  <span style="visibility: hidden;">Hidden (has space)</span>
  <span>After</span>
  <!-- Renders: "Before         After" (gap where hidden element is) -->
</div>

Display defines how an element participates in layout. Block takes full width. Inline flows with text. Flex and grid create new formatting contexts for children. display:none removes the element entirely. visibility:hidden hides it visually but preserves its space in the layout.

Key points

Concepts covered

CSS Specificity, Box Model, Positioning, Display Property, Pseudo-elements, Cascade