diff --git a/CHANGELOG.md b/CHANGELOG.md index 247fe492c465..c8e0736f714c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add consistent base styles for buttons and form controls ([#15036](https://github.com/tailwindlabs/tailwindcss/pull/15036)) - _Upgrade (experimental)_: Convert `group-[]:flex` to `in-[.group]:flex` ([#15054](https://github.com/tailwindlabs/tailwindcss/pull/15054)) +- _Upgrade (experimental)_: Add form reset styles to CSS files for compatibility with v3 ([#15036](https://github.com/tailwindlabs/tailwindcss/pull/15036)) ### Fixed diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index f4dbb2ad2c5a..1d91788d3e7e 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -131,6 +131,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @utility foo { color: red; @@ -223,6 +238,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } .btn { @apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white; @@ -287,6 +317,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } .a { @apply flex; @@ -359,6 +404,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @layer base { html { @@ -436,6 +496,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @utility btn { @apply rounded-md px-2 py-1 bg-blue-500 text-white; @@ -545,6 +620,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/base.css --- html { @@ -1049,6 +1139,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/utilities.css --- @utility no-scrollbar { @@ -1497,6 +1602,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/root.2/index.css --- /* Already contains @config */ @@ -1521,6 +1641,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/root.3/index.css --- /* Inject missing @config above first @theme */ @@ -1555,6 +1690,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/root.4/index.css --- /* Inject missing @config due to nested imports with tailwind imports */ @@ -1584,6 +1734,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/root.4/utilities.css --- @import 'tailwindcss/utilities' layer(utilities); @@ -1615,6 +1780,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, @@ -1795,6 +1975,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @layer base { html { @@ -1931,6 +2126,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @layer base { html { @@ -2048,6 +2258,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @layer base { html { @@ -2123,6 +2348,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/styles/components.css --- .btn { @@ -2340,6 +2580,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- index.html ---
@@ -2422,6 +2677,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- index.html ---
@@ -2534,6 +2804,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- ./src/components.css --- @import './typography.css'; diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index e0173bcf367c..e683986d1cb6 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -284,6 +284,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- src/test.js --- export default { @@ -393,6 +408,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) @@ -475,6 +505,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) @@ -549,6 +594,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) @@ -627,6 +687,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) @@ -701,6 +776,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) @@ -813,6 +903,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- project-b/src/input.css --- @import 'tailwindcss'; @@ -838,6 +943,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, @@ -915,6 +1035,21 @@ test( border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, @@ -975,6 +1110,21 @@ describe('border compatibility', () => { border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, @@ -1037,6 +1187,21 @@ describe('border compatibility', () => { border-color: oklch(0.623 0.214 259.815); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, @@ -1081,6 +1246,22 @@ describe('border compatibility', () => { " --- src/input.css --- @import 'tailwindcss'; + + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, @@ -1141,6 +1322,21 @@ describe('border compatibility', () => { border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, @@ -1210,6 +1406,21 @@ describe('border compatibility', () => { border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } --- src/input.css --- @import './base.css'; @@ -1328,6 +1539,21 @@ describe('border compatibility', () => { border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } .container { width: calc(var(--spacing) * 2); @@ -1442,6 +1668,21 @@ describe('border compatibility', () => { border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } .container { width: var(--spacing-2); @@ -1545,6 +1786,21 @@ describe('border compatibility', () => { border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } " `) }, diff --git a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap index 17c977f96e35..263e01037d26 100644 --- a/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap +++ b/packages/@tailwindcss-postcss/src/__snapshots__/index.test.ts.snap @@ -393,6 +393,11 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` font-feature-settings: var(--default-font-feature-settings, normal); font-variation-settings: var(--default-font-variation-settings, normal); -webkit-tap-highlight-color: transparent; + --lightningcss-light: initial; + --lightningcss-dark: ; + --lightningcss-light: initial; + --lightningcss-dark: ; + color-scheme: light; } body { @@ -459,7 +464,33 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` border-collapse: collapse; } - button, input, optgroup, select, textarea { + :-moz-focusring { + outline: auto; + } + + progress { + vertical-align: baseline; + } + + summary { + display: list-item; + } + + ol, ul, menu { + list-style: none; + } + + img, svg, video, canvas, audio, iframe, embed, object { + vertical-align: middle; + display: block; + } + + img, video { + max-width: 100%; + height: auto; + } + + button, input, select, optgroup, textarea { font: inherit; font-feature-settings: inherit; font-variation-settings: inherit; @@ -475,34 +506,13 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` color: inherit; } - button, input:where([type="button"], [type="reset"], [type="submit"]) { - appearance: button; - background: none; - } - - ::file-selector-button { - appearance: button; - background: none; - } - - :-moz-focusring { - outline: auto; - } - - :-moz-ui-invalid { - box-shadow: none; - } - - progress { - vertical-align: baseline; - } - - ::-webkit-inner-spin-button { - height: auto; + ::placeholder { + opacity: 1; + color: color-mix(in oklch, currentColor 50%, transparent); } - ::-webkit-outer-spin-button { - height: auto; + textarea { + resize: vertical; } ::-webkit-search-decoration { @@ -558,31 +568,107 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = ` padding-block: 0; } - summary { - display: list-item; + :-moz-ui-invalid { + box-shadow: none; } - ol, ul, menu { - list-style: none; + button, input:where([type="button"], [type="reset"], [type="submit"]) { + appearance: button; } - textarea { - resize: vertical; + ::file-selector-button { + appearance: button; } - ::placeholder { + ::-webkit-inner-spin-button { + height: auto; + } + + ::-webkit-outer-spin-button { + height: auto; + } + + select, textarea, input:where([type="text"], [type="email"], [type="password"], [type="date"], [type="datetime-local"], [type="month"], [type="number"], [type="search"], [type="time"], [type="week"], [type="tel"], [type="url"]) { + color: var(--lightningcss-light, #000) var(--lightningcss-dark, #fff); + background-color: var(--lightningcss-light, #fff) var(--lightningcss-dark, #ffffff1a); + border: 1px solid var(--lightningcss-light, #00000080) var(--lightningcss-dark, #ffffff40); + border-radius: 3px; + padding-block: 2px; + padding-inline: 4px; + } + + :where(select:not([multiple], [size])) option, :where(select:not([multiple], [size])) optgroup { + color: fieldtext; + background-color: field; + } + + :where(select:is([multiple], [size])) optgroup { + font-weight: bold; + } + + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + + select:where(:disabled), textarea:where(:disabled), input:where([type="text"], [type="email"], [type="password"], [type="date"], [type="datetime-local"], [type="month"], [type="number"], [type="search"], [type="time"], [type="week"], [type="tel"], [type="url"]):where(:disabled) { opacity: 1; - color: color-mix(in oklch, currentColor 50%, transparent); + color: var(--lightningcss-light, #00000080) var(--lightningcss-dark, #ffffff80); + background-color: var(--lightningcss-light, #00000006) var(--lightningcss-dark, #ffffff1a); + border-color: var(--lightningcss-light, #0003) var(--lightningcss-dark, #ffffff26); } - img, svg, video, canvas, audio, iframe, embed, object { - vertical-align: middle; - display: block; + button, input:where([type="button"], [type="reset"], [type="submit"]) { + color: var(--lightningcss-light, #000) var(--lightningcss-dark, #fff); + background-color: var(--lightningcss-light, #0000000d) var(--lightningcss-dark, #fff6); + border: 1px solid var(--lightningcss-light, #00000080) var(--lightningcss-dark, #ffffff1a); + border-radius: 4px; + padding-block: 2px; + padding-inline: 4px; } - img, video { - max-width: 100%; - height: auto; + ::file-selector-button { + color: var(--lightningcss-light, #000) var(--lightningcss-dark, #fff); + background-color: var(--lightningcss-light, #0000000d) var(--lightningcss-dark, #fff6); + border: 1px solid var(--lightningcss-light, #00000080) var(--lightningcss-dark, #ffffff1a); + border-radius: 4px; + padding-block: 2px; + padding-inline: 4px; + } + + button:where(:hover), input:where([type="button"], [type="reset"], [type="submit"]):where(:hover) { + background-color: var(--lightningcss-light, #0000001a) var(--lightningcss-dark, #ffffff73); + } + + ::file-selector-button:where(:hover) { + background-color: var(--lightningcss-light, #0000001a) var(--lightningcss-dark, #ffffff73); + } + + button:where(:active), input:where([type="button"], [type="reset"], [type="submit"]):where(:active) { + background-color: var(--lightningcss-light, #00000006) var(--lightningcss-dark, #ffffff4d); + } + + ::file-selector-button:where(:active) { + background-color: var(--lightningcss-light, #00000006) var(--lightningcss-dark, #ffffff4d); + } + + button:where(:disabled), input:where([type="button"], [type="reset"], [type="submit"]):where(:disabled) { + opacity: 1; + background-color: var(--lightningcss-light, #00000006) var(--lightningcss-dark, #ffffff40); + border-color: var(--lightningcss-light, #0003) var(--lightningcss-dark, #ffffff1a); + } + + :where(input:disabled)::file-selector-button { + opacity: 1; + background-color: var(--lightningcss-light, #00000006) var(--lightningcss-dark, #ffffff40); + border-color: var(--lightningcss-light, #0003) var(--lightningcss-dark, #ffffff1a); + } + + input:where([type="file"]:disabled) { + color: var(--lightningcss-light, #00000080) var(--lightningcss-dark, #ffffff80); + } + + ::file-selector-button { + margin-inline-end: 4px; } [hidden]:where(:not([hidden="until-found"])) { diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-forms-reset.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-forms-reset.test.ts new file mode 100644 index 000000000000..da7118ab223c --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-forms-reset.test.ts @@ -0,0 +1,305 @@ +import dedent from 'dedent' +import postcss from 'postcss' +import { expect, it } from 'vitest' +import { formatNodes } from './format-nodes' +import { migrateFormsReset } from './migrate-forms-reset' +import { sortBuckets } from './sort-buckets' + +const css = dedent + +async function migrate(input: string) { + return postcss() + .use(migrateFormsReset()) + .use(sortBuckets()) + .use(formatNodes()) + .process(input, { from: expect.getState().testPath }) + .then((result) => result.css) +} + +it("should add compatibility CSS after the `@import 'tailwindcss'`", async () => { + expect( + await migrate(css` + @import 'tailwindcss'; + `), + ).toMatchInlineSnapshot(` + "@import 'tailwindcss'; + + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + }" + `) +}) + +it('should add the compatibility CSS after the last `@import`', async () => { + expect( + await migrate(css` + @import 'tailwindcss'; + @import './foo.css'; + @import './bar.css'; + `), + ).toMatchInlineSnapshot(` + "@import 'tailwindcss'; + @import './foo.css'; + @import './bar.css'; + + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + }" + `) +}) + +it('should add the compatibility CSS after the last import, even if a body-less `@layer` exists', async () => { + expect( + await migrate(css` + @charset "UTF-8"; + @layer foo, bar, baz, base; + + /**! + * License header + */ + + @import 'tailwindcss'; + @import './foo.css'; + @import './bar.css'; + `), + ).toMatchInlineSnapshot(` + "@charset "UTF-8"; + @layer foo, bar, baz, base; + + /**! + * License header + */ + + @import 'tailwindcss'; + @import './foo.css'; + @import './bar.css'; + + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + }" + `) +}) + +it('should add the compatibility CSS before the first `@layer base` (if the "tailwindcss" import exists)', async () => { + expect( + await migrate(css` + @import 'tailwindcss'; + + @variant foo { + } + + @utility bar { + } + + @layer base { + } + + @utility baz { + } + + @layer base { + } + `), + ).toMatchInlineSnapshot(` + "@import 'tailwindcss'; + + @variant foo { + } + + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } + + @utility bar { + } + + @utility baz { + } + + @layer base { + } + + @layer base { + }" + `) +}) + +it('should add the compatibility CSS before the first `@layer base` (if the "tailwindcss/preflight" import exists)', async () => { + expect( + await migrate(css` + @import 'tailwindcss/preflight'; + + @variant foo { + } + + @utility bar { + } + + @layer base { + } + + @utility baz { + } + + @layer base { + } + `), + ).toMatchInlineSnapshot(` + "@import 'tailwindcss/preflight'; + + @variant foo { + } + + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } + + @utility bar { + } + + @utility baz { + } + + @layer base { + } + + @layer base { + }" + `) +}) + +it('should not add the backwards compatibility CSS when no `@import "tailwindcss"` or `@import "tailwindcss/preflight"` exists', async () => { + expect( + await migrate(css` + @variant foo { + } + + @utility bar { + } + + @layer base { + } + + @utility baz { + } + + @layer base { + } + `), + ).toMatchInlineSnapshot(` + "@variant foo { + } + + @utility bar { + } + + @utility baz { + } + + @layer base { + } + + @layer base { + }" + `) +}) + +it('should not add the backwards compatibility CSS when another `@import "tailwindcss"` import exists such as theme or utilities', async () => { + expect( + await migrate(css` + @import 'tailwindcss/theme'; + + @variant foo { + } + + @utility bar { + } + + @layer base { + } + + @utility baz { + } + + @layer base { + } + `), + ).toMatchInlineSnapshot(` + "@import 'tailwindcss/theme'; + + @variant foo { + } + + @utility bar { + } + + @utility baz { + } + + @layer base { + } + + @layer base { + }" + `) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-forms-reset.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-forms-reset.ts new file mode 100644 index 000000000000..875095318c80 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-forms-reset.ts @@ -0,0 +1,49 @@ +import dedent from 'dedent' +import postcss, { type Plugin, type Root } from 'postcss' + +const css = dedent + +const FORMS_RESET_CSS = css` + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } +` + +export function migrateFormsReset(): Plugin { + function migrate(root: Root) { + let isTailwindRoot = false + root.walkAtRules('import', (node) => { + if ( + /['"]tailwindcss['"]/.test(node.params) || + /['"]tailwindcss\/preflight['"]/.test(node.params) + ) { + isTailwindRoot = true + return false + } + }) + + if (!isTailwindRoot) return + + let compatibilityCssString = `\n@tw-bucket compatibility {\n${FORMS_RESET_CSS}\n}\n` + let compatibilityCss = postcss.parse(compatibilityCssString) + + root.append(compatibilityCss) + } + + return { + postcssPlugin: '@tailwindcss/upgrade/migrate-forms-reset', + OnceExit: migrate, + } +} diff --git a/packages/@tailwindcss-upgrade/src/index.test.ts b/packages/@tailwindcss-upgrade/src/index.test.ts index 65d74937b42b..1381480714fd 100644 --- a/packages/@tailwindcss-upgrade/src/index.test.ts +++ b/packages/@tailwindcss-upgrade/src/index.test.ts @@ -113,6 +113,21 @@ it('should migrate a stylesheet', async () => { border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @utility b { z-index: 2; @@ -183,6 +198,21 @@ it('should migrate a stylesheet (with imports)', async () => { ::file-selector-button { border-color: var(--color-gray-200, currentColor); } + } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } }" `) }) @@ -226,6 +256,21 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in border-color: var(--color-gray-200, currentColor); } } + /* + In Tailwind CSS v4, basic styles are applied to form elements by default. To + maintain compatibility with v3, the following resets have been added: + */ + @layer base { + input, + textarea, + select, + button { + border: 0px solid; + border-radius: 0; + padding: 0; + background-color: transparent; + } + } @layer base { html { diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts index 1e6af335ab5d..7ddca0e520e3 100644 --- a/packages/@tailwindcss-upgrade/src/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/migrate.ts @@ -10,6 +10,7 @@ import { migrateAtApply } from './codemods/migrate-at-apply' import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities' import { migrateBorderCompatibility } from './codemods/migrate-border-compatibility' import { migrateConfig } from './codemods/migrate-config' +import { migrateFormsReset } from './codemods/migrate-forms-reset' import { migrateImport } from './codemods/migrate-import' import { migrateMediaScreen } from './codemods/migrate-media-screen' import { migrateMissingLayers } from './codemods/migrate-missing-layers' @@ -51,6 +52,7 @@ export async function migrateContents( .use(migrateTailwindDirectives(options)) .use(migrateConfig(stylesheet, options)) .use(migrateBorderCompatibility(options)) + .use(migrateFormsReset()) .use(migrateThemeToVar(options)) .process(stylesheet.root, { from: stylesheet.file ?? undefined }) } diff --git a/packages/tailwindcss/preflight.css b/packages/tailwindcss/preflight.css index 6b8cbbae41db..7ccadd35d6ff 100644 --- a/packages/tailwindcss/preflight.css +++ b/packages/tailwindcss/preflight.css @@ -23,6 +23,7 @@ 5. Use the user's configured `sans` font-feature-settings by default. 6. Use the user's configured `sans` font-variation-settings by default. 7. Disable tap highlights on iOS. + 8. Set a default `color-scheme`. */ html, @@ -43,6 +44,7 @@ html, font-feature-settings: var(--default-font-feature-settings, normal); /* 5 */ font-variation-settings: var(--default-font-variation-settings, normal); /* 6 */ -webkit-tap-highlight-color: transparent; /* 7 */ + color-scheme: light; /* 8 */ } /* @@ -175,67 +177,102 @@ table { } /* - Inherit font styles in all browsers. + Use the modern Firefox focus style for all focusable elements. */ -button, -input, -optgroup, -select, -textarea, -::file-selector-button { - font: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - letter-spacing: inherit; - color: inherit; +:-moz-focusring { + outline: auto; } /* - 1. Remove the default background color of buttons by default. - 2. Correct the inability to style the border radius in iOS Safari. + Add the correct vertical alignment in Chrome and Firefox. */ -button, -input:where([type='button'], [type='reset'], [type='submit']), -::file-selector-button { - background: transparent; /* 1 */ - appearance: button; /* 2 */ +progress { + vertical-align: baseline; } /* - Use the modern Firefox focus style for all focusable elements. + Add the correct display in Chrome and Safari. */ -:-moz-focusring { - outline: auto; +summary { + display: list-item; } /* - Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) + Make lists unstyled by default. */ -:-moz-ui-invalid { - box-shadow: none; +ol, +ul, +menu { + list-style: none; } /* - Add the correct vertical alignment in Chrome and Firefox. + 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) + 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. */ -progress { - vertical-align: baseline; +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; /* 1 */ + vertical-align: middle; /* 2 */ } /* - Correct the cursor style of increment and decrement buttons in Safari. + Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) */ -::-webkit-inner-spin-button, -::-webkit-outer-spin-button { +img, +video { + max-width: 100%; height: auto; } +/* + Inherit font styles in all browsers. +*/ + +button, +input, +select, +optgroup, +textarea, +::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; +} + +/* + 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) + 2. Set the default placeholder color to a semi-transparent version of the current text color. +*/ + +::placeholder { + opacity: 1; /* 1 */ + color: color-mix(in oklch, currentColor 50%, transparent); /* 2 */ +} + +/* + Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + /* Remove the inner padding in Chrome and Safari on macOS. */ @@ -283,67 +320,137 @@ progress { } /* - Add the correct display in Chrome and Safari. + Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) */ -summary { - display: list-item; +:-moz-ui-invalid { + box-shadow: none; } /* - Make lists unstyled by default. + Correct the inability to style the border radius in iOS Safari. */ -ol, -ul, -menu { - list-style: none; +button, +input:where([type='button'], [type='reset'], [type='submit']), +::file-selector-button { + appearance: button; } /* - Prevent resizing textareas horizontally by default. + Correct the cursor style of increment and decrement buttons in Safari. */ -textarea { - resize: vertical; +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; } /* - 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) - 2. Set the default placeholder color to a semi-transparent version of the current text color. + Apply consistent base form control styles across browsers. */ -::placeholder { - opacity: 1; /* 1 */ - color: color-mix(in oklch, currentColor 50%, transparent); /* 2 */ +select, +textarea, +input:where( + [type='text'], + [type='email'], + [type='password'], + [type='date'], + [type='datetime-local'], + [type='month'], + [type='number'], + [type='search'], + [type='time'], + [type='week'], + [type='tel'], + [type='url'] + ) { + border-radius: 3px; + padding-inline: 4px; + padding-block: 2px; + color: light-dark(black, white); + background-color: light-dark(white, rgb(255 255 255 / 10%)); + border: 1px solid light-dark(rgb(0 0 0 / 50%), rgb(255 255 255 / 25%)); +} + +:where(select:not([multiple], [size])) option, +:where(select:not([multiple], [size])) optgroup { + color: FieldText; + background-color: Field; +} + +:where(select:is([multiple], [size])) optgroup { + font-weight: bold; +} + +:where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; +} + +select:where(:disabled), +textarea:where(:disabled), +input:where( + [type='text'], + [type='email'], + [type='password'], + [type='date'], + [type='datetime-local'], + [type='month'], + [type='number'], + [type='search'], + [type='time'], + [type='week'], + [type='tel'], + [type='url'] + ):where(:disabled) { + opacity: 1; + color: light-dark(rgb(0 0 0 / 50%), rgb(255 255 255 / 50%)); + background-color: light-dark(rgb(0 0 0 / 2.5%), rgb(255 255 255 / 10%)); + border-color: light-dark(rgb(0 0 0 / 20%), rgb(255 255 255 / 15%)); } /* - 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) - 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) - This can trigger a poorly considered lint error in some tools but is included by design. + Apply consistent base button styles across browsers. */ -img, -svg, -video, -canvas, -audio, -iframe, -embed, -object { - display: block; /* 1 */ - vertical-align: middle; /* 2 */ +button, +::file-selector-button, +input:where([type='button'], [type='reset'], [type='submit']) { + border-radius: 4px; + padding-inline: 4px; + padding-block: 2px; + color: light-dark(#000, #fff); + background-color: light-dark(rgb(0 0 0 / 5%), rgb(255 255 255 / 40%)); + border: 1px solid light-dark(rgb(0 0 0 / 50%), rgb(255 255 255 / 10%)); } -/* - Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) -*/ +button:where(:hover), +::file-selector-button:where(:hover), +input:where([type='button'], [type='reset'], [type='submit']):where(:hover) { + background-color: light-dark(rgb(0 0 0 / 10%), rgb(255 255 255 / 45%)); +} -img, -video { - max-width: 100%; - height: auto; +button:where(:active), +::file-selector-button:where(:active), +input:where([type='button'], [type='reset'], [type='submit']):where(:active) { + background-color: light-dark(rgb(0 0 0 / 2.5%), rgb(255 255 255 / 30%)); +} + +button:where(:disabled), +:where(input:disabled)::file-selector-button, +input:where([type='button'], [type='reset'], [type='submit']):where(:disabled) { + opacity: 1; + background-color: light-dark(rgb(0 0 0 / 2.5%), rgb(255 255 255 / 25%)); + border-color: light-dark(rgb(0 0 0 / 20%), rgb(255 255 255 / 10%)); +} + +input:where([type='file']:disabled) { + color: light-dark(rgb(0 0 0 / 50%), rgb(255 255 255 / 50%)); +} + +::file-selector-button { + margin-inline-end: 4px; } /*