What is CSS?
CSS means Cascading Style Sheets. HTML says what the content is. CSS says how it should look: colour, size, spacing and position. "Cascading" means many rules can hit the same element, and CSS has a fixed order to decide which one wins.
66 questions - Beginner to intermediate
CSS interviews are mostly about three things: how the box model works, how the browser picks the winning rule, and how you build a layout that survives on a small screen. Learn those three well and the rest is detail.
Last updated:
Selectors, the cascade and how rules are chosen.
CSS means Cascading Style Sheets. HTML says what the content is. CSS says how it should look: colour, size, spacing and position. "Cascading" means many rules can hit the same element, and CSS has a fixed order to decide which one wins.
Inline, with a style attribute on the element. Internal, inside a <style> tag in the head. External, in a .css file linked with <link>. External is best: one file styles the whole site and the browser can cache it.
It is the order the browser uses to pick a winner when many rules hit the same element. It checks origin and importance first. Then specificity. Then source order, so the rule written last wins when everything else is equal.
Specificity is a score for how exact a selector is. An inline style counts as 1000. Each id counts as 100. Each class, attribute or pseudo-class counts as 10. Each element or pseudo-element counts as 1. The higher score wins. If the score ties, the later rule wins.
/* 0,0,1,1 = 11 */ div.card { }
/* 0,1,0,0 = 100 */ #hero { } /* this one wins */
/* 0,0,2,0 = 20 */ .card.active { }
It forces one declaration to beat normal specificity. Avoid it because the next person then needs another !important to change it. Very soon nobody can guess which style is applied. Fix the specificity instead.
Some properties pass down from a parent to its children on their own. These are mostly text properties like color, font-family and line-height. Box properties like padding, border and background do not pass down. You can force it with the inherit keyword.
A class starts with a dot and can be used on many elements. An id starts with # and should appear once on a page. An id also has much higher specificity. That is exactly why most teams style with classes only.
A combinator says how two selectors are related. A space means any element inside. > means a direct child. + means the very next sibling. ~ means any later sibling.
.card p { } /* any p inside .card */
.card > p { } /* only a direct child p */
h2 + p { } /* the p right after an h2 */
h2 ~ p { } /* every p after an h2 */
A pseudo-class targets a state of an element that already exists, like :hover, :focus or :nth-child(2). A pseudo-element creates a part that is not in the HTML, like ::before and ::after. Pseudo-elements use two colons.
:nth-child counts every child, no matter which tag it is. :nth-of-type counts only children of that same tag. So p:nth-child(2) means "the second child, and it must be a p". p:nth-of-type(2) means "the second p, wherever it sits".
<div><h2>Title</h2><p>One</p><p>Two</p></div>
p:nth-child(2) /* matches "One" - it is the 2nd child */
p:nth-of-type(2) /* matches "Two" - it is the 2nd p */
:is() groups selectors and takes the specificity of its strongest part. :where() groups them the same way but always has zero specificity, so it is easy to override. :not() matches everything except what is inside it.
:is(h1, h2, h3) { margin-block: 0.5em; }
:where(.theme-dark) a { color: #7dd3fc; }
li:not(.active) { opacity: 0.6; }
They are values you write once and reuse. The name starts with two dashes and you read it with var(). Unlike Sass variables, they live in the browser. So you can change them at runtime with JavaScript or inside a media query.
:root {
--brand: #22c55e;
--space: 16px;
}
.button {
background: var(--brand);
padding: var(--space);
}
Almost always asked, in some form.
Every element is a box with four layers, from inside out: content, padding, border, margin. Padding is space inside the border. Margin is space outside it.
content-box is the default. There, width applies only to the content, so padding and border are added on top and the element becomes wider than you asked. With border-box, width includes padding and border, which is much easier to think about. Most projects set it for everything.
*, *::before, *::after {
box-sizing: border-box;
}
A block element takes the full width and starts on a new line. width, height and vertical padding all work. An inline element sits inside the line of text. width and height are ignored on it. inline-block sits in the line like inline, but width, height and padding work like block. It is useful for buttons and nav items.
span { width: 200px; } /* ignored */
.btn { display: inline-block; width: 200px; } /* works */
When two vertical margins touch, they join into one and the bigger value wins. They do not add up. It happens between siblings, and between a parent and its first or last child. Padding, a border, flexbox or grid all stop it.
display: none removes the element fully: no space, no clicks, and screen readers skip it. visibility: hidden keeps the space, hides the element and blocks clicks. opacity: 0 keeps the space, and the element can still be clicked and focused.
Use a small "visually hidden" class instead of display: none. It clips the element to one pixel, so eyes cannot see it but a screen reader still reads it. Use it for labels, table captions and skip links.
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
px is a fixed pixel. em is relative to the font size of the current element, so it builds up when elements are nested. rem is relative to the root font size, so it stays steady. % is relative to the parent. vh and vw are percentages of the screen height and width.
Because rem follows the font size the user set in their browser. A person with weak eyesight raises that size and your whole layout grows with it. A fixed px value ignores that setting.
It lets you mix units in one value and do maths in CSS. Keep spaces around the + and - signs, or the value will not work.
.sidebar { width: calc(100% - 240px); }
min() picks the smallest value. max() picks the largest. clamp(min, ideal, max) keeps a value inside a range. clamp is the easiest way to make text scale smoothly with no media queries.
h1 { font-size: clamp(1.75rem, 4vw + 1rem, 3.5rem); }
auto lets the element size itself to the space left after its own margins, so it fits. 100% means exactly the parent content width. So if the element also has padding or margin and is not border-box, it overflows.
overflow decides what happens when the content is bigger than the box. visible lets it spill out. hidden cuts it off. scroll always shows scrollbars. auto shows them only when needed. You can also set overflow-x and overflow-y on their own.
static is the default and ignores top and left. relative moves the element from its normal spot but keeps its space. absolute takes it out of the flow and places it against the nearest positioned parent. fixed places it against the screen. sticky acts like relative until you scroll past a point, then acts like fixed.
It is the nearest parent whose position is anything other than static. An absolutely positioned child measures its top, left, right and bottom from that parent. If there is no such parent, it uses the page itself.
Usually one of three reasons. You did not set top, bottom, left or right. A parent has overflow: hidden or auto, which kills sticky. Or the parent is not tall enough for anything to scroll.
.header {
position: sticky;
top: 0; /* required */
}
float pushes an element to the left or right and lets text wrap around it. It was the old way to build columns. A floated element is taken out of the normal flow, so the parent thinks it is empty and collapses to zero height. The clearfix fixes that. Today you should use flexbox or grid for layout and keep float only for text wrapping.
.clearfix::after {
content: "";
display: block;
clear: both;
}
z-index decides which element is painted on top. It works only on positioned elements, and on flex or grid children. A higher number means closer to the user.
It is a group of layers that stack together as one unit. A z-index on a positioned element, opacity below 1, transform, filter or will-change all create one. Inside a context, a child with z-index 9999 still cannot rise above a sibling context that has a higher z-index. This is the usual reason a modal hides behind a header.
The simplest modern way is flexbox or grid on the parent. The old absolute-position trick still works when you cannot change the parent.
/* modern */
.parent { display: grid; place-items: center; }
/* older */
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
One-dimensional layout - a row or a column.
Flexbox lays items out along one line, either a row or a column, and lets them grow and shrink to fill the space. You set display: flex on the parent and then control the children from the parent.
The main axis follows flex-direction. With row it runs left to right. With column it runs top to bottom. The cross axis runs across it. justify-content works along the main axis. align-items works along the cross axis.
It is short for flex-grow: 1, flex-shrink: 1, flex-basis: 0%. Every item with flex: 1 takes an equal share of the free space, no matter how wide its content is.
flex-basis is the starting size along the main axis, before growing or shrinking, and it beats width in a row. width is a plain size and still works on the cross axis.
align-items lines up items inside a single line on the cross axis. align-content lines up the lines themselves. align-content only does something when flex-wrap is on and there is more than one line.
Give it margin-left: auto. An auto margin eats all the free space, which is the cleanest way to push one item away from the rest.
.nav { display: flex; gap: 16px; }
.nav .login { margin-left: auto; }
Because a flex item has min-width: auto by default. It will not shrink below its content, and long text or a big image blocks it. Set min-width: 0 or overflow: hidden on the item to let it shrink.
.flex-child { min-width: 0; }
It puts space between flex or grid items without adding a margin on the outer edges. It replaced the old trick of adding a margin to every item and then removing it from the last one.
Two-dimensional layout - rows and columns together.
Grid works in two directions at once: rows and columns. Flexbox works in one direction. Use grid for the page structure and flexbox for the inside of a small component. They work well together.
fr means one share of the free space in the grid. 1fr 2fr gives the second column twice as much leftover space as the first.
.layout {
display: grid;
grid-template-columns: 240px 1fr;
gap: 24px;
}
It makes as many equal columns as can fit. Each column is at least 220px and then grows to share the space. You get a responsive card grid with no media queries at all.
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
Both make as many tracks as fit. With auto-fit the empty tracks collapse, so the real items stretch and fill the row. With auto-fill the empty tracks stay, so items keep their size and leave a gap on the right.
They let you give names to regions and then draw the layout as text. It reads very clearly and is easy to rearrange inside a media query.
.page {
display: grid;
grid-template-areas:
"head head"
"side main"
"foot foot";
}
.header { grid-area: head; }
.sidebar { grid-area: side; }
Use grid-column with the span keyword or with line numbers. Grid lines are counted from 1, and -1 means the last line.
.featured { grid-column: span 2; }
.full { grid-column: 1 / -1; }
justify-items places the content inside each cell. justify-content places the whole grid inside the container, and it matters only when the tracks are smaller than the container.
It means one set of HTML and CSS that fits any screen size. You use flexible widths, flexible images and media queries. You do not build a separate mobile site.
It is a block of CSS that applies only when a condition is true. Usually the condition is a screen width. It can also check orientation, print, dark mode or reduced motion.
@media (min-width: 768px) {
.sidebar { display: block; }
}
@media (prefers-color-scheme: dark) {
:root { --bg: #05080f; }
}
You write the base styles for the smallest screen first. Then you add min-width media queries to improve bigger screens. The mobile CSS stays small and you never have to undo desktop styles.
They style a component based on the width of its own container, not the whole screen. That makes a component truly reusable: the same card can be narrow in a sidebar and wide in the main area, without knowing where it sits.
.wrapper { container-type: inline-size; }
@container (min-width: 400px) {
.card { display: grid; grid-template-columns: 120px 1fr; }
}
Set max-width: 100% and height: auto. The image then never overflows its parent and keeps its shape. For a fixed box, add object-fit: cover so the image fills the box without stretching.
img { max-width: 100%; height: auto; }
.avatar { width: 64px; height: 64px; object-fit: cover; border-radius: 50%; }
It is a media query that tells you the user asked their computer to reduce animation. Many people do this because motion makes them feel unwell. Respecting it is a basic accessibility need.
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
A transition moves from one state to another and needs a trigger, like hover or a class change. An animation uses @keyframes. It can have many steps, can repeat, and can start on its own.
.btn { transition: background-color 200ms ease; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.dot { animation: pulse 1.5s infinite; }
transform and opacity. The browser can handle them on the compositor, often on the GPU, without measuring the layout again. Animating width, height, top or margin forces layout work on every frame and the animation stutters.
Reflow, also called layout, is the browser working out the size and position of elements again. It is costly. Repaint is redrawing pixels without changing size or position. It is cheaper. Composite only moves layers that already exist, and it is the cheapest.
It warns the browser that a property is about to change, so the browser can get a layer ready. Use it rarely and remove it after the animation. Too many layers use a lot of memory.
Keep selectors short. Avoid deep nesting. Split the CSS so each page loads only what it needs. Inline the critical CSS for the first screen. Animate only transform and opacity. Remove unused CSS in the build.
It is the small amount of CSS needed to draw the top of the page. Putting it inline in the HTML lets the first screen paint before the main stylesheet arrives. That improves First Contentful Paint.
BEM means Block, Element, Modifier. It is a naming rule. .card is the block. .card__title is an element inside it. .card--featured is a variation. It keeps specificity flat and class names easy to read.
It is a tool that adds variables, nesting, mixins, functions and file splitting, then turns it all into plain CSS. Plain CSS now has variables and nesting too, so preprocessors matter less than before.
It is a build step that gives every class name a unique hash. Styles then cannot leak between components. You import the styles object in the component and write styles.button instead of a global name.
Tailwind gives small utility classes like flex, p-4 and text-sm that you put straight in the markup. You never invent class names and the final CSS stays small. The cost is long HTML, and the whole team must agree to work this way.
CSS-in-JS writes styles inside JavaScript. Styles are then scoped to the component and can use props. The cost is extra work at runtime and a bigger bundle, unless the library pulls the static CSS out at build time.
It is the parent selector developers asked for over many years. It styles an element based on what is inside it. Before :has(), this needed JavaScript.
/* a card that contains an image gets extra padding */
.card:has(img) { padding-top: 0; }
/* a label whose checkbox is checked */
label:has(input:checked) { font-weight: 700; }
Browsers add their own default margins and font sizes, and they do not agree with each other. A reset removes those defaults. normalize.css instead makes them the same everywhere and keeps the useful ones. Both give you a steady starting point.
It keeps a box in a fixed shape while it resizes. It replaces the old padding-top percentage trick that was used for responsive video boxes.
.video { width: 100%; aspect-ratio: 16 / 9; }
CrackInterviewAI listens to the live interview and gives a structured answer on screen for coding, system design, HR and project questions. Download for Windows.