Container Queries

Tailwind CSS · Lesson 14 of 27 6 min read Updated

Breakpoints like md: look at the width of the whole browser window. That works for page layout, but not for reusable components. A course card might sit in a wide main column on one page and in a narrow sidebar on another, on the same laptop screen. It should look different in each place, and the screen width cannot tell it which place it is in.

Container queries solve this. A component asks "how wide is my container?" instead of "how wide is the screen?". In this lesson you will mark containers, use the @sm: and @md: variants, name containers, target ranges and use container query units. Container queries are built into Tailwind 4; for the CSS behind them see Container Queries.

Mark a container, then query it

Two steps:

  1. Add @container to the parent. This makes it a query container (in CSS, container-type: inline-size).
  2. On the children, use variants that start with @: @sm:flex, @md:text-lg, @lg:grid-cols-3.

@md:flex-row means "use a row when my nearest @container ancestor is at least 28rem wide". The screen size does not matter at all.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Same card, two places</title>
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
</head>
<body class="bg-slate-100 p-6">
  <div class="flex flex-col gap-6 md:flex-row">
    <main class="@container flex-1">
      <article class="flex flex-col gap-4 rounded-xl bg-white p-4 @md:flex-row @md:items-center">
        <img src="https://course.ethicscomputer.in/assets/samples/landscape-400.svg" alt="" class="aspect-video w-full rounded-lg object-cover @md:w-40">
        <div>
          <h3 class="font-semibold @md:text-xl">Data analysis with Python</h3>
          <p class="text-sm text-slate-600">Weekend batch, Pune</p>
        </div>
      </article>
    </main>
    <aside class="@container w-full md:w-60">
      <article class="flex flex-col gap-4 rounded-xl bg-white p-4 @md:flex-row @md:items-center">
        <img src="https://course.ethicscomputer.in/assets/samples/landscape-400.svg" alt="" class="aspect-video w-full rounded-lg object-cover @md:w-40">
        <div>
          <h3 class="font-semibold @md:text-xl">Data analysis with Python</h3>
          <p class="text-sm text-slate-600">Weekend batch, Pune</p>
        </div>
      </article>
    </aside>
  </div>
</body>
</html>

The two cards have exactly the same classes. On a wide result window, the card in the wide <main> lays out as a row, while the card in the 15rem sidebar stays stacked, because its container is narrower than 28rem. Make the window narrow: now the sidebar drops below and becomes full width, and its card switches to a row too.

Container sizes

Container variants use their own size scale, which is smaller than the screen breakpoints because containers are usually smaller than screens:

VariantMinimum container width
@3xs:16rem (256px)
@2xs:18rem (288px)
@xs:20rem (320px)
@sm:24rem (384px)
@md:28rem (448px)
@lg:32rem (512px)
@xl:36rem (576px)
@2xl:42rem (672px)
@3xl: to @7xl:48rem up to 80rem

Notice that @sm: (24rem) is not the same as sm: (40rem). These match the --container-* theme sizes you already use with max-w-md and friends, so @md: triggers exactly when the container could hold a max-w-md element.

Ranges and one-off sizes

Container queries support the same tricks as breakpoints:

  • @max-md:hidden hides an element when the container is narrower than 28rem.
  • @sm:@max-md:flex applies only between 24rem and 28rem.
  • @min-[30rem]:flex and @max-[20rem]:text-sm use any size you need.

Named containers

@container variants look at the nearest container. When containers are nested, for example a card inside a dashboard panel, you may want a child to respond to an outer one. Give that container a name with a slash, and use the same name in the variant:

HTML
<section class="@container/panel">
  <div class="@container">
    <!-- Responds to the outer panel, not the inner div -->
    <ul class="grid gap-3 @lg/panel:grid-cols-3">...</ul>
  </div>
</section>

@container/panel creates a container named panel, and @lg/panel:grid-cols-3 checks that container's width.

A self-adjusting component

Container queries are best for components you reuse in many places: product cards, user profiles, stats widgets. This stats widget shows one, two or four columns depending only on where you put it:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Stats widget</title>
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
</head>
<body class="flex flex-col gap-6 bg-slate-100 p-6">
  <div class="@container w-full max-w-3xl">
    <dl class="grid gap-3 @xs:grid-cols-2 @2xl:grid-cols-4">
      <div class="rounded-lg bg-white p-3"><dt class="text-sm text-slate-500">Students</dt><dd class="text-2xl font-bold">1,240</dd></div>
      <div class="rounded-lg bg-white p-3"><dt class="text-sm text-slate-500">Batches</dt><dd class="text-2xl font-bold">36</dd></div>
      <div class="rounded-lg bg-white p-3"><dt class="text-sm text-slate-500">Teachers</dt><dd class="text-2xl font-bold">18</dd></div>
      <div class="rounded-lg bg-white p-3"><dt class="text-sm text-slate-500">Cities</dt><dd class="text-2xl font-bold">5</dd></div>
    </dl>
  </div>
  <div class="@container w-72">
    <dl class="grid gap-3 @xs:grid-cols-2 @2xl:grid-cols-4">
      <div class="rounded-lg bg-white p-3"><dt class="text-sm text-slate-500">Students</dt><dd class="text-2xl font-bold">1,240</dd></div>
      <div class="rounded-lg bg-white p-3"><dt class="text-sm text-slate-500">Batches</dt><dd class="text-2xl font-bold">36</dd></div>
    </dl>
  </div>
</body>
</html>

The wide container shows four columns; the 18rem-wide one (w-72) is below the @xs size of 20rem, so it shows one column. The widget never needed to know anything about the page.

Container query units and size containers

CSS also has container query units: cqw and cqi are 1% of the container's width (or inline size). With an arbitrary value, you can make a heading scale smoothly with its card: text-[7cqi]. Combine it with a minimum, such as text-[max(1.25rem,7cqi)], so it never becomes too small to read.

Plain @container only tracks width. If you need the container's height as well, for height-based units like cqb, Tailwind 4.3 added @container-size, which sets container-type: size. A size container must have a set height, because its content no longer gives it one.

Media queries or container queries?

  • Use breakpoints (md:, lg:) for page-level layout: how many columns the page has, whether the sidebar is shown, how big the main heading is.
  • Use container queries (@md:) inside reusable components, so they adapt to wherever you place them.

Common mistakes

  • Forgetting @container. Without a container ancestor, @md: variants never match.
  • Putting @container on the element you are styling. An element cannot query itself; the container must be a parent.
  • Mixing up @sm and sm. @sm: is a 24rem container; sm: is a 40rem screen.
  • Containers that collapse. A container with inline-size takes no width from its content. Inside a flex row, give it flex-1 or a width, or it may shrink to nothing.

Key points

  • Container queries style elements based on their container's width, not the screen's.
  • Add @container to a parent, then use @sm:, @md:, @lg: and so on in children.
  • Container sizes start at @3xs (16rem) and differ from screen breakpoints.
  • Use @max-*, ranges such as @sm:@max-md:, and arbitrary sizes such as @min-[30rem]:.
  • Name containers with @container/name and query them with @md/name:.
  • Container queries are built into v4; v3 needed a plugin. Tailwind 4.3 added @container-size.

Exercise

Build a "teacher profile" component that shows the avatar above the text when its container is narrow, and beside the text when the container is at least @sm (24rem) wide. Place the same component twice: once in a container w-64 wide, and once in a container w-full max-w-xl. Use {{samples}}/avatar.svg.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Teacher profile</title>
  <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script>
</head>
<body class="flex flex-col gap-6 bg-slate-100 p-6">
  <div class="@container w-64">
    <div class="flex flex-col items-center gap-3 rounded-xl bg-white p-4 text-center @sm:flex-row @sm:text-left">
      <img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="" class="size-16 shrink-0 rounded-full">
      <div><p class="font-semibold">Priya Menon</p><p class="text-sm text-slate-600">Teaches Java in Kochi</p></div>
    </div>
  </div>
  <div class="@container w-full max-w-xl">
    <div class="flex flex-col items-center gap-3 rounded-xl bg-white p-4 text-center @sm:flex-row @sm:text-left">
      <img src="https://course.ethicscomputer.in/assets/samples/avatar.svg" alt="" class="size-16 shrink-0 rounded-full">
      <div><p class="font-semibold">Priya Menon</p><p class="text-sm text-slate-600">Teaches Java in Kochi</p></div>
    </div>
  </div>
</body>
</html>

Both copies are identical. The w-64 container (16rem) is below @sm, so it stays stacked and centred; the wider container passes 24rem, so @sm:flex-row puts the avatar beside the text.

Quick Quiz

1. What does @md:flex-row check before applying flex-row?

2. Which class turns an element into a query container?

3. How do you make a child respond to an outer container named panel instead of its nearest container?

4. When should you prefer container queries over breakpoints?

Learn Tailwind CSS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call