├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── eslint.config.mjs ├── jest ├── content │ ├── composition.html │ ├── delay.html │ ├── direction.html │ ├── duration.html │ ├── fill-mode.html │ ├── iteration-count.html │ ├── play-state.html │ ├── predefined-animations.html │ └── timing-function.html ├── customMatchers.js ├── index.test.js └── legacy │ ├── index.test.js │ └── package.json ├── package.json ├── public └── index.html ├── resources └── app.css └── src ├── compat ├── composition.js ├── delay.js ├── direction.js ├── duration.js ├── fill-mode.js ├── helper │ └── bare-values.js ├── iteration-count.js ├── play-state.js ├── theme.js └── timing-function.js ├── index.css ├── index.d.ts ├── index.js └── utilities ├── composition.css ├── delay.css ├── direction.css ├── duration.css ├── fill-mode.css ├── iteration-count.css ├── play-state.css ├── predefined-animations.css └── timing-function.css /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: '20.x' 15 | - run: npm install 16 | - run: npm install --prefix jest/legacy 17 | - run: npm test 18 | 19 | publish: 20 | needs: test 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: '20.x' 27 | registry-url: 'https://registry.npmjs.org' 28 | - run: npm version --no-git-tag-version ${{ github.event.release.tag_name }} 29 | - run: npm publish 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [20.x, 22.x] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - run: npm install 18 | - run: npm install --prefix jest/legacy 19 | - run: npm run lint 20 | - run: npm run test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | .npm 6 | 7 | # logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # misc 15 | /public/tailwind.css 16 | /public/app.css 17 | /.build 18 | /.idea 19 | /.nvmrc 20 | /.vscode 21 | *.local 22 | .DS_Store 23 | .env 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 new-data-services GmbH 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tailwind CSS Animated 2 | 3 | Extended animation utilities for Tailwind CSS
4 | https://tailwindcss-animated.com 5 | 6 | ## Installation 7 | 8 | First, install the plugin via npm: 9 | 10 | ```sh 11 | npm install tailwindcss-animated 12 | ``` 13 | 14 | ## Import 15 | 16 | Second, import it alongside Tailwind CSS in your CSS file: 17 | 18 | ```css 19 | /* tailwind css v4.x */ 20 | @import "tailwindcss"; 21 | @import "tailwindcss-animated"; 22 | ``` 23 | 24 | Or, if you are using **Tailwind CSS v3.x** or the legacy JavaScript configuration file, import the plugin like this: 25 | 26 | ```js 27 | // tailwind.config.js 28 | module.exports = { 29 | // ... 30 | plugins: [ 31 | require('tailwindcss-animated') 32 | ], 33 | } 34 | ``` 35 | 36 | ## Usage 37 | 38 | This plugin brings various utility classes as well as several ready-to-use CSS animations. Here are some simple examples: 39 | 40 | ```html 41 | 44 | 45 | 48 | ``` 49 | 50 | ### Ready-to-use animations 51 | 52 | There are several animations that can be integrated with a single utility class. These extend the Spin, Ping and Pulse animations of Tailwind CSS. 53 | 54 | Open the configurator to see them in action:
55 | https://tailwindcss-animated.com/configurator.html 56 | 57 | All animations can be customized with the utility classes below. 58 | 59 | ### Duration 60 | 61 | | Class | Properties | 62 | |-----|-----| 63 | | animate-duration-75 | animation-duration: 75ms; | 64 | | animate-duration-100 | animation-duration: 100ms; | 65 | | animate-duration-150 | animation-duration: 150ms; | 66 | | animate-duration-200 | animation-duration: 200ms; | 67 | | animate-duration-300 | animation-duration: 300ms; | 68 | | animate-duration-500 | animation-duration: 500ms; | 69 | | animate-duration-700 | animation-duration: 700ms; | 70 | | animate-duration-1000 | animation-duration: 1000ms; | 71 | | animate-duration-[*\*] | animation-duration: *\* ms; | 72 | | animate-duration-*\* [*](#custom-properties-and-bare-values) | animation-duration: *\* ms; | 73 | | animate-duration-(*\*) [*](#custom-properties-and-bare-values) | animation-duration: var(*\*); | 74 | 75 | ### Delay 76 | 77 | | Class | Properties | 78 | |-----|-----| 79 | | animate-delay-none | animation-delay: 0ms; | 80 | | animate-delay-75 | animation-delay: 75ms; | 81 | | animate-delay-100 | animation-delay: 100ms; | 82 | | animate-delay-150 | animation-delay: 150ms; | 83 | | animate-delay-200 | animation-delay: 200ms; | 84 | | animate-delay-300 | animation-delay: 300ms; | 85 | | animate-delay-500 | animation-delay: 500ms; | 86 | | animate-delay-700 | animation-delay: 700ms; | 87 | | animate-delay-1000 | animation-delay: 1000ms; | 88 | | animate-delay-[*\*] | animation-delay: *\* ms; | 89 | | animate-delay-*\* [*](#custom-properties-and-bare-values) | animation-delay: *\* ms; | 90 | | animate-delay-(*\*) [*](#custom-properties-and-bare-values) | animation-delay: var(*\*); | 91 | 92 | ### Direction 93 | 94 | | Class | Properties | 95 | |-----|-----| 96 | | animate-normal | animation-direction: normal; | 97 | | animate-reverse | animation-direction: reverse; | 98 | | animate-alternate | animation-direction: alternate; | 99 | | animate-alternate-reverse | animation-direction: alternate-reverse; | 100 | 101 | ### Iteration Count 102 | 103 | | Class | Properties | 104 | |-----|-----| 105 | | animate-infinite | animation-iteration-count: infinite; | 106 | | animate-once | animation-iteration-count: 1; | 107 | | animate-twice | animation-iteration-count: 2; | 108 | | animate-thrice | animation-iteration-count: 3; | 109 | | animate-iteration-[*\*] | animation-iteration-count: *\*; | 110 | | animate-iteration-*\* [*](#custom-properties-and-bare-values) | animation-iteration-count: *\*; | 111 | | animate-iteration-(*\*) [*](#custom-properties-and-bare-values) | animation-iteration-count: var(*\*); | 112 | 113 | ### Timing Function 114 | 115 | | Class | Properties | 116 | |-----|-----| 117 | | animate-ease | animation-timing-function: ease; | 118 | | animate-ease-linear | animation-timing-function: linear; | 119 | | animate-ease-in | animation-timing-function: cubic-bezier(0.4, 0, 1, 1); | 120 | | animate-ease-out | animation-timing-function: cubic-bezier(0, 0, 0.2, 1); | 121 | | animate-ease-in-out | animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | 122 | | animate-ease-[*\*] | animation-timing-function: *\*; | 123 | | animate-ease-(*\*) [*](#custom-properties-and-bare-values) | animation-timing-function: var(*\*); | 124 | 125 | ### Fill Mode 126 | 127 | | Class | Properties | 128 | |-----|-----| 129 | | animate-fill-none | animation-fill-mode: normal; | 130 | | animate-fill-forwards | animation-fill-mode: forwards; | 131 | | animate-fill-backwards | animation-fill-mode: backwards; | 132 | | animate-fill-both | animation-fill-mode: both; | 133 | 134 | ### Play State 135 | 136 | | Class | Properties | 137 | |-----|-----| 138 | | animate-run | animation-play-state: running; | 139 | | animate-play | animation-play-state: running; | 140 | | animate-stop | animation-play-state: paused; | 141 | | animate-pause | animation-play-state: paused; | 142 | 143 | ### Composition 144 | 145 | | Class | Properties | 146 | |-----|-----| 147 | | animate-replace | animation-composition: replace; | 148 | | animate-add | animation-composition: add; | 149 | | animate-accumulate | animation-composition: accumulate; | 150 | 151 | ## Variant modifiers and breakpoints 152 | 153 | All variants and breakpoints (hover, focus, lg, ...) work with animations und animation utility classes. 154 | 155 | ```html 156 |
157 | 158 |
159 | ``` 160 | 161 | ## Arbitrary values 162 | 163 | Of course, you can use arbitrary values for animations ultilities: 164 | 165 | ```html 166 |
167 | 168 |
169 | ``` 170 | 171 | ## Custom properties and bare values 172 | 173 | With **Tailwind CSS v4.0** or newer you can use the shorthand syntax for custom properties. Bare values for delay, duration and iteration utilities also work. 174 | 175 | ```html 176 |
177 | 178 |
179 | ``` 180 | 181 | More information on the new arbitrary value syntax can be found in the [Tailwind CSS Docs](https://tailwindcss.com/docs/adding-custom-styles#using-arbitrary-values). 182 | 183 | ## Override default values 184 | 185 | All animations come with default values for duration, delay and timing function. If you want to overwrite these values globally, you can set the following CSS properties: 186 | 187 | ```css 188 | :root { 189 | --default-animation-duration: 500ms; 190 | --default-animation-delay: 0s; 191 | --default-animation-timing-function: ease; 192 | } 193 | ``` 194 | 195 | ## FAQ 196 | 197 | ### How to animate on scroll? 198 | 199 | To run animations when an element enters the viewport, you need JavaScript. (At least until [animation-timeline](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline) has good browser support) 200 | 201 | A good starting point for a JavaScript solution would be the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). Or tools that build on it, such as the [Alpine.js Intersect plugin](https://alpinejs.dev/plugins/intersect) and the [Tailwind CSS Intersection plugin](https://github.com/heidkaemper/tailwindcss-intersect), to name just two. 202 | 203 | ### How to combine multiple animations? 204 | 205 | The simplest approach is to nest two elements: 206 | 207 | ```html 208 |
209 |
210 |
211 | ``` 212 | 213 | ### Can keyframes and offset values be changed? 214 | 215 | Offset positions of predefined animations can't be changed on the fly. But the behavior can still be modified with [animation-composition](#composition) utilities. 216 | 217 | If you need more details on how compositions work, check out the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-composition). 218 | 219 | ### Does this work with the Play CDN? 220 | 221 | Unfortunately not. The Tailwind CSS Play CDN currently does not support third-party plugins. 222 | 223 | --- 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import eslintPluginJest from 'eslint-plugin-jest' 4 | 5 | export default [ 6 | js.configs.recommended, 7 | eslintPluginJest.configs['flat/recommended'], 8 | { 9 | languageOptions: { 10 | ecmaVersion: 2022, 11 | sourceType: 'commonjs', 12 | globals: { 13 | ...globals.node, 14 | }, 15 | }, 16 | rules: { 17 | 'comma-spacing': ['error', { 'before': false, 'after': true }], 18 | 'comma-style': ['error', 'last'], 19 | 'eol-last': ['error', 'always'], 20 | 'func-call-spacing': ['error', 'never'], 21 | 'key-spacing': ['error', { 'mode': 'minimum', 'beforeColon': false, 'afterColon': true }], 22 | 'keyword-spacing': ['error', { 'before': true, 'after': true }], 23 | 'linebreak-style': ['error', 'unix'], 24 | 'lines-between-class-members': ['error', 'always'], 25 | 'no-console': ['warn', { 'allow': ['warn', 'error'] }], 26 | 'no-empty-function': 'error', 27 | 'no-multiple-empty-lines': ['error', { 'max': 2 }], 28 | 'no-var': 'error', 29 | 'quotes': ['error', 'single'], 30 | 'semi': ['error', 'never'], 31 | 'space-before-blocks': ['error', 'always'], 32 | 'space-before-function-paren': ['error', { 'named': 'never', 'anonymous': 'always', 'asyncArrow': 'always' }], 33 | }, 34 | }, 35 | ] 36 | -------------------------------------------------------------------------------- /jest/content/composition.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /jest/content/delay.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /jest/content/direction.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /jest/content/duration.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /jest/content/fill-mode.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /jest/content/iteration-count.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /jest/content/play-state.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /jest/content/predefined-animations.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /jest/content/timing-function.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /jest/customMatchers.js: -------------------------------------------------------------------------------- 1 | expect.extend({ 2 | toIncludeAll(received, expected) { 3 | function stripped(str) { 4 | return str.replace(/\s/g, '').replace(/;/g, '') 5 | } 6 | 7 | const receivedStripped = stripped(received) 8 | 9 | const pass = Array.isArray(expected) && expected.every(value => receivedStripped.includes(stripped(value))) 10 | 11 | return { 12 | pass, 13 | message: () => pass 14 | ? this.utils.matcherHint('.not.toIncludeAll') + 15 | '\n\n' + 16 | `Expected not to have all of: ${this.utils.printExpected(received)}\n` + 17 | `Received: ${this.utils.printReceived(expected)}` 18 | : this.utils.matcherHint('.toIncludeAll') + 19 | '\n\n' + 20 | `Expected to have all of: ${this.utils.printExpected(expected)}\n` + 21 | `Received: ${this.utils.printReceived(received)}`, 22 | } 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /jest/index.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const postcss = require('postcss') 3 | const tailwindcss = require('@tailwindcss/postcss') 4 | 5 | async function run(file, options = {}) { 6 | const { currentTestName } = expect.getState() 7 | 8 | const config = [ 9 | options.scope 10 | ? `@import "tailwindcss/${options.scope}" source(none);` 11 | : '@import "tailwindcss" source(none);', 12 | options?.importType === 'legacy' 13 | ? '@plugin "./../src/";' 14 | : '@import "./../src/";', 15 | `@source "./${file}";`, 16 | ].join('\n') 17 | 18 | const result = await postcss(tailwindcss()).process(config, { 19 | from: `${path.resolve(__filename)}?test=${currentTestName}`, 20 | }) 21 | 22 | return result.css 23 | } 24 | 25 | describe('modern tailwind', () => { 26 | it('generates `composition` utilities', async () => { 27 | expect(await run('content/composition.html', { scope: 'utilities' })).toIncludeAll([ 28 | '.animate-add { animation-composition: add; }', 29 | '.animate-replace { animation-composition: replace; }', 30 | '.animate-accumulate { animation-composition: accumulate; }', 31 | ]) 32 | }) 33 | 34 | it('generates `delay` utilities', async () => { 35 | expect(await run('content/delay.html', { scope: 'utilities' })).toIncludeAll([ 36 | '.animate-delay-75 { animation-delay: 75ms; }', 37 | '.animate-delay-333 { animation-delay: 333ms; }', 38 | '.animate-delay-\\[666ms\\] { animation-delay: 666ms; }', 39 | ]) 40 | }) 41 | 42 | it('generates `direction` utilities', async () => { 43 | expect(await run('content/direction.html', { scope: 'utilities' })).toIncludeAll([ 44 | '.animate-normal { animation-direction: normal; }', 45 | '.animate-reverse { animation-direction: reverse; }', 46 | ]) 47 | }) 48 | 49 | it('generates `duration` utilities', async () => { 50 | expect(await run('content/duration.html', { scope: 'utilities' })).toIncludeAll([ 51 | '.animate-duration-75 { animation-duration: 75ms; }', 52 | '.animate-duration-333 { animation-duration: 333ms; }', 53 | '.animate-duration-\\[666ms\\] { animation-duration: 666ms; }', 54 | ]) 55 | }) 56 | 57 | it('generates `fill-mode` utilities', async () => { 58 | expect(await run('content/fill-mode.html', { scope: 'utilities' })).toIncludeAll([ 59 | '.animate-fill-both { animation-fill-mode: both; }', 60 | '.animate-fill-none { animation-fill-mode: normal; }', 61 | ]) 62 | }) 63 | 64 | it('generates `iteration-count` utilities', async () => { 65 | expect(await run('content/iteration-count.html', { scope: 'utilities' })).toIncludeAll([ 66 | '.animate-infinite { animation-iteration-count: infinite; }', 67 | '.animate-once { animation-iteration-count: 1; }', 68 | '.animate-iteration-7 { animation-iteration-count: 7; }', 69 | '.animate-iteration-\\[14\\] { animation-iteration-count: 14; }', 70 | ]) 71 | }) 72 | 73 | it('generates `play-state` utilities', async () => { 74 | expect(await run('content/play-state.html', { scope: 'utilities' })).toIncludeAll([ 75 | '.animate-play { animation-play-state: running; }', 76 | '.animate-stop { animation-play-state: paused; }', 77 | ]) 78 | }) 79 | 80 | it('generates predefined animations for modern import', async () => { 81 | expect(await run('content/predefined-animations.html', { importType: 'modern' })).toIncludeAll([ 82 | '.animate-fade { animation: var(--animate-fade); }', 83 | '--animate-fade: fade var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both;', 84 | '@keyframes fade { 0% { opacity: 0; } 100% { opacity: 1; }}', 85 | '.animate-spin { animation: var(--animate-spin); }', 86 | '--animate-spin: spin var(--default-animation-duration, 1s) var(--default-animation-timing-function, linear) var(--default-animation-delay, 0s) infinite;', 87 | '@keyframes spin { to { transform: rotate(360deg); }}', 88 | ]) 89 | }) 90 | 91 | it('generates predefined animations for legacy import', async () => { 92 | expect(await run('content/predefined-animations.html', { importType: 'legacy' })).toIncludeAll([ 93 | '.animate-fade { animation: fade var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; }', 94 | '@keyframes fade { 0% { opacity: 0; } 100% { opacity: 1; }}', 95 | '.animate-spin { animation: spin var(--default-animation-duration, 1s) var(--default-animation-timing-function, linear) var(--default-animation-delay, 0s) infinite; }', 96 | '@keyframes spin { to { transform: rotate(360deg); }}', 97 | ]) 98 | }) 99 | 100 | it('generates `timing-function` utilities', async () => { 101 | expect(await run('content/timing-function.html', { scope: 'utilities' })).toIncludeAll([ 102 | '.animate-ease { animation-timing-function: ease; }', 103 | '.animate-ease-linear { animation-timing-function: linear; }', 104 | '.animate-ease-\\[cubic-bezier\\(1\\,0\\.66\\,0\\.33\\,0\\)\\] { animation-timing-function: cubic-bezier(1,0.66,0.33,0); }', 105 | ]) 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /jest/legacy/index.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const postcss = require('postcss') 4 | const tailwindcss = require('tailwindcss') 5 | 6 | async function run(file) { 7 | const { currentTestName } = expect.getState() 8 | 9 | const config = { 10 | content: [`./jest/${file}`], 11 | corePlugins: { preflight: false }, 12 | plugins: [require('../../src')], 13 | } 14 | 15 | const result = await postcss(tailwindcss(config)).process('@tailwind utilities', { 16 | from: `${path.resolve(__filename)}?test=${currentTestName}`, 17 | }) 18 | 19 | return result.css 20 | } 21 | 22 | describe('legacy tailwind', () => { 23 | if (! fs.existsSync(`${__dirname}/node_modules`)) { 24 | throw new Error(`Dependencies in ${__dirname} are missing`) 25 | } 26 | 27 | it('generates `composition` utilities', async () => { 28 | expect(await run('content/composition.html')).toIncludeAll([ 29 | '.animate-add { animation-composition: add; }', 30 | '.animate-replace { animation-composition: replace; }', 31 | '.animate-accumulate { animation-composition: accumulate; }', 32 | ]) 33 | }) 34 | 35 | it('should add `delay` utilities', async () => { 36 | expect(await run('content/delay.html')).toIncludeAll([ 37 | '.animate-delay-75 { animation-delay: 75ms; }', 38 | '.animate-delay-\\[666ms\\] { animation-delay: 666ms; }', 39 | ]) 40 | }) 41 | 42 | it('generates `direction` utilities', async () => { 43 | expect(await run('content/direction.html')).toIncludeAll([ 44 | '.animate-normal { animation-direction: normal; }', 45 | '.animate-reverse { animation-direction: reverse; }', 46 | ]) 47 | }) 48 | 49 | it('generates `duration` utilities', async () => { 50 | expect(await run('content/duration.html')).toIncludeAll([ 51 | '.animate-duration-75 { animation-duration: 75ms; }', 52 | '.animate-duration-\\[666ms\\] { animation-duration: 666ms; }', 53 | ]) 54 | }) 55 | 56 | it('generates `fill-mode` utilities', async () => { 57 | expect(await run('content/fill-mode.html')).toIncludeAll([ 58 | '.animate-fill-both { animation-fill-mode: both; }', 59 | '.animate-fill-none { animation-fill-mode: normal; }', 60 | ]) 61 | }) 62 | 63 | it('generates `iteration-count` utilities', async () => { 64 | expect(await run('content/iteration-count.html')).toIncludeAll([ 65 | '.animate-infinite { animation-iteration-count: infinite; }', 66 | '.animate-once { animation-iteration-count: 1; }', 67 | '.animate-iteration-\\[14\\] { animation-iteration-count: 14; }', 68 | ]) 69 | }) 70 | 71 | it('generates `play-state` utilities', async () => { 72 | expect(await run('content/play-state.html')).toIncludeAll([ 73 | '.animate-play { animation-play-state: running; }', 74 | '.animate-stop { animation-play-state: paused; }', 75 | ]) 76 | }) 77 | 78 | it('generates predefined animations', async () => { 79 | expect(await run('content/predefined-animations.html')).toIncludeAll([ 80 | '.animate-fade { animation: fade var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; }', 81 | '@keyframes fade { 0% { opacity: 0; } 100% { opacity: 1; }}', 82 | '.animate-spin { animation: spin var(--default-animation-duration, 1s) var(--default-animation-timing-function, linear) var(--default-animation-delay, 0s) infinite; }', 83 | '@keyframes spin { to { transform: rotate(360deg); }}', 84 | ]) 85 | }) 86 | 87 | it('generates `timing-function` utilities', async () => { 88 | expect(await run('content/timing-function.html')).toIncludeAll([ 89 | '.animate-ease { animation-timing-function: ease; }', 90 | '.animate-ease-linear { animation-timing-function: linear; }', 91 | '.animate-ease-\\[cubic-bezier\\(1\\2c 0\\.66\\2c 0\\.33\\2c 0\\)\\] { animation-timing-function: cubic-bezier(1,0.66,0.33,0); }', 92 | ]) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /jest/legacy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "autoprefixer": "^10.4.20", 5 | "tailwindcss": "3.1.0", 6 | "postcss": "^8.5.1" 7 | }, 8 | "browserslist": { 9 | "defaults": [ 10 | "> 2%" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss-animated", 3 | "description": "Extended animation utilities for Tailwind CSS", 4 | "author": "new-data-services", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/new-data-services/tailwindcss-animated.git" 9 | }, 10 | "homepage": "https://tailwindcss-animated.com", 11 | "keywords": [ 12 | "tailwindcss", 13 | "tailwind", 14 | "plugin", 15 | "animation", 16 | "animated", 17 | "keyframes" 18 | ], 19 | "files": [ 20 | "/src" 21 | ], 22 | "main": "src/index.js", 23 | "types": "src/index.d.ts", 24 | "style": "src/index.css", 25 | "scripts": { 26 | "watch": "npm run dev -- -w", 27 | "dev": "npx @tailwindcss/cli -i resources/app.css -o public/app.css", 28 | "test": "jest --setupFilesAfterEnv '/jest/customMatchers.js'", 29 | "lint": "npx eslint {src,jest}/**", 30 | "lint:fix": "npx eslint {src,jest}/** --fix" 31 | }, 32 | "peerDependencies": { 33 | "tailwindcss": ">=3.1.0 || >=4.0.0" 34 | }, 35 | "devDependencies": { 36 | "@eslint/js": "^9.18.0", 37 | "@tailwindcss/cli": "^4.0.0", 38 | "@tailwindcss/postcss": "^4.0.0", 39 | "eslint": "^9.18.0", 40 | "eslint-plugin-jest": "^28.11.0", 41 | "jest": "^29.7.0", 42 | "postcss": "^8.5.1", 43 | "tailwindcss": "^4.0.0", 44 | "typescript": "^5.7.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tailwindcss-animated 7 | 8 | 9 | 10 | 11 |

12 | Test file for development 13 |

14 | 15 | 16 | -------------------------------------------------------------------------------- /resources/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss" source(none); 2 | 3 | @import "../src/"; 4 | 5 | @source "../public/index.html" 6 | -------------------------------------------------------------------------------- /src/compat/composition.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ addUtilities }) => { 2 | addUtilities({ 3 | '.animate-replace': { 4 | 'animation-composition': 'replace', 5 | }, 6 | '.animate-add': { 7 | 'animation-composition': 'add', 8 | }, 9 | '.animate-accumulate': { 10 | 'animation-composition': 'accumulate', 11 | }, 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /src/compat/delay.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ matchUtilities, theme }) => matchUtilities({ 2 | 'animate-delay': value => ({ 3 | 'animation-delay': value, 4 | }), 5 | }, { 6 | values: theme('animationDelay'), 7 | }) 8 | -------------------------------------------------------------------------------- /src/compat/direction.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ addUtilities }) => addUtilities({ 2 | '.animate-normal': { 3 | 'animation-direction': 'normal', 4 | }, 5 | '.animate-reverse': { 6 | 'animation-direction': 'reverse', 7 | }, 8 | '.animate-alternate': { 9 | 'animation-direction': 'alternate', 10 | }, 11 | '.animate-alternate-reverse': { 12 | 'animation-direction': 'alternate-reverse', 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /src/compat/duration.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ matchUtilities, theme }) => matchUtilities({ 2 | 'animate-duration': value => ({ 3 | 'animation-duration': value, 4 | }), 5 | }, { 6 | values: theme('animationDuration'), 7 | }) 8 | -------------------------------------------------------------------------------- /src/compat/fill-mode.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ addUtilities }) => addUtilities({ 2 | '.animate-fill-none': { 3 | 'animation-fill-mode': 'normal', 4 | }, 5 | '.animate-fill-forwards': { 6 | 'animation-fill-mode': 'forwards', 7 | }, 8 | '.animate-fill-backwards': { 9 | 'animation-fill-mode': 'backwards', 10 | }, 11 | '.animate-fill-both': { 12 | 'animation-fill-mode': 'both', 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /src/compat/helper/bare-values.js: -------------------------------------------------------------------------------- 1 | const tailwindPackage = require('tailwindcss/package.json') 2 | 3 | function isLegacyTailwind() { 4 | return tailwindPackage.version?.startsWith('3.') 5 | } 6 | 7 | function isPositiveInteger(value) { 8 | const num = Number(value) 9 | 10 | return Number.isInteger(num) && num >= 0 && String(num) === String(value) 11 | } 12 | 13 | function getBareMilliseconds() { 14 | if (isLegacyTailwind()) { 15 | return {} 16 | } 17 | 18 | return { 19 | __BARE_VALUE__: (value) => { 20 | if (isPositiveInteger(value.value)) { 21 | return `${value.value}ms` 22 | } 23 | } 24 | } 25 | } 26 | 27 | function getBareIntegers() { 28 | if (isLegacyTailwind()) { 29 | return {} 30 | } 31 | 32 | return { 33 | __BARE_VALUE__: (value) => { 34 | if (isPositiveInteger(value.value)) { 35 | return value.value 36 | } 37 | } 38 | } 39 | } 40 | 41 | module.exports = { getBareMilliseconds, getBareIntegers } 42 | -------------------------------------------------------------------------------- /src/compat/iteration-count.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ addUtilities, matchUtilities, theme }) => { 2 | addUtilities({ 3 | '.animate-infinite': { 4 | 'animation-iteration-count': 'infinite', 5 | }, 6 | '.animate-once': { 7 | 'animation-iteration-count': '1', 8 | }, 9 | '.animate-twice': { 10 | 'animation-iteration-count': '2', 11 | }, 12 | '.animate-thrice': { 13 | 'animation-iteration-count': '3', 14 | }, 15 | }) 16 | 17 | matchUtilities({ 18 | 'animate-iteration': value => ({ 19 | 'animation-iteration-count': value, 20 | }), 21 | }, { 22 | values: theme('animationIteration'), 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /src/compat/play-state.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ addUtilities }) => addUtilities({ 2 | '.animate-run': { 3 | 'animation-play-state': 'running', 4 | }, 5 | '.animate-play': { 6 | 'animation-play-state': 'running', 7 | }, 8 | '.animate-stop': { 9 | 'animation-play-state': 'paused', 10 | }, 11 | '.animate-pause': { 12 | 'animation-play-state': 'paused', 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /src/compat/theme.js: -------------------------------------------------------------------------------- 1 | const { getBareMilliseconds, getBareIntegers } = require('./helper/bare-values') 2 | 3 | const bareMilliseconds = getBareMilliseconds() 4 | const bareIntegers = getBareIntegers() 5 | 6 | module.exports = { 7 | extend: { 8 | animationDelay: { 9 | none: '0s', 10 | 0: '0ms', 11 | 75: '75ms', 12 | 100: '100ms', 13 | 150: '150ms', 14 | 200: '200ms', 15 | 300: '300ms', 16 | 500: '500ms', 17 | 700: '700ms', 18 | 1000: '1000ms', 19 | ...bareMilliseconds, 20 | }, 21 | animationDuration: { 22 | none: '0s', 23 | 75: '75ms', 24 | 100: '100ms', 25 | 150: '150ms', 26 | 200: '200ms', 27 | 300: '300ms', 28 | 500: '500ms', 29 | 700: '700ms', 30 | 1000: '1000ms', 31 | ...bareMilliseconds, 32 | }, 33 | animationTimingFunction: { 34 | DEFAULT: 'ease', 35 | 'linear': 'linear', 36 | 'in': 'cubic-bezier(0.4, 0, 1, 1)', 37 | 'out': 'cubic-bezier(0, 0, 0.2, 1)', 38 | 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)', 39 | }, 40 | animationIteration: { 41 | 'infinite': 'infinite', 42 | 'once': '1', 43 | 'twice': '2', 44 | 'thrice': '3', 45 | ...bareIntegers, 46 | }, 47 | animation: { 48 | 'spin': 'spin var(--default-animation-duration, 1s) var(--default-animation-timing-function, linear) var(--default-animation-delay, 0s) infinite', 49 | 'ping': 'ping var(--default-animation-duration, 1s) var(--default-animation-timing-function, cubic-bezier(0, 0, 0.2, 1)) var(--default-animation-delay, 0s) infinite', 50 | 'pulse': 'pulse var(--default-animation-duration, 2s) var(--default-animation-timing-function, cubic-bezier(0.4, 0, 0.6, 1)) var(--default-animation-delay, 0s) infinite', 51 | 'bounce': 'bounce var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) infinite', 52 | 'wiggle': 'wiggle var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 53 | 'wiggle-more': 'wiggle-more var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 54 | 'rotate-y': 'rotate-y var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 55 | 'rotate-x': 'rotate-x var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 56 | 'jump': 'jump var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 57 | 'jump-in': 'jump-in var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 58 | 'jump-out': 'jump-out var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 59 | 'shake': 'shake var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 60 | 'fade': 'fade var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 61 | 'fade-down': 'fade-down var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 62 | 'fade-up': 'fade-up var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 63 | 'fade-left': 'fade-left var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 64 | 'fade-right': 'fade-right var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 65 | 'flip-up': 'flip-up var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 66 | 'flip-down': 'flip-down var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both', 67 | }, 68 | keyframes: { 69 | 'wiggle': { 70 | '0%, 100%': { 71 | transform: 'rotate(-3deg)', 72 | }, 73 | '50%': { 74 | transform: 'rotate(3deg)', 75 | }, 76 | }, 77 | 'wiggle-more': { 78 | '0%, 100%': { 79 | transform: 'rotate(-12deg)', 80 | }, 81 | '50%': { 82 | transform: 'rotate(12deg)', 83 | }, 84 | }, 85 | 'rotate-y': { 86 | '0%': { 87 | transform: 'rotateY(360deg)', 88 | }, 89 | '100%': { 90 | transform: 'rotateY(0)', 91 | }, 92 | }, 93 | 'rotate-x': { 94 | '0%': { 95 | transform: 'rotateX(360deg)', 96 | }, 97 | '100%': { 98 | transform: 'rotateX(0)', 99 | }, 100 | }, 101 | 'jump': { 102 | '0%, 100%': { 103 | transform: 'scale(1)', 104 | }, 105 | '10%': { 106 | transform: 'scale(0.8)', 107 | }, 108 | '50%': { 109 | transform: 'scale(1.2)', 110 | }, 111 | }, 112 | 'jump-in': { 113 | '0%': { 114 | transform: 'scale(0)', 115 | }, 116 | '80%': { 117 | transform: 'scale(1.2)', 118 | }, 119 | '100%': { 120 | transform: 'scale(1)', 121 | }, 122 | }, 123 | 'jump-out': { 124 | '0%': { 125 | transform: 'scale(1)', 126 | }, 127 | '20%': { 128 | transform: 'scale(1.2)', 129 | }, 130 | '100%': { 131 | transform: 'scale(0)', 132 | }, 133 | }, 134 | 'shake': { 135 | '0%': { 136 | transform: 'translateX(0rem)', 137 | }, 138 | '25%': { 139 | transform: 'translateX(-1rem)', 140 | }, 141 | '75%': { 142 | transform: 'translateX(1rem)', 143 | }, 144 | '100%': { 145 | transform: 'translateX(0rem)', 146 | }, 147 | }, 148 | 'fade': { 149 | '0%': { 150 | opacity: '0', 151 | }, 152 | '100%': { 153 | opacity: '1', 154 | }, 155 | }, 156 | 'fade-down': { 157 | '0%': { 158 | opacity: '0', 159 | transform: 'translateY(-2rem)', 160 | }, 161 | '100%': { 162 | opacity: '1', 163 | transform: 'translateY(0)', 164 | }, 165 | }, 166 | 'fade-up': { 167 | '0%': { 168 | opacity: '0', 169 | transform: 'translateY(2rem)', 170 | }, 171 | '100%': { 172 | opacity: '1', 173 | transform: 'translateY(0)', 174 | }, 175 | }, 176 | 'fade-left': { 177 | '0%': { 178 | opacity: '0', 179 | transform: 'translateX(2rem)', 180 | }, 181 | '100%': { 182 | opacity: '1', 183 | transform: 'translateX(0)', 184 | }, 185 | }, 186 | 'fade-right': { 187 | '0%': { 188 | opacity: '0', 189 | transform: 'translateX(-2rem)', 190 | }, 191 | '100%': { 192 | opacity: '1', 193 | transform: 'translateX(0)', 194 | }, 195 | }, 196 | 'flip-up': { 197 | '0%': { 198 | transform: 'rotateX(90deg)', 199 | transformOrigin: 'bottom', 200 | }, 201 | '100%': { 202 | transform: 'rotateX(0)', 203 | transformOrigin: 'bottom', 204 | }, 205 | }, 206 | 'flip-down': { 207 | '0%': { 208 | transform: 'rotateX(-90deg)', 209 | transformOrigin: 'top', 210 | }, 211 | '100%': { 212 | transform: 'rotateX(0)', 213 | transformOrigin: 'top', 214 | }, 215 | }, 216 | }, 217 | }, 218 | } 219 | -------------------------------------------------------------------------------- /src/compat/timing-function.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ matchUtilities, theme }) => matchUtilities({ 2 | 'animate-ease': value => ({ 3 | 'animation-timing-function': value, 4 | }), 5 | }, { 6 | values: theme('animationTimingFunction'), 7 | }) 8 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import './utilities/composition.css'; 2 | @import './utilities/delay.css'; 3 | @import './utilities/direction.css'; 4 | @import './utilities/duration.css'; 5 | @import './utilities/fill-mode.css'; 6 | @import './utilities/iteration-count.css'; 7 | @import './utilities/play-state.css'; 8 | @import './utilities/predefined-animations.css'; 9 | @import './utilities/timing-function.css'; 10 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const plugin: { handler: () => void } 2 | 3 | export = plugin 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const plugin = require('tailwindcss/plugin') 2 | 3 | module.exports = plugin(api => { 4 | require('./compat/composition')(api) 5 | require('./compat/delay')(api) 6 | require('./compat/direction')(api) 7 | require('./compat/duration')(api) 8 | require('./compat/fill-mode')(api) 9 | require('./compat/iteration-count')(api) 10 | require('./compat/play-state')(api) 11 | require('./compat/timing-function')(api) 12 | }, { 13 | theme: require('./compat/theme'), 14 | }) 15 | -------------------------------------------------------------------------------- /src/utilities/composition.css: -------------------------------------------------------------------------------- 1 | @utility animate-replace { 2 | animation-composition: replace; 3 | } 4 | 5 | @utility animate-add { 6 | animation-composition: add; 7 | } 8 | 9 | @utility animate-accumulate { 10 | animation-composition: accumulate; 11 | } 12 | -------------------------------------------------------------------------------- /src/utilities/delay.css: -------------------------------------------------------------------------------- 1 | @utility animate-delay-none { 2 | animation-delay: 0s; 3 | } 4 | 5 | @utility animate-delay-* { 6 | animation-delay: --value(integer)ms; 7 | animation-delay: --value([*]); 8 | } 9 | -------------------------------------------------------------------------------- /src/utilities/direction.css: -------------------------------------------------------------------------------- 1 | @utility animate-normal { 2 | animation-direction: normal; 3 | } 4 | 5 | @utility animate-reverse { 6 | animation-direction: reverse; 7 | } 8 | 9 | @utility animate-alternate { 10 | animation-direction: alternate; 11 | } 12 | 13 | @utility animate-alternate-reverse { 14 | animation-direction: alternate-reverse; 15 | } 16 | -------------------------------------------------------------------------------- /src/utilities/duration.css: -------------------------------------------------------------------------------- 1 | @utility animate-duration-none { 2 | animation-duration: 0s; 3 | } 4 | 5 | @utility animate-duration-* { 6 | animation-duration: --value(integer)ms; 7 | animation-duration: --value([*]); 8 | } 9 | -------------------------------------------------------------------------------- /src/utilities/fill-mode.css: -------------------------------------------------------------------------------- 1 | @utility animate-fill-none { 2 | animation-fill-mode: normal; 3 | } 4 | 5 | @utility animate-fill-forwards { 6 | animation-fill-mode: forwards; 7 | } 8 | 9 | @utility animate-fill-backwards { 10 | animation-fill-mode: backwards; 11 | } 12 | 13 | @utility animate-fill-both { 14 | animation-fill-mode: both; 15 | } 16 | -------------------------------------------------------------------------------- /src/utilities/iteration-count.css: -------------------------------------------------------------------------------- 1 | @utility animate-infinite { 2 | animation-iteration-count: infinite; 3 | } 4 | 5 | @utility animate-once { 6 | animation-iteration-count: 1; 7 | } 8 | 9 | @utility animate-twice { 10 | animation-iteration-count: 2; 11 | } 12 | 13 | @utility animate-thrice { 14 | animation-iteration-count: 3; 15 | } 16 | 17 | @utility animate-iteration-* { 18 | animation-iteration-count: --value(integer, [integer]); 19 | } 20 | -------------------------------------------------------------------------------- /src/utilities/play-state.css: -------------------------------------------------------------------------------- 1 | @utility animate-run { 2 | animation-play-state: running; 3 | } 4 | 5 | @utility animate-play { 6 | animation-play-state: running; 7 | } 8 | 9 | @utility animate-stop { 10 | animation-play-state: paused; 11 | } 12 | 13 | @utility animate-pause { 14 | animation-play-state: paused; 15 | } 16 | -------------------------------------------------------------------------------- /src/utilities/predefined-animations.css: -------------------------------------------------------------------------------- 1 | @theme default { 2 | --animate-spin: spin var(--default-animation-duration, 1s) var(--default-animation-timing-function, linear) var(--default-animation-delay, 0s) infinite; 3 | --animate-ping: ping var(--default-animation-duration, 1s) var(--default-animation-timing-function, cubic-bezier(0, 0, 0.2, 1)) var(--default-animation-delay, 0s) infinite; 4 | --animate-pulse: pulse var(--default-animation-duration, 2s) var(--default-animation-timing-function, cubic-bezier(0.4, 0, 0.6, 1)) var(--default-animation-delay, 0s) infinite; 5 | --animate-bounce: bounce var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) infinite; 6 | 7 | --animate-wiggle: wiggle var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 8 | --animate-wiggle-more: wiggle-more var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 9 | --animate-rotate-y: rotate-y var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 10 | --animate-rotate-x: rotate-x var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 11 | --animate-jump: jump var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 12 | --animate-jump-in: jump-in var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 13 | --animate-jump-out: jump-out var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 14 | --animate-shake: shake var(--default-animation-duration, 0.5s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 15 | --animate-fade: fade var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 16 | --animate-fade-down: fade-down var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 17 | --animate-fade-up: fade-up var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 18 | --animate-fade-left: fade-left var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 19 | --animate-fade-right: fade-right var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 20 | --animate-flip-up: flip-up var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 21 | --animate-flip-down: flip-down var(--default-animation-duration, 1s) var(--default-animation-timing-function, ease) var(--default-animation-delay, 0s) both; 22 | 23 | @keyframes wiggle { 24 | 0%, 25 | 100% { 26 | transform: rotate(-3deg); 27 | } 28 | 50% { 29 | transform: rotate(3deg); 30 | } 31 | } 32 | 33 | @keyframes wiggle-more { 34 | 0%, 35 | 100% { 36 | transform: rotate(-12deg); 37 | } 38 | 50% { 39 | transform: rotate(12deg); 40 | } 41 | } 42 | 43 | @keyframes rotate-y { 44 | 0% { 45 | transform: rotateY(360deg); 46 | } 47 | 100% { 48 | transform: rotateY(0); 49 | } 50 | } 51 | 52 | @keyframes rotate-x { 53 | 0% { 54 | transform: rotateX(360deg); 55 | } 56 | 100% { 57 | transform: rotateX(0); 58 | } 59 | } 60 | 61 | @keyframes jump { 62 | 0%, 63 | 100% { 64 | transform: scale(1); 65 | } 66 | 10% { 67 | transform: scale(0.8); 68 | } 69 | 50% { 70 | transform: scale(1.2); 71 | } 72 | } 73 | 74 | @keyframes jump-in { 75 | 0% { 76 | transform: scale(0); 77 | } 78 | 80% { 79 | transform: scale(1.2); 80 | } 81 | 100% { 82 | transform: scale(1); 83 | } 84 | } 85 | 86 | @keyframes jump-out { 87 | 0% { 88 | transform: scale(100%); 89 | } 90 | 20% { 91 | transform: scale(120%); 92 | } 93 | 100% { 94 | transform: scale(0); 95 | } 96 | } 97 | 98 | @keyframes shake { 99 | 0% { 100 | transform: translateX(0); 101 | } 102 | 25% { 103 | transform: translateX(-1rem); 104 | } 105 | 75% { 106 | transform: translateX(1rem); 107 | } 108 | 100% { 109 | transform: translateX(0); 110 | } 111 | } 112 | 113 | @keyframes fade { 114 | 0% { 115 | opacity: 0; 116 | } 117 | 100% { 118 | opacity: 1; 119 | } 120 | } 121 | 122 | @keyframes fade-down { 123 | 0% { 124 | opacity: 0; 125 | transform: translateY(-2rem); 126 | } 127 | 100% { 128 | opacity: 1; 129 | transform: translateY(0); 130 | } 131 | } 132 | 133 | @keyframes fade-up { 134 | 0% { 135 | opacity: 0; 136 | transform: translateY(2rem); 137 | } 138 | 100% { 139 | opacity: 1; 140 | transform: translateY(0); 141 | } 142 | } 143 | 144 | @keyframes fade-left { 145 | 0% { 146 | opacity: 0; 147 | transform: translateX(2rem); 148 | } 149 | 100% { 150 | opacity: 1; 151 | transform: translateX(0); 152 | } 153 | } 154 | 155 | @keyframes fade-right { 156 | 0% { 157 | opacity: 0; 158 | transform: translateX(-2rem); 159 | } 160 | 100% { 161 | opacity: 1; 162 | transform: translateX(0); 163 | } 164 | } 165 | 166 | @keyframes flip-up { 167 | 0% { 168 | transform: rotateX(90deg); 169 | transform-origin: bottom; 170 | } 171 | 100% { 172 | transform: rotateX(0); 173 | transform-origin: bottom; 174 | } 175 | } 176 | 177 | @keyframes flip-down { 178 | 0% { 179 | transform: rotateX(-90deg); 180 | transform-origin: top; 181 | } 182 | 100% { 183 | transform: rotateX(0); 184 | transform-origin: top; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/utilities/timing-function.css: -------------------------------------------------------------------------------- 1 | @utility animate-ease { 2 | animation-timing-function: ease; 3 | } 4 | 5 | @utility animate-ease-linear { 6 | animation-timing-function: linear; 7 | } 8 | 9 | @utility animate-ease-in { 10 | animation-timing-function: cubic-bezier(0.4, 0, 1, 1); 11 | } 12 | 13 | @utility animate-ease-out { 14 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1); 15 | } 16 | 17 | @utility animate-ease-in-out { 18 | animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 19 | } 20 | 21 | @utility animate-ease-* { 22 | animation-timing-function: --value([*]); 23 | } 24 | --------------------------------------------------------------------------------