diff --git a/README.md b/README.md index 74d3ec2..01b4465 100644 --- a/README.md +++ b/README.md @@ -37,27 +37,42 @@ module.exports = { All of the basic form elements you use will now have some simple default styles that are easy to override with utilities. -Currently we add basic utility-friendly form styles for the following form element types: - -- `input[type='text']` -- `input[type='password']` -- `input[type='email']` -- `input[type='number']` -- `input[type='url']` -- `input[type='date']` -- `input[type='datetime-local']` -- `input[type='month']` -- `input[type='week']` -- `input[type='time']` -- `input[type='search']` -- `input[type='tel']` -- `input[type='checkbox']` -- `input[type='radio']` -- `select` -- `select[multiple]` -- `textarea` - -**Note that for text inputs, you must add the `type="text"` attribute for these styles to take effect.** This is a necessary trade-off to avoid relying on the overly greedy `input` selector and unintentionally styling elements we don't have solutions for yet, like `input[type="range"]` for example. +There's currently two options you can choose in how we we add basic utility-friendly form styles: + +- `base` - The default selector strategy +- `class` - Requires a `form-` class to be applied to the form element in order for styles to be applied + +```js +// tailwind.config.js +plugins: [ + require("@tailwindcss/forms")({ + strategy: 'class', + }), +], +``` + +| Base | Class | +| ------------------------- | ------------------ | +| `[type='text']` | `form-input` | +| `[type='email']` | `form-input` | +| `[type='url']` | `form-input` | +| `[type='password']` | `form-input` | +| `[type='number']` | `form-input` | +| `[type='date']` | `form-input` | +| `[type='datetime-local']` | `form-input` | +| `[type='month']` | `form-input` | +| `[type='search']` | `form-input` | +| `[type='tel']` | `form-input` | +| `[type='time']` | `form-input` | +| `[type='week']` | `form-input` | +| `[multiple]` | `form-multiselect` | +| `textarea` | `form-textarea` | +| `select` | `form-select` | +| `[type='checkbox']` | `form-checkbox` | +| `[type='radio']` | `form-radio` | +| `[type='file']` | `form-file` | + +**Note that for text inputs, when using the default `base` strategy, you must add the `type="text"` attribute for these styles to take effect.** This is a necessary trade-off to avoid relying on the overly greedy `input` selector and unintentionally styling elements we don't have solutions for yet, like `input[type="range"]` for example. Every element has been normalized/reset to a simple visually consistent style that is easy to customize with utilities, even elements like `` that normally need to be reset with `appearance: none` and customized using custom CSS: diff --git a/src/index.js b/src/index.js index 5adac22..6b694b2 100644 --- a/src/index.js +++ b/src/index.js @@ -4,199 +4,257 @@ const defaultTheme = require('tailwindcss/defaultTheme') const [baseFontSize, { lineHeight: baseLineHeight }] = defaultTheme.fontSize.base const { colors, spacing, borderWidth, borderRadius, outline } = defaultTheme -const forms = plugin(function ({ addBase, theme }) { - addBase({ - [` - [type='text'], - [type='email'], - [type='url'], - [type='password'], - [type='number'], - [type='date'], - [type='datetime-local'], - [type='month'], - [type='search'], - [type='tel'], - [type='time'], - [type='week'], - [multiple], - textarea, - select - `]: { - appearance: 'none', - 'background-color': '#fff', - 'border-color': theme('colors.gray.500', colors.gray[500]), - 'border-width': borderWidth['DEFAULT'], - 'border-radius': borderRadius.none, - 'padding-top': spacing[2], - 'padding-right': spacing[3], - 'padding-bottom': spacing[2], - 'padding-left': spacing[3], - 'font-size': baseFontSize, - 'line-height': baseLineHeight, - '&:focus': { - outline: outline.none[0], - 'outline-offset': outline.none[1], - '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)', - '--tw-ring-offset-width': '0px', - '--tw-ring-offset-color': '#fff', - '--tw-ring-color': theme('colors.blue.600', colors.blue[600]), - '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, - '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)`, - 'box-shadow': `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)`, - 'border-color': theme('colors.blue.600', colors.blue[600]), - }, - }, +const forms = plugin.withOptions(function (options) { + return function ({ addBase, theme }) { + const strategy = options && options.strategy === 'class' ? 'class' : 'base' - 'input::placeholder, textarea::placeholder': { - color: theme('colors.gray.500', colors.gray[500]), - opacity: '1', - }, - - '::-webkit-datetime-edit-fields-wrapper': { - padding: '0', - }, - - // Unfortunate hack until https://bugs.webkit.org/show_bug.cgi?id=198959 is fixed. - // This sucks because users can't change line-height with a utility on date inputs now. - // Reference: https://github.com/twbs/bootstrap/pull/31993 - '::-webkit-date-and-time-value': { - 'min-height': '1.5em', - }, - - select: { - 'background-image': `url("${svgToDataUri( - `` - )}")`, - 'background-position': `right ${spacing[2]} center`, - 'background-repeat': `no-repeat`, - 'background-size': `1.5em 1.5em`, - 'padding-right': spacing[10], - 'color-adjust': `exact`, - }, - - '[multiple]': { - 'background-image': 'initial', - 'background-position': 'initial', - 'background-repeat': 'unset', - 'background-size': 'initial', - 'padding-right': spacing[3], - 'color-adjust': 'unset', - }, - - [` - [type='checkbox'], - [type='radio'] - `]: { - appearance: 'none', - padding: '0', - 'color-adjust': 'exact', - display: 'inline-block', - 'vertical-align': 'middle', - 'background-origin': 'border-box', - 'user-select': 'none', - 'flex-shrink': '0', - height: spacing[4], - width: spacing[4], - color: theme('colors.blue.600', colors.blue[600]), - 'background-color': '#fff', - 'border-color': theme('colors.gray.500', colors.gray[500]), - 'border-width': borderWidth['DEFAULT'], - }, - - [`[type='checkbox']`]: { - 'border-radius': borderRadius['none'], - }, - - [`[type='radio']`]: { - 'border-radius': '100%', - }, - - [` - [type='checkbox']:focus, - [type='radio']:focus - `]: { - outline: outline.none[0], - 'outline-offset': outline.none[1], - '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)', - '--tw-ring-offset-width': '2px', - '--tw-ring-offset-color': '#fff', - '--tw-ring-color': theme('colors.blue.600', colors.blue[600]), - '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, - '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)`, - 'box-shadow': `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)`, - }, - - [` - [type='checkbox']:checked, - [type='radio']:checked - `]: { - 'border-color': `transparent`, - 'background-color': `currentColor`, - 'background-size': `100% 100%`, - 'background-position': `center`, - 'background-repeat': `no-repeat`, - }, - - [`[type='checkbox']:checked`]: { - 'background-image': `url("${svgToDataUri( - `` - )}")`, - }, - - [`[type='radio']:checked`]: { - 'background-image': `url("${svgToDataUri( - `` - )}")`, - }, - - [` - [type='checkbox']:checked:hover, - [type='checkbox']:checked:focus, - [type='radio']:checked:hover, - [type='radio']:checked:focus - `]: { - 'border-color': 'transparent', - 'background-color': 'currentColor', - }, - - [`[type='checkbox']:indeterminate`]: { - 'background-image': `url("${svgToDataUri( - `` - )}")`, - 'border-color': `transparent`, - 'background-color': `currentColor`, - 'background-size': `100% 100%`, - 'background-position': `center`, - 'background-repeat': `no-repeat`, - }, - - [` - [type='checkbox']:indeterminate:hover, - [type='checkbox']:indeterminate:focus - `]: { - 'border-color': 'transparent', - 'background-color': 'currentColor', - }, - - [`[type='file']`]: { - background: 'unset', - 'border-color': 'inherit', - 'border-width': '0', - 'border-radius': '0', - padding: '0', - 'font-size': 'unset', - 'line-height': 'inherit', - }, + const rules = [ + { + base: [ + "[type='text']", + "[type='email']", + "[type='url']", + "[type='password']", + "[type='number']", + "[type='date']", + "[type='datetime-local']", + "[type='month']", + "[type='search']", + "[type='tel']", + "[type='time']", + "[type='week']", + '[multiple]', + 'textarea', + 'select', + ], + class: ['.form-input', '.form-textarea', '.form-select', '.form-multiselect'], + styles: { + appearance: 'none', + 'background-color': '#fff', + 'border-color': theme('colors.gray.500', colors.gray[500]), + 'border-width': borderWidth['DEFAULT'], + 'border-radius': borderRadius.none, + 'padding-top': spacing[2], + 'padding-right': spacing[3], + 'padding-bottom': spacing[2], + 'padding-left': spacing[3], + 'font-size': baseFontSize, + 'line-height': baseLineHeight, + '&:focus': { + outline: outline.none[0], + 'outline-offset': outline.none[1], + '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)', + '--tw-ring-offset-width': '0px', + '--tw-ring-offset-color': '#fff', + '--tw-ring-color': theme('colors.blue.600', colors.blue[600]), + '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, + '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)`, + 'box-shadow': `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)`, + 'border-color': theme('colors.blue.600', colors.blue[600]), + }, + }, + }, + { + base: ['input::placeholder', 'textarea::placeholder'], + class: ['.form-input::placeholder', '.form-textarea::placeholder'], + styles: { + color: theme('colors.gray.500', colors.gray[500]), + opacity: '1', + }, + }, + { + base: ['::-webkit-datetime-edit-fields-wrapper'], + class: ['.form-input::-webkit-datetime-edit-fields-wrapper'], + styles: { + padding: '0', + }, + }, + { + // Unfortunate hack until https://bugs.webkit.org/show_bug.cgi?id=198959 is fixed. + // This sucks because users can't change line-height with a utility on date inputs now. + // Reference: https://github.com/twbs/bootstrap/pull/31993 + base: ['::-webkit-date-and-time-value'], + class: ['.form-input::-webkit-date-and-time-value'], + styles: { + 'min-height': '1.5em', + }, + }, + { + base: ['select'], + class: ['.form-select'], + styles: { + 'background-image': `url("${svgToDataUri( + `` + )}")`, + 'background-position': `right ${spacing[2]} center`, + 'background-repeat': `no-repeat`, + 'background-size': `1.5em 1.5em`, + 'padding-right': spacing[10], + 'color-adjust': `exact`, + }, + }, + { + base: ['[multiple]'], + class: ['.form-multiselect'], + styles: { + 'background-image': 'initial', + 'background-position': 'initial', + 'background-repeat': 'unset', + 'background-size': 'initial', + 'padding-right': spacing[3], + 'color-adjust': 'unset', + }, + }, + { + base: [`[type='checkbox']`, `[type='radio']`], + class: ['.form-checkbox', '.form-radio'], + styles: { + appearance: 'none', + padding: '0', + 'color-adjust': 'exact', + display: 'inline-block', + 'vertical-align': 'middle', + 'background-origin': 'border-box', + 'user-select': 'none', + 'flex-shrink': '0', + height: spacing[4], + width: spacing[4], + color: theme('colors.blue.600', colors.blue[600]), + 'background-color': '#fff', + 'border-color': theme('colors.gray.500', colors.gray[500]), + 'border-width': borderWidth['DEFAULT'], + }, + }, + { + base: [`[type='checkbox']`], + class: ['.form-checkbox'], + styles: { + 'border-radius': borderRadius['none'], + }, + }, + { + base: [`[type='radio']`], + class: ['.form-radio'], + styles: { + 'border-radius': '100%', + }, + }, + { + base: [`[type='checkbox']:focus`, `[type='radio']:focus`], + class: ['.form-checkbox:focus', '.form-radio:focus'], + styles: { + outline: outline.none[0], + 'outline-offset': outline.none[1], + '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)', + '--tw-ring-offset-width': '2px', + '--tw-ring-offset-color': '#fff', + '--tw-ring-color': theme('colors.blue.600', colors.blue[600]), + '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, + '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)`, + 'box-shadow': `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)`, + }, + }, + { + base: [`[type='checkbox']:checked`, `[type='radio']:checked`], + class: ['.form-checkbox:checked', '.form-radio:checked'], + styles: { + 'border-color': `transparent`, + 'background-color': `currentColor`, + 'background-size': `100% 100%`, + 'background-position': `center`, + 'background-repeat': `no-repeat`, + }, + }, + { + base: [`[type='checkbox']:checked`], + class: ['.form-checkbox:checked'], + styles: { + 'background-image': `url("${svgToDataUri( + `` + )}")`, + }, + }, + { + base: [`[type='radio']:checked`], + class: ['.form-radio:checked'], + styles: { + 'background-image': `url("${svgToDataUri( + `` + )}")`, + }, + }, + { + base: [ + `[type='checkbox']:checked:hover`, + `[type='checkbox']:checked:focus`, + `[type='radio']:checked:hover`, + `[type='radio']:checked:focus`, + ], + class: [ + '.form-checkbox:checked:hover', + '.form-checkbox:checked:focus', + '.form-radio:checked:hover', + '.form-radio:check:focus', + ], + styles: { + 'border-color': 'transparent', + 'background-color': 'currentColor', + }, + }, + { + base: [`[type='checkbox']:indeterminate`], + class: ['.form-checkbox:indeterminate'], + styles: { + 'background-image': `url("${svgToDataUri( + `` + )}")`, + 'border-color': `transparent`, + 'background-color': `currentColor`, + 'background-size': `100% 100%`, + 'background-position': `center`, + 'background-repeat': `no-repeat`, + }, + }, + { + base: [`[type='checkbox']:indeterminate:hover`, `[type='checkbox']:indeterminate:focus`], + class: ['.form-checkbox:indeterminate:hover', '.form-checkbox:indeterminate:focus'], + styles: { + 'border-color': 'transparent', + 'background-color': 'currentColor', + }, + }, + { + base: [`[type='file']`], + class: ['.form-file'], + styles: { + background: 'unset', + 'border-color': 'inherit', + 'border-width': '0', + 'border-radius': '0', + padding: '0', + 'font-size': 'unset', + 'line-height': 'inherit', + }, + }, + { + base: [`[type='file']:focus`], + class: ['.form-file:focus'], + styles: { + outline: `1px solid ButtonText`, + outline: `1px auto -webkit-focus-ring-color`, + }, + }, + ] - [`[type='file']:focus`]: { - outline: `1px solid ButtonText`, - outline: `1px auto -webkit-focus-ring-color`, - }, - }) + for (const rule of rules) { + addBase({ + [rule[strategy]]: rule.styles, + }) + } + } }) module.exports = forms