Repository: tailwindlabs/tailwindcss

Problem statement:
[v4] Styles are not applied to the "container" class
**What version of Tailwind CSS are you using?**
v.next

**What build tool (or framework if it abstracts the build tool) are you using?**
vite (v5.1.5), @builder.io/qwik (v1.5.1), @builder.io/qwik-city (v1.5.1), @tailwindcss/vite (v.next)

**What version of Node.js are you using?**
v20.9.0

**What browser are you using?**
Chrome, Safari, Edge

**What operating system are you using?**
macOS

**Reproduction URL** 
https://codesandbox.io/p/devbox/peaceful-hertz-jx9sd2?file=%2Fsrc%2Froutes%2Findex.tsx

**Describe your issue**

In TailwindCSS version 4, it seems that the styles of the "container" class are not loaded. In previous versions the "container" class works correctly.


Hints:
I am using SvelteKit and I can comfirm this.
Using Vite with nuxt with same problem
I guess it is not implemented yet, I don't see it in the [packages/tailwindcss/src/utilities.ts](https://github.com/tailwindlabs/tailwindcss/blob/next/packages/tailwindcss/src/utilities.ts).
@adamwathan 

Has any decision been made regarding adding this class? I think it is a very useful and widely used class.
What could we use to replace the container while you guys figure out how to tackle the inclusion of the container?
up
I made the below suggestion in closed PR https://github.com/tailwindlabs/tailwindcss/pull/13395#issuecomment-2327178695, but I think it's more relevant here:

> maybe the real ask here is a better, simpler v4 container utility. The v3 utility was for one very specific use case, whereas I could imagine v4 utilities that are both a lot simpler and a lot more like the rest of Tailwind:
> 
> ```css
> .container-sm {
>   width: 100%;
>   max-width: var(--breakpoint-sm);
> }
> 
> .container-md {
>   width: 100%;
>   max-width: var(--breakpoint-md);
> }
> 
> /* etc. */
> ```
> 
> Users could easily recreate the v3 `container` utility themselves, including all of its configuration options:
> 
> ```css
> @utility container {
>   /* Set the `max-width` to match the `min-width` of the current breakpoint */
>   @apply sm:container-sm md:container-md lg:container-lg xl:container-xl 2xl:container-2xl;
> 
>   /* Center by default */
>   @apply mx-auto;
> 
>   /* Add horizontal padding */
>   @apply px-4 sm:px-8;
> }
> ```

Patch:
diff --git a/packages/tailwindcss/src/property-order.ts b/packages/tailwindcss/src/property-order.ts
--- a/packages/tailwindcss/src/property-order.ts
+++ b/packages/tailwindcss/src/property-order.ts
@@ -28,6 +28,10 @@ export default [
   'float',
   'clear',
 
+  // Ensure that the included `container` class is always sorted before any
+  // custom container extensions
+  '--tw-container-component',
+
   // How do we make `mx-0` come before `mt-0`?
   // Idea: `margin-x` property that we compile away with a Visitor plugin?
   'margin',
diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts
--- a/packages/tailwindcss/src/utilities.ts
+++ b/packages/tailwindcss/src/utilities.ts
@@ -1,6 +1,7 @@
 import { atRoot, atRule, decl, styleRule, type AstNode } from './ast'
 import type { Candidate, CandidateModifier, NamedUtilityValue } from './candidate'
 import type { Theme, ThemeKey } from './theme'
+import { compareBreakpoints } from './utils/compare-breakpoints'
 import { DefaultMap } from './utils/default-map'
 import {
   inferDataType,
@@ -897,6 +898,18 @@ export function createUtilities(theme: Theme) {
     })
   }
 
+  utilities.static('container', () => {
+    let breakpoints = [...theme.namespace('--breakpoint').values()]
+    breakpoints.sort((a, z) => compareBreakpoints(a, z, 'asc'))
+
+    let decls: AstNode[] = [decl('--tw-sort', '--tw-container-component'), decl('width', '100%')]
+    for (let breakpoint of breakpoints) {
+      decls.push(atRule('@media', `(min-width: ${breakpoint})`, [decl('max-width', breakpoint)]))
+    }
+
+    return decls
+  })
+
   /**
    * @css `flex`
    */
diff --git a/packages/tailwindcss/src/utils/compare-breakpoints.ts b/packages/tailwindcss/src/utils/compare-breakpoints.ts
new file mode 100644
--- /dev/null
+++ b/packages/tailwindcss/src/utils/compare-breakpoints.ts
@@ -0,0 +1,48 @@
+export function compareBreakpoints(a: string, z: string, direction: 'asc' | 'desc') {
+  if (a === z) return 0
+
+  // Assumption: when a `(` exists, we are dealing with a CSS function.
+  //
+  // E.g.: `calc(100% - 1rem)`
+  let aIsCssFunction = a.indexOf('(')
+  let zIsCssFunction = z.indexOf('(')
+
+  let aBucket =
+    aIsCssFunction === -1
+      ? // No CSS function found, bucket by unit instead
+        a.replace(/[\d.]+/g, '')
+      : // CSS function found, bucket by function name
+        a.slice(0, aIsCssFunction)
+
+  let zBucket =
+    zIsCssFunction === -1
+      ? // No CSS function found, bucket by unit
+        z.replace(/[\d.]+/g, '')
+      : // CSS function found, bucket by function name
+        z.slice(0, zIsCssFunction)
+
+  let order =
+    // Compare by bucket name
+    (aBucket === zBucket ? 0 : aBucket < zBucket ? -1 : 1) ||
+    // If bucket names are the same, compare by value
+    (direction === 'asc' ? parseInt(a) - parseInt(z) : parseInt(z) - parseInt(a))
+
+  // If the groups are the same, and the contents are not numbers, the
+  // `order` will result in `NaN`. In this case, we want to make sorting
+  // stable by falling back to a string comparison.
+  //
+  // This can happen when using CSS functions such as `calc`.
+  //
+  // E.g.:
+  //
+  // - `min-[calc(100%-1rem)]` and `min-[calc(100%-2rem)]`
+  // - `@[calc(100%-1rem)]` and `@[calc(100%-2rem)]`
+  //
+  // In this scenario, we want to alphabetically sort `calc(100%-1rem)` and
+  // `calc(100%-2rem)` to make it deterministic.
+  if (Number.isNaN(order)) {
+    return a < z ? -1 : 1
+  }
+
+  return order
+}
diff --git a/packages/tailwindcss/src/variants.ts b/packages/tailwindcss/src/variants.ts
--- a/packages/tailwindcss/src/variants.ts
+++ b/packages/tailwindcss/src/variants.ts
@@ -13,6 +13,7 @@ import {
 } from './ast'
 import { type Variant } from './candidate'
 import type { Theme } from './theme'
+import { compareBreakpoints } from './utils/compare-breakpoints'
 import { DefaultMap } from './utils/default-map'
 import { isPositiveInteger } from './utils/infer-data-type'
 import { segment } from './utils/segment'
@@ -869,7 +870,7 @@ export function createVariants(theme: Theme): Variants {
     // Helper to compare variants by their resolved values, this is used by the
     // responsive variants (`sm`, `md`, ...), `min-*`, `max-*` and container
     // queries (`@`).
-    function compareBreakpoints(
+    function compareBreakpointVariants(
       a: Variant,
       z: Variant,
       direction: 'asc' | 'desc',
@@ -882,54 +883,7 @@ export function createVariants(theme: Theme): Variants {
       let zValue = lookup.get(z)
       if (zValue === null) return direction === 'asc' ? 1 : -1
 
-      if (aValue === zValue) return 0
-
-      // Assumption: when a `(` exists, we are dealing with a CSS function.
-      //
-      // E.g.: `calc(100% - 1rem)`
-      let aIsCssFunction = aValue.indexOf('(')
-      let zIsCssFunction = zValue.indexOf('(')
-
-      let aBucket =
-        aIsCssFunction === -1
-          ? // No CSS function found, bucket by unit instead
-            aValue.replace(/[\d.]+/g, '')
-          : // CSS function found, bucket by function name
-            aValue.slice(0, aIsCssFunction)
-
-      let zBucket =
-        zIsCssFunction === -1
-          ? // No CSS function found, bucket by unit
-            zValue.replace(/[\d.]+/g, '')
-          : // CSS function found, bucket by function name
-            zValue.slice(0, zIsCssFunction)
-
-      let order =
-        // Compare by bucket name
-        (aBucket === zBucket ? 0 : aBucket < zBucket ? -1 : 1) ||
-        // If bucket names are the same, compare by value
-        (direction === 'asc'
-          ? parseInt(aValue) - parseInt(zValue)
-          : parseInt(zValue) - parseInt(aValue))
-
-      // If the groups are the same, and the contents are not numbers, the
-      // `order` will result in `NaN`. In this case, we want to make sorting
-      // stable by falling back to a string comparison.
-      //
-      // This can happen when using CSS functions such as `calc`.
-      //
-      // E.g.:
-      //
-      // - `min-[calc(100%-1rem)]` and `min-[calc(100%-2rem)]`
-      // - `@[calc(100%-1rem)]` and `@[calc(100%-2rem)]`
-      //
-      // In this scenario, we want to alphabetically sort `calc(100%-1rem)` and
-      // `calc(100%-2rem)` to make it deterministic.
-      if (Number.isNaN(order)) {
-        return aValue < zValue ? -1 : 1
-      }
-
-      return order
+      return compareBreakpoints(aValue, zValue, direction)
     }
 
     // Breakpoints
@@ -978,7 +932,7 @@ export function createVariants(theme: Theme): Variants {
             { compounds: Compounds.AtRules },
           )
         },
-        (a, z) => compareBreakpoints(a, z, 'desc', resolvedBreakpoints),
+        (a, z) => compareBreakpointVariants(a, z, 'desc', resolvedBreakpoints),
       )
 
       variants.suggest(
@@ -1013,7 +967,7 @@ export function createVariants(theme: Theme): Variants {
             { compounds: Compounds.AtRules },
           )
         },
-        (a, z) => compareBreakpoints(a, z, 'asc', resolvedBreakpoints),
+        (a, z) => compareBreakpointVariants(a, z, 'asc', resolvedBreakpoints),
       )
 
       variants.suggest(
@@ -1072,7 +1026,7 @@ export function createVariants(theme: Theme): Variants {
             { compounds: Compounds.AtRules },
           )
         },
-        (a, z) => compareBreakpoints(a, z, 'desc', resolvedWidths),
+        (a, z) => compareBreakpointVariants(a, z, 'desc', resolvedWidths),
       )
 
       variants.suggest(
@@ -1119,7 +1073,7 @@ export function createVariants(theme: Theme): Variants {
             { compounds: Compounds.AtRules },
           )
         },
-        (a, z) => compareBreakpoints(a, z, 'asc', resolvedWidths),
+        (a, z) => compareBreakpointVariants(a, z, 'asc', resolvedWidths),
       )
 
       variants.suggest(


Test patch:
diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap
--- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap
+++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap
@@ -3375,6 +3375,7 @@ exports[`getClassList 1`] = `
   "contain-size",
   "contain-strict",
   "contain-style",
+  "container",
   "content-around",
   "content-baseline",
   "content-between",
diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts
--- a/packages/tailwindcss/src/utilities.test.ts
+++ b/packages/tailwindcss/src/utilities.test.ts
@@ -3148,6 +3148,230 @@ test('max-height', async () => {
   ).toEqual('')
 })
 
+describe('container', () => {
+  test('creates the right media queries and sorts it before width', async () => {
+    expect(
+      await compileCss(
+        css`
+          @theme {
+            --breakpoint-sm: 40rem;
+            --breakpoint-md: 48rem;
+            --breakpoint-lg: 64rem;
+            --breakpoint-xl: 80rem;
+            --breakpoint-2xl: 96rem;
+          }
+          @tailwind utilities;
+        `,
+        ['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'],
+      ),
+    ).toMatchInlineSnapshot(`
+      ":root {
+        --breakpoint-sm: 40rem;
+        --breakpoint-md: 48rem;
+        --breakpoint-lg: 64rem;
+        --breakpoint-xl: 80rem;
+        --breakpoint-2xl: 96rem;
+      }
+
+      .container {
+        width: 100%;
+      }
+
+      @media (width >= 40rem) {
+        .container {
+          max-width: 40rem;
+        }
+      }
+
+      @media (width >= 48rem) {
+        .container {
+          max-width: 48rem;
+        }
+      }
+
+      @media (width >= 64rem) {
+        .container {
+          max-width: 64rem;
+        }
+      }
+
+      @media (width >= 80rem) {
+        .container {
+          max-width: 80rem;
+        }
+      }
+
+      @media (width >= 96rem) {
+        .container {
+          max-width: 96rem;
+        }
+      }
+
+      .w-1\\/2 {
+        width: 50%;
+      }
+
+      .max-w-\\[var\\(--breakpoint-sm\\)\\] {
+        max-width: var(--breakpoint-sm);
+      }"
+    `)
+  })
+
+  test('sorts breakpoints based on unit and then in ascending aOrder', async () => {
+    expect(
+      await compileCss(
+        css`
+          @theme reference {
+            --breakpoint-lg: 64rem;
+            --breakpoint-xl: 80rem;
+            --breakpoint-3xl: 1600px;
+            --breakpoint-sm: 40em;
+            --breakpoint-2xl: 96rem;
+            --breakpoint-xs: 30px;
+            --breakpoint-md: 48em;
+          }
+          @tailwind utilities;
+        `,
+        ['container'],
+      ),
+    ).toMatchInlineSnapshot(`
+      ".container {
+        width: 100%;
+      }
+
+      @media (width >= 40em) {
+        .container {
+          max-width: 40em;
+        }
+      }
+
+      @media (width >= 48em) {
+        .container {
+          max-width: 48em;
+        }
+      }
+
+      @media (width >= 30px) {
+        .container {
+          max-width: 30px;
+        }
+      }
+
+      @media (width >= 1600px) {
+        .container {
+          max-width: 1600px;
+        }
+      }
+
+      @media (width >= 64rem) {
+        .container {
+          max-width: 64rem;
+        }
+      }
+
+      @media (width >= 80rem) {
+        .container {
+          max-width: 80rem;
+        }
+      }
+
+      @media (width >= 96rem) {
+        .container {
+          max-width: 96rem;
+        }
+      }"
+    `)
+  })
+
+  test('custom `@utility container` always follow the core utility ', async () => {
+    expect(
+      await compileCss(
+        css`
+          @theme {
+            --breakpoint-sm: 40rem;
+            --breakpoint-md: 48rem;
+            --breakpoint-lg: 64rem;
+            --breakpoint-xl: 80rem;
+            --breakpoint-2xl: 96rem;
+          }
+          @tailwind utilities;
+
+          @utility container {
+            margin-inline: auto;
+            padding-inline: 1rem;
+
+            @media (width >= theme(--breakpoint-sm)) {
+              padding-inline: 2rem;
+            }
+          }
+        `,
+        ['w-1/2', 'container', 'max-w-[var(--breakpoint-sm)]'],
+      ),
+    ).toMatchInlineSnapshot(`
+      ":root {
+        --breakpoint-sm: 40rem;
+        --breakpoint-md: 48rem;
+        --breakpoint-lg: 64rem;
+        --breakpoint-xl: 80rem;
+        --breakpoint-2xl: 96rem;
+      }
+
+      .container {
+        width: 100%;
+      }
+
+      @media (width >= 40rem) {
+        .container {
+          max-width: 40rem;
+        }
+      }
+
+      @media (width >= 48rem) {
+        .container {
+          max-width: 48rem;
+        }
+      }
+
+      @media (width >= 64rem) {
+        .container {
+          max-width: 64rem;
+        }
+      }
+
+      @media (width >= 80rem) {
+        .container {
+          max-width: 80rem;
+        }
+      }
+
+      @media (width >= 96rem) {
+        .container {
+          max-width: 96rem;
+        }
+      }
+
+      .container {
+        margin-inline: auto;
+        padding-inline: 1rem;
+      }
+
+      @media (width >= 40rem) {
+        .container {
+          padding-inline: 2rem;
+        }
+      }
+
+      .w-1\\/2 {
+        width: 50%;
+      }
+
+      .max-w-\\[var\\(--breakpoint-sm\\)\\] {
+        max-width: var(--breakpoint-sm);
+      }"
+    `)
+  })
+})
+
 test('flex', async () => {
   expect(
     await run([
@@ -16680,7 +16904,7 @@ describe('spacing utilities', () => {
     `)
   })
 
-  test('only multiples of 0.25 with no trailing zeroes are supported with the spacing multipler', async () => {
+  test('only multiples of 0.25 with no trailing zeroes are supported with the spacing multiplier', async () => {
     let { build } = await compile(css`
       @theme {
         --spacing: 4px;