├── .gitignore ├── tailwind.config.js ├── src ├── index.d.ts └── index.js ├── scripts ├── release-channel.js └── release-notes.js ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── 1.bug_report.yml └── workflows │ ├── release.yml │ ├── release-insiders.yml │ └── prepare-release.yml ├── LICENSE ├── package.json ├── README.md ├── CHANGELOG.md ├── kitchen-sink.html └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | yarn.lock 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./index.html', './kitchen-sink.html'], 3 | theme: { 4 | extend: { 5 | // 6 | }, 7 | }, 8 | plugins: [require('./src')], 9 | } 10 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare function plugin(options?: Partial<{ strategy: 'base' | 'class' }>): { 2 | handler: () => void 3 | } 4 | 5 | declare namespace plugin { 6 | const __isOptionsFunction: true 7 | } 8 | 9 | export = plugin 10 | -------------------------------------------------------------------------------- /scripts/release-channel.js: -------------------------------------------------------------------------------- 1 | // Given a version, figure out what the release channel is so that we can publish to the correct 2 | // channel on npm. 3 | // 4 | // E.g.: 5 | // 6 | // 1.2.3 -> latest (default) 7 | // 0.0.0-insiders.ffaa88 -> insiders 8 | // 4.1.0-alpha.4 -> alpha 9 | 10 | let version = 11 | process.argv[2] || process.env.npm_package_version || require('../package.json').version 12 | 13 | let match = /\d+\.\d+\.\d+-(.*)\.\d+/g.exec(version) 14 | if (match) { 15 | console.log(match[1]) 16 | } else { 17 | console.log('latest') 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature Request 4 | url: https://github.com/tailwindlabs/tailwindcss/discussions/new?category=ideas 5 | about: 'Suggest any ideas you have using our discussion forums.' 6 | - name: Help 7 | url: https://github.com/tailwindlabs/tailwindcss/discussions/new?category=help 8 | about: 'If you have a question or need help, ask a question on the discussion forums.' 9 | - name: Kind Words 10 | url: https://github.com/tailwindlabs/tailwindcss/discussions/new?category=kind-words 11 | about: "Have something nice to say about @tailwindcss/forms or Tailwind CSS in general? We'd love to hear it!" 12 | -------------------------------------------------------------------------------- /scripts/release-notes.js: -------------------------------------------------------------------------------- 1 | // Given a version, figure out what the release notes are so that we can use this to pre-fill the 2 | // relase notes on a GitHub release for the current version. 3 | 4 | let path = require('path') 5 | let fs = require('fs') 6 | 7 | let version = 8 | process.argv[2] || process.env.npm_package_version || require('../package.json').version 9 | 10 | let changelog = fs.readFileSync(path.resolve(__dirname, '..', 'CHANGELOG.md'), 'utf8') 11 | let match = new RegExp( 12 | `## \\[${version}\\] - (.*)\\n\\n([\\s\\S]*?)\\n(?:(?:##\\s)|(?:\\[))`, 13 | 'g' 14 | ).exec(changelog) 15 | 16 | if (match) { 17 | let [, date, notes] = match 18 | console.log(notes.trim()) 19 | } else { 20 | console.log(`Placeholder release notes for version: v${version}`) 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | id-token: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [22] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | registry-url: 'https://registry.npmjs.org' 27 | cache: 'npm' 28 | 29 | - name: Install dependencies 30 | run: npm install 31 | 32 | - name: Test 33 | run: npm test 34 | 35 | - name: Calculate environment variables 36 | run: | 37 | echo "RELEASE_CHANNEL=$(npm run release-channel --silent)" >> $GITHUB_ENV 38 | 39 | - name: Publish 40 | run: npm publish --provenance --tag ${{ env.RELEASE_CHANNEL }} 41 | env: 42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Tailwind Labs, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/release-insiders.yml: -------------------------------------------------------------------------------- 1 | name: Release Insiders 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | permissions: 8 | contents: read 9 | id-token: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [22] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | registry-url: 'https://registry.npmjs.org' 27 | cache: 'npm' 28 | 29 | - name: Install dependencies 30 | run: npm install 31 | 32 | - name: Resolve version 33 | id: vars 34 | run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" 35 | 36 | - name: 'Version based on commit: 0.0.0-insiders.${{ steps.vars.outputs.sha_short }}' 37 | run: npm version 0.0.0-insiders.${{ steps.vars.outputs.sha_short }} --force --no-git-tag-version 38 | 39 | - name: Publish 40 | run: npm publish --provenance --tag insiders 41 | env: 42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tailwindcss/forms", 3 | "version": "0.5.11", 4 | "main": "src/index.js", 5 | "types": "src/index.d.ts", 6 | "license": "MIT", 7 | "repository": "https://github.com/tailwindlabs/tailwindcss-forms", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "prettier": { 12 | "printWidth": 100, 13 | "semi": false, 14 | "singleQuote": true, 15 | "trailingComma": "es5", 16 | "plugins": [ 17 | "prettier-plugin-tailwindcss" 18 | ] 19 | }, 20 | "scripts": { 21 | "dev": "concurrently \"npm run serve\" \"npm run watch\"", 22 | "serve": "live-server .", 23 | "watch": "npm run build -- -w", 24 | "build": "tailwindcss -o dist/tailwind.css", 25 | "test": "exit 0", 26 | "release-channel": "node ./scripts/release-channel.js", 27 | "release-notes": "node ./scripts/release-notes.js", 28 | "format": "prettier . --write" 29 | }, 30 | "peerDependencies": { 31 | "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" 32 | }, 33 | "devDependencies": { 34 | "autoprefixer": "^10.4.6", 35 | "concurrently": "^5.3.0", 36 | "live-server": "^1.2.2", 37 | "postcss": "^8.4.13", 38 | "prettier": "^3.3.3", 39 | "prettier-plugin-tailwindcss": "^0.6.8", 40 | "tailwindcss": "^3.0.24" 41 | }, 42 | "dependencies": { 43 | "mini-svg-data-uri": "^1.2.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1.bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a bug report for @tailwindcss/forms. 3 | labels: [] 4 | body: 5 | - type: input 6 | attributes: 7 | label: What version of @tailwindcss/forms are you using? 8 | description: 'For example: v0.1.4' 9 | validations: 10 | required: true 11 | - type: input 12 | attributes: 13 | label: What version of Node.js are you using? 14 | description: 'For example: v12.0.0' 15 | validations: 16 | required: true 17 | - type: input 18 | attributes: 19 | label: What browser are you using? 20 | description: 'For example: Chrome, Safari, or N/A' 21 | validations: 22 | required: true 23 | - type: input 24 | attributes: 25 | label: What operating system are you using? 26 | description: 'For example: macOS, Windows' 27 | validations: 28 | required: true 29 | - type: input 30 | attributes: 31 | label: Reproduction repository 32 | description: A public GitHub repo that includes a minimal reproduction of the bug. Unfortunately we can't provide support without a reproduction, and your issue will be closed and locked with no comment if this is not provided. 33 | validations: 34 | required: true 35 | - type: textarea 36 | attributes: 37 | label: Describe your issue 38 | description: Describe the problem you're seeing, any important steps to reproduce and what behavior you expect instead 39 | -------------------------------------------------------------------------------- /.github/workflows/prepare-release.yml: -------------------------------------------------------------------------------- 1 | name: Prepare Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | prepare: 14 | permissions: 15 | contents: write # for softprops/action-gh-release to create GitHub release 16 | 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | node-version: [22] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | registry-url: 'https://registry.npmjs.org' 31 | cache: 'npm' 32 | 33 | - name: Install dependencies 34 | run: npm install 35 | 36 | - name: Test 37 | run: npm test 38 | 39 | - name: Resolve version 40 | id: vars 41 | run: | 42 | echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV 43 | 44 | - name: Get release notes 45 | run: | 46 | RELEASE_NOTES=$(npm run release-notes --silent) 47 | echo "RELEASE_NOTES<> $GITHUB_ENV 48 | echo "$RELEASE_NOTES" >> $GITHUB_ENV 49 | echo "EOF" >> $GITHUB_ENV 50 | 51 | - name: Release 52 | uses: softprops/action-gh-release@v1 53 | with: 54 | draft: true 55 | tag_name: ${{ env.TAG_NAME }} 56 | body: ${{ env.RELEASE_NOTES }} 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @tailwindcss/forms 2 | 3 | A plugin that provides a basic reset for form styles that makes form elements easy to override with utilities. 4 | 5 | ## Installation 6 | 7 | Install the plugin from npm: 8 | 9 | ```sh 10 | npm install -D @tailwindcss/forms 11 | ``` 12 | 13 | Then, when using Tailwind CSS v4, add the plugin to your main stylesheet: 14 | 15 | ```css 16 | /* app.css */ 17 | @import "tailwindcss"; 18 | @plugin "@tailwindcss/forms"; 19 | ``` 20 | 21 | If you are still using **Tailwind CSS v3**, add the plugin to your `tailwind.config.js` file: 22 | 23 | ```js 24 | // tailwind.config.js 25 | module.exports = { 26 | theme: { 27 | // ... 28 | }, 29 | plugins: [ 30 | require('@tailwindcss/forms'), 31 | // ... 32 | ], 33 | } 34 | ``` 35 | 36 | ## Basic usage 37 | 38 | [**View the live demo**](https://tailwindcss-forms.vercel.app/) 39 | 40 | All of the basic form elements you use will now have some simple default styles that are easy to override with utilities. 41 | 42 | Currently we add basic utility-friendly form styles for the following form element types: 43 | 44 | - `input[type='text']` 45 | - `input[type='password']` 46 | - `input[type='email']` 47 | - `input[type='number']` 48 | - `input[type='url']` 49 | - `input[type='date']` 50 | - `input[type='datetime-local']` 51 | - `input[type='month']` 52 | - `input[type='week']` 53 | - `input[type='time']` 54 | - `input[type='search']` 55 | - `input[type='tel']` 56 | - `input[type='checkbox']` 57 | - `input[type='radio']` 58 | - `select` 59 | - `select[multiple]` 60 | - `textarea` 61 | 62 | 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: 63 | 64 | ```html 65 | 66 | 69 | 70 | 71 | 72 | ``` 73 | 74 | More customization examples and best practices coming soon. 75 | 76 | ### Using classes to style 77 | 78 | In addition to the global styles, we also generate a set of corresponding classes which can be used to explicitly apply the form styles to an element. This can be useful in situations where you need to make a non-form element, such as a `
`, look like a form element. 79 | 80 | ```html 81 | 82 | 83 | 86 | 87 | 88 | ``` 89 | 90 | Here is a complete table of the provided `form-*` classes for reference: 91 | 92 | | Base | Class | 93 | | ------------------------- | ------------------ | 94 | | `[type='text']` | `form-input` | 95 | | `[type='email']` | `form-input` | 96 | | `[type='url']` | `form-input` | 97 | | `[type='password']` | `form-input` | 98 | | `[type='number']` | `form-input` | 99 | | `[type='date']` | `form-input` | 100 | | `[type='datetime-local']` | `form-input` | 101 | | `[type='month']` | `form-input` | 102 | | `[type='search']` | `form-input` | 103 | | `[type='tel']` | `form-input` | 104 | | `[type='time']` | `form-input` | 105 | | `[type='week']` | `form-input` | 106 | | `textarea` | `form-textarea` | 107 | | `select` | `form-select` | 108 | | `select[multiple]` | `form-multiselect` | 109 | | `[type='checkbox']` | `form-checkbox` | 110 | | `[type='radio']` | `form-radio` | 111 | 112 | ### Using only global styles or only classes 113 | 114 | Although we recommend thinking of this plugin as a "form reset" rather than a collection of form component styles, in some cases our default approach may be too heavy-handed, especially when integrating this plugin into existing projects. 115 | 116 | If generating both the global (base) styles and classes doesn't work well with your project, you can use the `strategy` option to limit the plugin to just one of these approaches. 117 | 118 | When using Tailwind CSS v4, configure the plugin in your main stylesheet: 119 | 120 | ```css 121 | /* app.css */ 122 | @plugin "@tailwindcss/forms" { 123 | strategy: "base"; /* only generate global styles; or */ 124 | strategy: "class"; /* only generate classes */ 125 | } 126 | ``` 127 | 128 | If you are still using **Tailwind CSS v3**, configure the plugin in your `tailwind.config.js` file: 129 | 130 | ```js 131 | // tailwind.config.js 132 | plugins: [ 133 | require("@tailwindcss/forms")({ 134 | strategy: 'base', // only generate global styles 135 | strategy: 'class', // only generate classes 136 | }), 137 | ], 138 | ``` 139 | 140 | When using the `base` strategy, form elements are styled globally, and no `form-{name}` classes are generated. 141 | 142 | When using the `class` strategy, form elements are not styled globally, and instead must be styled using the generated `form-{name}` classes. 143 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | - Nothing yet! 11 | 12 | ## [0.5.11] - 2025-12-17 13 | 14 | ### Fixed 15 | 16 | - Limit attribute rules to input and select elements ([#159](https://github.com/tailwindlabs/tailwindcss-forms/pull/159)) 17 | 18 | ## [0.5.10] - 2025-01-07 19 | 20 | ### Fixed 21 | 22 | - Support installing with beta versions of Tailwind CSS v4 ([#163](https://github.com/tailwindlabs/tailwindcss-forms/pull/163)) 23 | 24 | ## [0.5.9] - 2024-09-05 25 | 26 | ### Fixed 27 | 28 | - Fallback to static chevron color if theme is using variables ([#167](https://github.com/tailwindlabs/tailwindcss-forms/pull/167)) 29 | 30 | ## [0.5.8] - 2024-08-28 31 | 32 | ### Fixed 33 | 34 | - Support installing with alpha versions of Tailwind CSS v4 ([#163](https://github.com/tailwindlabs/tailwindcss-forms/pull/163)) 35 | 36 | ## [0.5.7] - 2023-11-10 37 | 38 | ### Fixed 39 | 40 | - Use normal `checkbox` and `radio` appearance in `forced-colors` mode ([#152](https://github.com/tailwindlabs/tailwindcss-forms/pull/152)) 41 | 42 | ## [0.5.6] - 2023-08-28 43 | 44 | ### Fixed 45 | 46 | - Fix date time bottom spacing on MacOS Safari ([#146](https://github.com/tailwindlabs/tailwindcss-forms/pull/146)) 47 | 48 | ## [0.5.5] - 2023-08-22 49 | 50 | ### Fixed 51 | 52 | - Fix text alignment on date and time inputs on iOS ([#144](https://github.com/tailwindlabs/tailwindcss-forms/pull/144)) 53 | 54 | ## [0.5.4] - 2023-07-13 55 | 56 | ### Fixed 57 | 58 | - Remove chevron for selects with a non-default size ([#137](https://github.com/tailwindlabs/tailwindcss-forms/pull/137)) 59 | - Allow for without `type` ([#141](https://github.com/tailwindlabs/tailwindcss-forms/pull/141)) 60 | 61 | ## [0.5.3] - 2022-09-02 62 | 63 | ### Fixed 64 | 65 | - Update TypeScript types ([#126](https://github.com/tailwindlabs/tailwindcss-forms/pull/126)) 66 | 67 | ## [0.5.2] - 2022-05-18 68 | 69 | ### Added 70 | 71 | - Add TypeScript type declarations ([#118](https://github.com/tailwindlabs/tailwindcss-forms/pull/118)) 72 | 73 | ## [0.5.1] - 2022-05-03 74 | 75 | ### Fixed 76 | 77 | - Remove duplicate `outline` property ([#116](https://github.com/tailwindlabs/tailwindcss-forms/pull/116)) 78 | - Fix autoprefixer warning about `color-adjust` ([#115](https://github.com/tailwindlabs/tailwindcss-forms/pull/115)) 79 | 80 | ## [0.5.0] - 2022-03-02 81 | 82 | ### Changed 83 | 84 | - Generate both global styles and classes by default ([#71](https://github.com/tailwindlabs/tailwindcss-forms/pull/71)) 85 | 86 | ## [0.4.1] - 2022-03-02 87 | 88 | ### Added 89 | 90 | - Remove `dist` folder and related dependencies ([#96](https://github.com/tailwindlabs/tailwindcss-forms/pull/96)) 91 | 92 | ### Fixed 93 | 94 | - Use `addComponents` for class strategy ([#91](https://github.com/tailwindlabs/tailwindcss-forms/pull/91)) 95 | - Fix extra height on Safari date/time inputs ([#109](https://github.com/tailwindlabs/tailwindcss-forms/pull/109)) 96 | 97 | ## [0.4.0] - 2021-12-09 98 | 99 | ### Changed 100 | 101 | - Update color palette references for v3 ([#83](https://github.com/tailwindlabs/tailwindcss-forms/pull/83)) 102 | - Don't read outline.none value from config ([#89](https://github.com/tailwindlabs/tailwindcss-forms/pull/89)) 103 | 104 | ## [0.3.4] - 2021-09-28 105 | 106 | ### Fixed 107 | 108 | - Fix compatibility with `optimizeUniversalDefaults` experimental feature in Tailwind CSS v2.2 ([#81](https://github.com/tailwindlabs/tailwindcss-forms/pull/81)) 109 | 110 | ## [0.3.3] - 2021-06-03 111 | 112 | ### Fixed 113 | 114 | - Fix typo in selector when using `class` strategy that breaks background colors on checkboxes and radio buttons ([#72](https://github.com/tailwindlabs/tailwindcss-forms/pull/72)) 115 | 116 | ## [0.3.2] - 2021-03-26 117 | 118 | ### Fixed 119 | 120 | - Filter `null` rules for JIT compatibility ([b4c4e03](https://github.com/tailwindlabs/tailwindcss-forms/commit/b4c4e039337c3a5599f5b6d9eabbcc8ab9e8c8d9)) 121 | 122 | ## [0.3.1] - 2021-03-26 123 | 124 | ### Fixed 125 | 126 | - Use `base` as default strategy, not `class` ([#61](https://github.com/tailwindlabs/tailwindcss-forms/pull/61)) 127 | 128 | ## [0.3.0] - 2021-03-25 129 | 130 | ### Added 131 | 132 | - Add `class` strategy for you babies and your custom select and date picker libraries ;) ([#39](https://github.com/tailwindlabs/tailwindcss-forms/pull/39)) 133 | 134 | ## [0.2.1] - 2020-11-17 135 | 136 | ### Fixed 137 | 138 | - Fix issue where default checkbox/radio border color took precedence over user border color on focus ([d0b9fd9](https://github.com/tailwindlabs/tailwindcss-forms/commit/d0b9fd9)) 139 | 140 | ## [0.2.0] - 2020-11-16 141 | 142 | ### Changed 143 | 144 | - Update form styles to be less opinionated and encourage custom styling ([3288709](https://github.com/tailwindlabs/tailwindcss-forms/commit/3288709b59f4101511ec19f30cb2dafe7738251e)) 145 | - Update custom property names to match namespaced variables in Tailwind CSS v2.0 ([adb9807](https://github.com/tailwindlabs/tailwindcss-forms/commit/adb98078fc830d0416cb5ea2c895e997d5f0a5ec), [bbd8510](https://github.com/tailwindlabs/tailwindcss-forms/commit/bbd85102ef4a402b3c39d997c025208a33694cc4)) 146 | 147 | ## [0.1.4] - 2020-11-12 148 | 149 | ### Fixed 150 | 151 | - Fix SVG background images not rendering properly in all browsers ([#5](https://github.com/tailwindlabs/tailwindcss-forms/pull/5)) 152 | 153 | ## [0.1.3] - 2020-11-12 154 | 155 | ### Changed 156 | 157 | - Update focus styles to account for changes to `ring` API in latest Tailwind CSS 2.0 alpha ([5c16689](https://github.com/tailwindlabs/tailwindcss-forms/commit/5c166896b06475832bd8364f9f3ef5c4baec585f)) 158 | 159 | ## [0.1.2] - 2020-11-11 160 | 161 | ### Fixed 162 | 163 | - Work around iOS Safari bug that causes date inputs to render with no height when empty ([b98365b](https://github.com/tailwindlabs/tailwindcss-forms/commit/b98365b903b586bfbe7a6ae745ba64b5d87e23e3)) 164 | 165 | ## [0.1.1] - 2020-11-11 166 | 167 | ### Changed 168 | 169 | - Move `tailwindcss` to dependencies, hoping to get it working with Tailwind Play ([d625ea1](https://github.com/tailwindlabs/tailwindcss-forms/commit/d625ea11bd111a4d8cde937e36f3d229ecdf7c6a)) 170 | 171 | ## [0.1.0] - 2020-11-11 172 | 173 | Initial release! 174 | 175 | [unreleased]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.11...HEAD 176 | [0.5.11]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.10...v0.5.11 177 | [0.5.10]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.9...v0.5.10 178 | [0.5.9]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.8...v0.5.9 179 | [0.5.8]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.7...v0.5.8 180 | [0.5.7]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.6...v0.5.7 181 | [0.5.6]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.5...v0.5.6 182 | [0.5.5]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.4...v0.5.5 183 | [0.5.4]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.3...v0.5.4 184 | [0.5.3]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.2...v0.5.3 185 | [0.5.2]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.1...v0.5.2 186 | [0.5.1]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.5.0...v0.5.1 187 | [0.5.0]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.4.1...v0.5.0 188 | [0.4.1]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.4.0...v0.4.1 189 | [0.4.0]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.3.3...v0.4.0 190 | [0.3.4]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.3.3...v0.3.4 191 | [0.3.3]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.3.2...v0.3.3 192 | [0.3.2]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.3.1...v0.3.2 193 | [0.3.1]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.3.0...v0.3.1 194 | [0.3.0]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.2.1...v0.3.0 195 | [0.2.1]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.2.0...v0.2.1 196 | [0.2.0]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.1.4...v0.2.0 197 | [0.1.4]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.1.3...v0.1.4 198 | [0.1.3]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.1.2...v0.1.3 199 | [0.1.2]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.1.1...v0.1.2 200 | [0.1.1]: https://github.com/tailwindlabs/tailwindcss-forms/compare/v0.1.0...v0.1.1 201 | [0.1.0]: https://github.com/tailwindlabs/tailwindcss-forms/releases/tag/v0.1.0 202 | -------------------------------------------------------------------------------- /kitchen-sink.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @tailwindcss/forms Kitchen Sink 7 | 8 | 9 | 10 |
11 |
12 |

Reset styles

13 |

14 | These are form elements this plugin styles by default. 15 |

16 |
17 |
18 | 26 | 34 | 43 | 51 | 55 | 59 | 63 | 67 | 71 | 75 | 79 |
80 |
81 | 90 | 99 | 106 | 116 | 126 | 136 | 144 |
145 | Checkboxes 146 |
147 |
148 | 152 |
153 |
154 | 158 |
159 |
160 | 164 |
165 |
166 |
167 |
168 | Radio Buttons 169 |
170 |
171 | 175 |
176 |
177 | 181 |
182 |
183 | 187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |

Untouched

195 |

196 | These are form elements we don't handle (yet?), but we use this to make sure we haven't 197 | accidentally styled them by mistake. 198 |

199 |
200 |
201 | 205 | 209 | 213 | 217 |
218 |
219 |
220 |
221 | 222 | 223 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @tailwindcss/forms examples 7 | 8 | 9 | 10 |
11 |
12 |
13 |

@tailwindcss/forms examples

14 |

15 | An opinionated form reset designed to make form elements easy to style with utility 16 | classes. 17 |

18 |
19 | Documentation 22 | Kitchen Sink 23 |
24 |
25 |
26 |

Unstyled

27 |

This is how form elements look out of the box.

28 |
29 |
30 | 34 | 38 | 42 | 51 | 55 |
56 |
57 |
58 | 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Simple

70 |
71 |
72 | 80 | 88 | 95 | 106 | 113 |
114 |
115 |
116 | 124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |

Underline

132 |
133 |
134 | 142 | 150 | 157 | 168 | 175 |
176 |
177 |
178 | 185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |

Solid

193 |
194 |
195 | 203 | 211 | 218 | 229 | 236 |
237 |
238 |
239 | 246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | 255 | 256 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const svgToDataUri = require('mini-svg-data-uri') 2 | const plugin = require('tailwindcss/plugin') 3 | const defaultTheme = require('tailwindcss/defaultTheme') 4 | const colors = require('tailwindcss/colors') 5 | const [baseFontSize, { lineHeight: baseLineHeight }] = defaultTheme.fontSize.base 6 | const { spacing, borderWidth, borderRadius } = defaultTheme 7 | 8 | function resolveColor(color, opacityVariableName) { 9 | return color.replace('', `var(${opacityVariableName}, 1)`) 10 | } 11 | 12 | const forms = plugin.withOptions(function (options = { strategy: undefined }) { 13 | return function ({ addBase, addComponents, theme }) { 14 | function resolveChevronColor(color, fallback) { 15 | let resolved = theme(color) 16 | 17 | if (!resolved || resolved.includes('var(')) { 18 | return fallback 19 | } 20 | 21 | return resolved.replace('', '1') 22 | } 23 | 24 | const strategy = options.strategy === undefined ? ['base', 'class'] : [options.strategy] 25 | 26 | const rules = [ 27 | { 28 | base: [ 29 | "input:where([type='text'])", 30 | 'input:where(:not([type]))', 31 | "input:where([type='email'])", 32 | "input:where([type='url'])", 33 | "input:where([type='password'])", 34 | "input:where([type='number'])", 35 | "input:where([type='date'])", 36 | "input:where([type='datetime-local'])", 37 | "input:where([type='month'])", 38 | "input:where([type='search'])", 39 | "input:where([type='tel'])", 40 | "input:where([type='time'])", 41 | "input:where([type='week'])", 42 | 'select:where([multiple])', 43 | 'textarea', 44 | 'select', 45 | ], 46 | class: ['.form-input', '.form-textarea', '.form-select', '.form-multiselect'], 47 | styles: { 48 | appearance: 'none', 49 | 'background-color': '#fff', 50 | 'border-color': resolveColor( 51 | theme('colors.gray.500', colors.gray[500]), 52 | '--tw-border-opacity' 53 | ), 54 | 'border-width': borderWidth['DEFAULT'], 55 | 'border-radius': borderRadius.none, 56 | 'padding-top': spacing[2], 57 | 'padding-right': spacing[3], 58 | 'padding-bottom': spacing[2], 59 | 'padding-left': spacing[3], 60 | 'font-size': baseFontSize, 61 | 'line-height': baseLineHeight, 62 | '--tw-shadow': '0 0 #0000', 63 | '&:focus': { 64 | outline: '2px solid transparent', 65 | 'outline-offset': '2px', 66 | '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)', 67 | '--tw-ring-offset-width': '0px', 68 | '--tw-ring-offset-color': '#fff', 69 | '--tw-ring-color': resolveColor( 70 | theme('colors.blue.600', colors.blue[600]), 71 | '--tw-ring-opacity' 72 | ), 73 | '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, 74 | '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)`, 75 | 'box-shadow': `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)`, 76 | 'border-color': resolveColor( 77 | theme('colors.blue.600', colors.blue[600]), 78 | '--tw-border-opacity' 79 | ), 80 | }, 81 | }, 82 | }, 83 | { 84 | base: ['input::placeholder', 'textarea::placeholder'], 85 | class: ['.form-input::placeholder', '.form-textarea::placeholder'], 86 | styles: { 87 | color: resolveColor(theme('colors.gray.500', colors.gray[500]), '--tw-text-opacity'), 88 | opacity: '1', 89 | }, 90 | }, 91 | { 92 | base: ['::-webkit-datetime-edit-fields-wrapper'], 93 | class: ['.form-input::-webkit-datetime-edit-fields-wrapper'], 94 | styles: { 95 | padding: '0', 96 | }, 97 | }, 98 | { 99 | // Unfortunate hack until https://bugs.webkit.org/show_bug.cgi?id=198959 is fixed. 100 | // This sucks because users can't change line-height with a utility on date inputs now. 101 | // Reference: https://github.com/twbs/bootstrap/pull/31993 102 | base: ['::-webkit-date-and-time-value'], 103 | class: ['.form-input::-webkit-date-and-time-value'], 104 | styles: { 105 | 'min-height': '1.5em', 106 | }, 107 | }, 108 | { 109 | // In Safari on iOS date and time inputs are centered instead of left-aligned and can't be 110 | // changed with `text-align` utilities on the input by default. Resetting this to `inherit` 111 | // makes them left-aligned by default and makes it possible to override the alignment with 112 | // utility classes without using an arbitrary variant to target the pseudo-elements. 113 | base: ['::-webkit-date-and-time-value'], 114 | class: ['.form-input::-webkit-date-and-time-value'], 115 | styles: { 116 | 'text-align': 'inherit', 117 | }, 118 | }, 119 | { 120 | // In Safari on macOS date time inputs that are set to `display: block` have unexpected 121 | // extra bottom spacing. This can be corrected by setting the `::-webkit-datetime-edit` 122 | // pseudo-element to `display: inline-flex`, instead of the browser default of 123 | // `display: inline-block`. 124 | base: ['::-webkit-datetime-edit'], 125 | class: ['.form-input::-webkit-datetime-edit'], 126 | styles: { 127 | display: 'inline-flex', 128 | }, 129 | }, 130 | { 131 | // In Safari on macOS date time inputs are 4px taller than normal inputs 132 | // This is because there is extra padding on the datetime-edit and datetime-edit-{part}-field pseudo elements 133 | // See https://github.com/tailwindlabs/tailwindcss-forms/issues/95 134 | base: [ 135 | '::-webkit-datetime-edit', 136 | '::-webkit-datetime-edit-year-field', 137 | '::-webkit-datetime-edit-month-field', 138 | '::-webkit-datetime-edit-day-field', 139 | '::-webkit-datetime-edit-hour-field', 140 | '::-webkit-datetime-edit-minute-field', 141 | '::-webkit-datetime-edit-second-field', 142 | '::-webkit-datetime-edit-millisecond-field', 143 | '::-webkit-datetime-edit-meridiem-field', 144 | ], 145 | class: [ 146 | '.form-input::-webkit-datetime-edit', 147 | '.form-input::-webkit-datetime-edit-year-field', 148 | '.form-input::-webkit-datetime-edit-month-field', 149 | '.form-input::-webkit-datetime-edit-day-field', 150 | '.form-input::-webkit-datetime-edit-hour-field', 151 | '.form-input::-webkit-datetime-edit-minute-field', 152 | '.form-input::-webkit-datetime-edit-second-field', 153 | '.form-input::-webkit-datetime-edit-millisecond-field', 154 | '.form-input::-webkit-datetime-edit-meridiem-field', 155 | ], 156 | styles: { 157 | 'padding-top': 0, 158 | 'padding-bottom': 0, 159 | }, 160 | }, 161 | { 162 | base: ['select'], 163 | class: ['.form-select'], 164 | styles: { 165 | 'background-image': `url("${svgToDataUri( 166 | `` 170 | )}")`, 171 | 'background-position': `right ${spacing[2]} center`, 172 | 'background-repeat': `no-repeat`, 173 | 'background-size': `1.5em 1.5em`, 174 | 'padding-right': spacing[10], 175 | 'print-color-adjust': `exact`, 176 | }, 177 | }, 178 | { 179 | base: ['select:where([multiple])', 'select:where([size]:not([size="1"]))'], 180 | class: ['.form-select:where([size]:not([size="1"]))'], 181 | styles: { 182 | 'background-image': 'initial', 183 | 'background-position': 'initial', 184 | 'background-repeat': 'unset', 185 | 'background-size': 'initial', 186 | 'padding-right': spacing[3], 187 | 'print-color-adjust': 'unset', 188 | }, 189 | }, 190 | { 191 | base: [`input:where([type='checkbox'])`, `input:where([type='radio'])`], 192 | class: ['.form-checkbox', '.form-radio'], 193 | styles: { 194 | appearance: 'none', 195 | padding: '0', 196 | 'print-color-adjust': 'exact', 197 | display: 'inline-block', 198 | 'vertical-align': 'middle', 199 | 'background-origin': 'border-box', 200 | 'user-select': 'none', 201 | 'flex-shrink': '0', 202 | height: spacing[4], 203 | width: spacing[4], 204 | color: resolveColor(theme('colors.blue.600', colors.blue[600]), '--tw-text-opacity'), 205 | 'background-color': '#fff', 206 | 'border-color': resolveColor( 207 | theme('colors.gray.500', colors.gray[500]), 208 | '--tw-border-opacity' 209 | ), 210 | 'border-width': borderWidth['DEFAULT'], 211 | '--tw-shadow': '0 0 #0000', 212 | }, 213 | }, 214 | { 215 | base: [`input:where([type='checkbox'])`], 216 | class: ['.form-checkbox'], 217 | styles: { 218 | 'border-radius': borderRadius['none'], 219 | }, 220 | }, 221 | { 222 | base: [`input:where([type='radio'])`], 223 | class: ['.form-radio'], 224 | styles: { 225 | 'border-radius': '100%', 226 | }, 227 | }, 228 | { 229 | base: [`input:where([type='checkbox']):focus`, `input:where([type='radio']):focus`], 230 | class: ['.form-checkbox:focus', '.form-radio:focus'], 231 | styles: { 232 | outline: '2px solid transparent', 233 | 'outline-offset': '2px', 234 | '--tw-ring-inset': 'var(--tw-empty,/*!*/ /*!*/)', 235 | '--tw-ring-offset-width': '2px', 236 | '--tw-ring-offset-color': '#fff', 237 | '--tw-ring-color': resolveColor( 238 | theme('colors.blue.600', colors.blue[600]), 239 | '--tw-ring-opacity' 240 | ), 241 | '--tw-ring-offset-shadow': `var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)`, 242 | '--tw-ring-shadow': `var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)`, 243 | 'box-shadow': `var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)`, 244 | }, 245 | }, 246 | { 247 | base: [`input:where([type='checkbox']):checked`, `input:where([type='radio']):checked`], 248 | class: ['.form-checkbox:checked', '.form-radio:checked'], 249 | styles: { 250 | 'border-color': `transparent`, 251 | 'background-color': `currentColor`, 252 | 'background-size': `100% 100%`, 253 | 'background-position': `center`, 254 | 'background-repeat': `no-repeat`, 255 | }, 256 | }, 257 | { 258 | base: [`input:where([type='checkbox']):checked`], 259 | class: ['.form-checkbox:checked'], 260 | styles: { 261 | 'background-image': `url("${svgToDataUri( 262 | `` 263 | )}")`, 264 | 265 | '@media (forced-colors: active) ': { 266 | appearance: 'auto', 267 | }, 268 | }, 269 | }, 270 | { 271 | base: [`input:where([type='radio']):checked`], 272 | class: ['.form-radio:checked'], 273 | styles: { 274 | 'background-image': `url("${svgToDataUri( 275 | `` 276 | )}")`, 277 | 278 | '@media (forced-colors: active) ': { 279 | appearance: 'auto', 280 | }, 281 | }, 282 | }, 283 | { 284 | base: [ 285 | `input:where([type='checkbox']):checked:hover`, 286 | `input:where([type='checkbox']):checked:focus`, 287 | `input:where([type='radio']):checked:hover`, 288 | `input:where([type='radio']):checked:focus`, 289 | ], 290 | class: [ 291 | '.form-checkbox:checked:hover', 292 | '.form-checkbox:checked:focus', 293 | '.form-radio:checked:hover', 294 | '.form-radio:checked:focus', 295 | ], 296 | styles: { 297 | 'border-color': 'transparent', 298 | 'background-color': 'currentColor', 299 | }, 300 | }, 301 | { 302 | base: [`input:where([type='checkbox']):indeterminate`], 303 | class: ['.form-checkbox:indeterminate'], 304 | styles: { 305 | 'background-image': `url("${svgToDataUri( 306 | `` 307 | )}")`, 308 | 'border-color': `transparent`, 309 | 'background-color': `currentColor`, 310 | 'background-size': `100% 100%`, 311 | 'background-position': `center`, 312 | 'background-repeat': `no-repeat`, 313 | 314 | '@media (forced-colors: active) ': { 315 | appearance: 'auto', 316 | }, 317 | }, 318 | }, 319 | { 320 | base: [`input:where([type='checkbox']):indeterminate:hover`, `input:where([type='checkbox']):indeterminate:focus`], 321 | class: ['.form-checkbox:indeterminate:hover', '.form-checkbox:indeterminate:focus'], 322 | styles: { 323 | 'border-color': 'transparent', 324 | 'background-color': 'currentColor', 325 | }, 326 | }, 327 | { 328 | base: [`input:where([type='file'])`], 329 | class: null, 330 | styles: { 331 | background: 'unset', 332 | 'border-color': 'inherit', 333 | 'border-width': '0', 334 | 'border-radius': '0', 335 | padding: '0', 336 | 'font-size': 'unset', 337 | 'line-height': 'inherit', 338 | }, 339 | }, 340 | { 341 | base: [`input:where([type='file']):focus`], 342 | class: null, 343 | styles: { 344 | outline: [`1px solid ButtonText`, `1px auto -webkit-focus-ring-color`], 345 | }, 346 | }, 347 | ] 348 | 349 | const getStrategyRules = (strategy) => 350 | rules 351 | .map((rule) => { 352 | if (rule[strategy] === null) return null 353 | 354 | return { [rule[strategy]]: rule.styles } 355 | }) 356 | .filter(Boolean) 357 | 358 | if (strategy.includes('base')) { 359 | addBase(getStrategyRules('base')) 360 | } 361 | 362 | if (strategy.includes('class')) { 363 | addComponents(getStrategyRules('class')) 364 | } 365 | } 366 | }) 367 | 368 | module.exports = forms 369 | --------------------------------------------------------------------------------