Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Support opacity values in increments of `0.25` by default ([#14980](https://github.com/tailwindlabs/tailwindcss/pull/14980))
- Support specifying the color interpolation method for gradients via modifier ([#14984](https://github.com/tailwindlabs/tailwindcss/pull/14984))
- Reintroduce `container` component as a utility ([#14993](https://github.com/tailwindlabs/tailwindcss/pull/14993))
- Reintroduce `container` component as a utility ([#14993](https://github.com/tailwindlabs/tailwindcss/pull/14993), [#14999](https://github.com/tailwindlabs/tailwindcss/pull/14999))
- _Upgrade (experimental)_: Migrate `container` component configuration to CSS ([#14999](https://github.com/tailwindlabs/tailwindcss/pull/14999))

### Fixed

Expand Down
93 changes: 93 additions & 0 deletions integrations/upgrade/js-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1435,4 +1435,97 @@ describe('border compatibility', () => {
`)
},
)

test(
'migrates `container` component configurations',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'

export default {
content: ['./src/**/*.html'],
theme: {
container: {
center: true,
padding: {
DEFAULT: '2rem',
'2xl': '4rem',
},
screens: {
md: '48rem', // Matches a default --breakpoint
xl: '1280px',
'2xl': '1536px',
},
},
},
} satisfies Config
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
'src/index.html': html`
<div class="container"></div>
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(`
"
--- src/index.html ---
<div class="container"></div>

--- src/input.css ---
@import 'tailwindcss';

@utility container {
margin-inline: auto;
padding-inline: 2rem;
@media (width >= theme(--breakpoint-sm)) {
max-width: none;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 1280px) {
max-width: 1280px;
}
@media (width >= 1536px) {
max-width: 1536px;
padding-inline: 4rem;
}
}

/*
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.

If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
"
`)
},
)
})
11 changes: 10 additions & 1 deletion packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { fileURLToPath } from 'node:url'
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
import { loadModule } from '../../@tailwindcss-node/src/compile'
import { toCss, type AstNode } from '../../tailwindcss/src/ast'
import { atRule, toCss, type AstNode } from '../../tailwindcss/src/ast'
import {
keyPathToCssProperty,
themeableValues,
} from '../../tailwindcss/src/compat/apply-config-to-theme'
import { keyframesToRules } from '../../tailwindcss/src/compat/apply-keyframes-to-theme'
import { resolveConfig, type ConfigFile } from '../../tailwindcss/src/compat/config/resolve-config'
import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/config/types'
import { buildCustomContainerUtilityRules } from '../../tailwindcss/src/compat/container'
import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode'
import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { escape } from '../../tailwindcss/src/utils/escape'
Expand Down Expand Up @@ -148,6 +149,14 @@ async function migrateTheme(
}

css += '}\n' // @theme

if ('container' in resolvedConfig.theme) {
let rules = buildCustomContainerUtilityRules(resolvedConfig.theme.container, designSystem)
if (rules.length > 0) {
css += '\n' + toCss([atRule('@utility', 'container', rules)])
}
}

css += '}\n' // @tw-bucket

return css
Expand Down
2 changes: 2 additions & 0 deletions packages/tailwindcss/src/compat/apply-compat-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { applyKeyframesToTheme } from './apply-keyframes-to-theme'
import { createCompatConfig } from './config/create-compat-config'
import { resolveConfig } from './config/resolve-config'
import type { UserConfig } from './config/types'
import { registerContainerCompat } from './container'
import { darkModePlugin } from './dark-mode'
import { buildPluginApi, type CssPluginOptions, type Plugin } from './plugin-api'
import { registerScreensConfig } from './screens-config'
Expand Down Expand Up @@ -239,6 +240,7 @@ function upgradeToFullPluginSupport({

registerThemeVariantOverrides(resolvedUserConfig, designSystem)
registerScreensConfig(resolvedUserConfig, designSystem)
registerContainerCompat(resolvedUserConfig, designSystem)

// If a prefix has already been set in CSS don't override it
if (!designSystem.theme.prefix && resolvedConfig.prefix) {
Expand Down
3 changes: 3 additions & 0 deletions packages/tailwindcss/src/compat/apply-config-to-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ export function themeableValues(config: ResolvedConfig['theme']): [string[], unk
const IS_VALID_KEY = /^[a-zA-Z0-9-_%/\.]+$/

export function keyPathToCssProperty(path: string[]) {
// The legacy container component config should not be included in the Theme
if (path[0] === 'container') return null

path = structuredClone(path)

if (path[0] === 'animation') path[0] = 'animate'
Expand Down
Loading