Responsive No Frameworks

Building responsive pages without a framework means creating fully adaptive layouts using only native CSS — fluid grids, fluid typography with clamp() , and a custom-property design token system — instead of relying on Bootstrap or Tailwind.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

By the end of this lesson you'll be able to build a complete, production-quality responsive page with pure modern CSS — fluid grids, fluid type, and a token system — without ever loading Bootstrap or Tailwind.

A CSS framework is like flat-pack furniture; building without one is custom carpentry. Flat-pack is quick, but every shelf is a fixed size and you live with the gaps. Custom carpentry means you measure each space and cut to fit.

Modern CSS hands you professional power tools so the custom approach is fast, not painful. A fluid auto-fit grid is a shelf that quietly adds or removes columns to fit the wall; clamp() is a leg that extends and retracts so the table is always the right height. You stop fighting fixed sizes and let the layout measure the room for you.

A responsive grid is one that changes how many columns it shows based on the space available — not on a list of fixed breakpoints. The trick is one line: grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); . Read it as: "make as many columns as fit, each at least 250px wide, sharing the leftover space equally."

minmax(250px, 1fr) sets a floor of 250px and a ceiling of 1fr (one share of free space). auto-fit packs in as many of those tracks as fit, then stretches the ones that exist to fill the row. Wrapping min(100%, 250px) as the floor stops the column overflowing on screens narrower than 250px. The result reflows from four columns to one as the window shrinks — with zero media queries.

Grid is for two-dimensional layouts; flexbox shines for one-dimensional rows like nav bars, toolbars, and button groups. display: flex lays children in a row; justify-content: space-between pushes the first and last to the edges; align-items: center lines them up vertically.

The one property that makes a flex row responsive is flex-wrap: wrap . Without it, a row of items squashes and overflows on small screens. With it, items that no longer fit drop onto the next line on their own — a button group that becomes two rows on a phone, no media query required.

clamp(min, preferred, max) gives you a value that scales with the viewport but is locked between a floor and a ceiling. The browser uses the preferred value — usually something with a vw (viewport-width) unit so it grows with the screen — but never lets it drop below min or rise above max .

So font-size: clamp(1.5rem, 5vw, 3rem) is a heading that is 1.5rem on a tiny phone, grows smoothly as the screen widens, and stops at 3rem so it never becomes comically large. Use the same idea for padding, gaps, and margins to get spacing that breathes — without a single breakpoint. This is the "fluid" half of responsive design; the grid handles the structural half.

When the layout must change shape — not just size — you need a query. A media query reacts to the viewport (the whole window): @media (min-width: 600px) {' … '} applies its rules only when the window is at least 600px wide. That is how a stacked layout becomes a sidebar layout on bigger screens.

A container query reacts to the size of a component's own container, not the window. Mark an ancestor with container-type: inline-size , then @container (min-width: 400px) {' … '} styles the child based on how much room it has. The same card can stack in a narrow sidebar and go side-by-side in a wide main column — on the same page. Use media queries for page structure, container queries for reusable components.

Mobile-first means your base CSS (everything outside a media query) describes the small-screen layout, and you add complexity for bigger screens with min-width queries. The base is the simplest case and always applies, so a phone gets a working single-column layout before any query runs. You layer on multi-column grids and a sidebar as the screen grows — never the other way round.

Here is the capstone: a full page that pulls every piece together — a flexbox nav, a hero with clamp() type, an auto-fit feature grid, :root design tokens, and one mobile-first media query for the sidebar. No Bootstrap, no Tailwind — just the platform.

This grid is fixed at three columns, so it overflows on a phone. Replace the fixed columns with an auto-fit / minmax() track so it reflows on its own. Fill in the blanks marked ___ , then run it and check the comment.

Make the heading fluid with clamp() , then add one mobile-first media query that turns the single-column layout into a two-column sidebar layout on wide screens. Fill in the blanks and verify against the comment.

Support is faded now — only an outline is given. Build a small pricing section from scratch using the techniques from this lesson. Lean on the worked examples in sections 1, 3, and 5 if you get stuck.

You can now build a fully responsive page with pure modern CSS. The essentials:

Next up: the Final Project , where you'll combine everything in this course into one complete site.

Practice quiz

What does 'grid-template-columns: repeat(auto-fit, minmax(250px, 1fr))' do?

  • Always makes exactly 4 columns
  • Stacks all items in one column
  • Makes as many columns as fit, each at least 250px, sharing leftover space — no media queries
  • Creates fixed 250px columns that overflow

Answer: Makes as many columns as fit, each at least 250px, sharing leftover space — no media queries. auto-fit packs in as many minmax(250px, 1fr) tracks as fit, then stretches them to fill the row, so the grid reflows without breakpoints.

In repeat(), how does auto-fit differ from auto-fill when there are few items?

  • auto-fit collapses empty tracks so items stretch to fill the row; auto-fill keeps empty tracks
  • They are identical
  • auto-fill is faster
  • auto-fit only works vertically

Answer: auto-fit collapses empty tracks so items stretch to fill the row; auto-fill keeps empty tracks. auto-fit collapses empty column tracks to zero width so existing items expand; auto-fill keeps the empty tracks, leaving gaps.

Which single property makes a flexbox row wrap onto a new line when items no longer fit?

  • flex-grow: 1
  • justify-content: space-between
  • align-items: stretch
  • flex-wrap: wrap

Answer: flex-wrap: wrap. flex-wrap: wrap lets items that don't fit drop onto the next line instead of squashing or overflowing.

What does clamp(min, preferred, max) return?

  • Always the min
  • The preferred value, but never below min or above max
  • The average of all three
  • Always the max

Answer: The preferred value, but never below min or above max. clamp() uses the preferred (middle) value, clamped between the floor (min) and ceiling (max) — it equals max(min, min(preferred, max)).

Why is 'font-size: clamp(1.5rem, 5vw, 3rem)' useful for headings?

  • It scales with the viewport but stays between 1.5rem and 3rem
  • It disables zoom
  • It forces a fixed pixel size
  • It only works on mobile

Answer: It scales with the viewport but stays between 1.5rem and 3rem. The heading grows fluidly with 5vw but is locked between a 1.5rem floor and a 3rem ceiling, so it never becomes too small or comically large.

When should you use a media query instead of clamp() or auto-fit?

  • For any color change
  • Never — they are interchangeable
  • When the layout must change shape — e.g. a sidebar stacking below content
  • Only for font sizing

Answer: When the layout must change shape — e.g. a sidebar stacking below content. Use clamp/auto-fit for continuous sizing; use a media query when the layout structure itself must rearrange (stacking, collapsing nav, column count changes).

What does a container query react to, unlike a media query?

  • The browser's zoom level
  • The size of a component's own container, not the viewport
  • The device's pixel density
  • The page's scroll position

Answer: The size of a component's own container, not the viewport. A container query (container-type: inline-size + @container) adapts a component to the space its container gives it, so the same card can adapt differently in a sidebar vs a wide column.

What does the mobile-first base CSS (outside any media query) describe?

  • The desktop layout
  • Only print styles
  • The largest possible layout
  • The small-screen layout, enhanced later with min-width queries

Answer: The small-screen layout, enhanced later with min-width queries. Mobile-first puts the simplest small-screen layout in the base styles, then layers on complexity for bigger screens with min-width queries.

Which reset is recommended at the top of a framework-free stylesheet to keep grid math sane?

  • * { margin: auto; }
  • *, *::before, *::after { box-sizing: border-box; }
  • * { display: flex; }
  • html { font-size: 62.5%; }

Answer: *, *::before, *::after { box-sizing: border-box; }. box-sizing: border-box makes padding and borders count inside the declared width, so they don't blow out your grid and column math.

Why wrap the minmax floor as 'minmax(min(100%, 250px), 1fr)'?

  • It centers the grid
  • It adds a media query automatically
  • It stops a column overflowing on screens narrower than 250px
  • It doubles the column count

Answer: It stops a column overflowing on screens narrower than 250px. min(100%, 250px) caps the floor at 100% of the container, so on very narrow screens the column shrinks to fit instead of overflowing.