Add consistent base styles for buttons and form controls (#15036)

This PR introduces consistent base styles for buttons and form controls
in Tailwind CSS v4.

## Motivation

In v3, form elements lack default styles, which can be
confusing—especially when certain elements, like a text input without a
placeholder or value, are rendered completely invisible on the page.

The goal of this change is to provide reasonable default styles for
buttons, inputs, selects, and textareas that are (mostly) consistent
across all browsers while remaining easy to customize with your own
styles.

This improvement should make Tailwind more accessible for developers new
to the framework and more convenient in scenarios where you need to
quickly create demos (e.g., using Tailwind Play).

## Light and dark mode support

These styles support both light and dark mode, achieved using the
`light-dark()` CSS function. While browser support for this function is
still somewhat limited, Lightning CSS transpiles it to a CSS
variable-based approach that works in older browsers.

For this approach to function correctly, a default `color-scheme` must
be set in your CSS (as explained in [the Lightning CSS
documentation](https://lightningcss.dev/transpilation.html#light-dark()-color-function)).
This PR addresses this requirement by setting the `color-scheme` to
`light` on the `html` element in Preflight.

<img width="1712" alt="image"
src="https://github.com/user-attachments/assets/dba56368-1427-47b3-9419-7c2f6313a944">

<img width="1709" alt="image"
src="https://github.com/user-attachments/assets/3d84fcd2-9606-4626-8e03-164a1dce9018">

## Breaking changes

While we don’t expect these changes to significantly impact v3 users
upgrading to v4, there may be minor differences for those relying on the
simpler v3 styles.

For example, Preflight now applies a `border-radius` to buttons and form
controls. If you weren’t explicitly setting the border radius to `0` in
your project, you’ll need to do so to restore the previous look.

Thankfully, reverting to the v3 styles is straightforward—just add the
following reset to your CSS:

```css
@layer base {
  input,
  textarea,
  select,
  button {
    border: 0px solid;
    border-radius: 0;
    padding: 0;
    background-color: transparent;
  }
}
```

It’s worth noting that this reset doesn't touch the
`::file-selector-button` styles that were added in this PR. This is
because it's not possible to reliably "undo" these styles and restore
the original user-agent styles (which is what was used in v3), as these
are different in each browser. However, these new styles actually match
the defaults in most browsers pretty closely, so hopefully this just
won't be an issue.

## Codemod

This PR includes a codemod that automatically inserts the above
mentioned v3 reset to help avoid breaking changes during the upgrade.
The codemod will insert the following 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;
  }
}
```

## Testing

These changes have been tested across a wide range of browsers,
including Chrome, Safari, Firefox, Edge, and Opera on macOS and Windows,
as well as Safari, Chrome, Firefox, and several lesser-known browsers on
iOS and Android.

However, some quirks still exist in certain mobile browsers, such as iOS
Safari, which adds too much bottom padding below date and time inputs:

<img width="1548" alt="Screenshot 2024-11-20 at 3 57 20 PM"
src="https://github.com/user-attachments/assets/507c7724-ac41-4634-a2b3-61ac4917ebce">

The only reliable way to address these issues is by applying
`appearance: none` to these form controls. However, this felt too
opinionated for Preflight, so we’ve opted to leave such adjustments to
user-land implementations.

---------

Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This commit is contained in:
Philipp Spiess 2024-11-21 11:21:31 +01:00 • committed by GitHub
parent ae249c7e56
commit fcf948f8c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1276 additions and 139 deletions

View file

@ -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

View file

@ -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 ---
<div>
@ -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 ---
<div class="rounded-sm blur-sm shadow-sm"></div>
@ -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';

View file

@ -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;
}
}
"
`)
},

View file

@ -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,50 +464,55 @@ exports[`\`@import 'tailwindcss'\` is replaced with the generated CSS 1`] = `
border-collapse: collapse;
}
button, input, optgroup, select, textarea {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
}
::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
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 {
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;
}
::-webkit-outer-spin-button {
height: auto;
button, input, select, optgroup, textarea {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
}
::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
}
::placeholder {
opacity: 1;
color: color-mix(in oklch, currentColor 50%, transparent);
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
@ -558,33 +568,109 @@ 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 {
opacity: 1;
color: color-mix(in oklch, currentColor 50%, transparent);
}
img, svg, video, canvas, audio, iframe, embed, object {
vertical-align: middle;
display: block;
}
img, video {
max-width: 100%;
::-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: 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);
}
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;
}
::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"])) {
display: none !important;
}

View file

@ -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 {
}"
`)
})

View file

@ -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,
}
}

View file

@ -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 {

View file

@ -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 })
}

View file

@ -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 */
}
/*
@ -174,35 +176,6 @@ table {
border-collapse: collapse; /* 3 */
}
/*
Inherit font styles in all browsers.
*/
button,
input,
optgroup,
select,
textarea,
::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
}
/*
1. Remove the default background color of buttons by default.
2. Correct the inability to style the border radius in iOS Safari.
*/
button,
input:where([type='button'], [type='reset'], [type='submit']),
::file-selector-button {
background: transparent; /* 1 */
appearance: button; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
@ -211,14 +184,6 @@ input:where([type='button'], [type='reset'], [type='submit']),
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
@ -228,14 +193,86 @@ progress {
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
Add the correct display in Chrome and Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
summary {
display: list-item;
}
/*
Make lists unstyled by default.
*/
ol,
ul,
menu {
list-style: none;
}
/*
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.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
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,69 +320,139 @@ 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;
}
/*
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 */
}
/*
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.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
Apply consistent base form control styles across browsers.
*/
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%));
}
/*
Apply consistent base button styles across browsers.
*/
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%));
}
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%));
}
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;
}
/*
Make elements with the HTML hidden attribute stay hidden by default.
*/