+
+ Documentation
+
+
+ How to Make Your CSS Animation Scroll-driven
+
+
+ CSS animations consist of two components, a set of keyframes and a style describing the
+ animation. Let's declare a simple @opacity keyframe set and apply it to an
+ element we want to control by a scroll timeline.
+
+ {keyframes101}
+ {keyframes102}
+
+ To effectively control the animation, make sure to declare the timeline in the code after
+ the animation. By default, the shorthand animation property sets the{' '}
+ animation-timeline: auto unless set otherwise. However, using this plugin and
+ Tailwind CSS animations ensures that the declaration order is correct.
+
+ {keyframes103}
+ Scroll the container.
+
+
+ Animation Timeline
+
+
+ Utility class specifying the timeline that is used to control the progress of a CSS
+ animation.
+
+
+
+ Scroll Timeline
+
+
+ Utility class setting the named scroll progress timeline, which is set on a scrollable
+ element.
+
+
+
+ View Timeline
+
+
+ Utility class setting the named view progress timeline, which is set on a subject inside
+ another scrollable element.
+
+
+
+ Animation Range
+
+
+ Animation range start controls where along the timeline an animation will start. It is set
+ on the animated element.
+
+
+
+ Scroll the container to see each how range utility class affects the animation.
+
+
+ {multiRange}
+ {multiRangeKeyframes}
+
+ Timeline Scope
+
+
+ Timeline scope allows to control animations outside the element which defines the timeline.
+
+
+
+ Fallback Styling
+
+
+ Use the no-animations modifier to apply fallback styling in browsers which do
+ not support scroll-driven animations yet.
+
+
+
+ {supports}
+
+ )
+}
+
+export interface DocsTableRowProps {
+ className: string
+ code: string
+}
+
+export default DocsTableRow
diff --git a/docs/src/fonts/inter-variable.woff2 b/docs/src/fonts/inter-variable.woff2
new file mode 100644
index 0000000..1c91452
Binary files /dev/null and b/docs/src/fonts/inter-variable.woff2 differ
diff --git a/docs/src/fonts/inter.css b/docs/src/fonts/inter.css
new file mode 100644
index 0000000..4568d0c
--- /dev/null
+++ b/docs/src/fonts/inter.css
@@ -0,0 +1,7 @@
+@font-face {
+ font-display: block;
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 100 900;
+ src: url('./inter-variable.woff2') format('woff2');
+}
diff --git a/docs/src/index.css b/docs/src/index.css
new file mode 100644
index 0000000..57729c4
--- /dev/null
+++ b/docs/src/index.css
@@ -0,0 +1,7 @@
+@import 'fonts/inter.css';
+@import 'css/keyframes.css';
+@import 'css/prism.css';
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/docs/src/layouts/Page.tsx b/docs/src/layouts/Page.tsx
new file mode 100644
index 0000000..e30cb74
--- /dev/null
+++ b/docs/src/layouts/Page.tsx
@@ -0,0 +1,27 @@
+import { PropsWithChildren } from 'react'
+import { ScrollRestoration } from 'react-router-dom'
+import PageBackground from '../components/PageBackground'
+import Nav from '../partials/Nav'
+import Footer from '../partials/Footer'
+import { Analytics } from '@vercel/analytics/react'
+
+function Page({ children }: PropsWithChildren) {
+ return (
+
+
+
+
+
{children}
+
+
+
+
+
+ )
+}
+
+export interface PageProps {
+ children: PropsWithChildren
+}
+
+export default Page
diff --git a/docs/src/main.tsx b/docs/src/main.tsx
new file mode 100644
index 0000000..cf445a8
--- /dev/null
+++ b/docs/src/main.tsx
@@ -0,0 +1,28 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import { RouterProvider, createBrowserRouter } from 'react-router-dom'
+import './index.css'
+import HomeView from './views/HomeView'
+import DocsView from './views/DocsView'
+
+const router = createBrowserRouter([
+ {
+ path: '/',
+ element: ,
+ errorElement: ,
+ },
+ {
+ path: '/usage',
+ element: ,
+ },
+ {
+ path: '/docs',
+ element: ,
+ },
+])
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+)
diff --git a/docs/src/partials/Animations.tsx b/docs/src/partials/Animations.tsx
new file mode 100644
index 0000000..f4e7070
--- /dev/null
+++ b/docs/src/partials/Animations.tsx
@@ -0,0 +1,131 @@
+import { Github, Minus } from 'lucide-react'
+import Code from '../components/Code.tsx'
+import CodeBlock from '../components/CodeBlock.tsx'
+import Heading from '../components/Heading.tsx'
+import Paragraph from '../components/Paragraph.tsx'
+import {
+ codeExampleRange,
+ codeExampleScope,
+ codeExampleSupports,
+ codeExampleTimeline,
+ codeExampleView,
+} from '../utils/codeExamples.ts'
+
+const Animations = () => {
+ return (
+ <>
+
+ Plugin
+
+
+ The plugin provides utilities for a subset of CSS scroll-driven animation properties:
+
+
+
+
+ animation-timeline
+
+
+
+ scroll-timeline, view-timeline
+
+
+
+ animation-range
+
+
+
+ timeline-scope
+
+
+
+
+ Animation Timeline
+
+
+ The single most impressive feature of scroll-driven animations is an anonymous animation
+ timeline. It allows user to easily trigger anything just by scrolling the page. Utility
+ below allows user to use the timeline CSS class which defaults to{' '}
+ animation-timeline: scroll(y) and also provides an option to set custom
+ timeline name with a modifier.
+
+
+ {codeExampleTimeline}
+
+
+ Scroll and View Timeline
+
+
+ Scroll and View timelines provide user with better control over the animations. Both{' '}
+ scroll-timeline and view-timeline are meant to be used with
+ modifiers to set the timeline name.
+
+
+ {codeExampleView}
+
+
+ Range
+
+
+ Animation range controls start and end of an animation. Utility class range{' '}
+ offers multiple options with default value set to cover.
+
+
+ {codeExampleRange}
+
+
+ Scope
+
+
+ Timeline scope allows to control animations outside the element which defines the timeline.
+ Utility scope should be used with a modifier to define the timeline name set by{' '}
+ scroll-timeline or view-timeline.
+
+
+ {codeExampleScope}
+
+
+ Fallback Styling
+
+
+ Scroll-driven animations are not broadly supported yet. I decided to apply an
+ animation-first approach. Use the no-animations modifier for fallback styling.
+
+
+ {codeExampleSupports}
+
+ >
+ )
+}
+
+export default Animations
diff --git a/docs/src/partials/Demo.tsx b/docs/src/partials/Demo.tsx
new file mode 100644
index 0000000..3ea6411
--- /dev/null
+++ b/docs/src/partials/Demo.tsx
@@ -0,0 +1,59 @@
+import Code from '../components/Code'
+import CodeBlock from '../components/CodeBlock'
+import Heading from '../components/Heading'
+import Paragraph from '../components/Paragraph'
+import AppearDemo from '../demos/AppearDemo'
+import ProgressBarDemo from '../demos/ProgressBarDemo'
+import RangeDemo from '../demos/RangeDemo'
+import {
+ appearDemo,
+ appearKeyframes,
+ progressBarDemo,
+ progressBarKeyframes,
+ rangeDemo,
+ rangeKeyframes,
+} from '../utils/demoExamples'
+
+const Demo = () => {
+ return (
+ <>
+ Demos
+
+ Anonymous Scroll Timeline
+
+
+ This demo showcases how to create a simple progress bar just by adding one utility class to
+ the element. We define the anonymous scroll timeline by adding timeline to the
+ progress bar.
+
+
+ {progressBarDemo}
+ {progressBarKeyframes}
+
+ Anonymous View Timeline
+
+
+ This demo showcases how to make the element appear after entering the view frame. We define
+ the anonymous view timeline by adding timeline-view to this element.
+
+
+ {appearDemo}
+ {appearKeyframes}
+
+ Range, Scope and Animation Timeline Name
+
+
+ This demo showcases the usage of the plugin to reveal the navigation bar. The{' '}
+ view-timeline/navbar utility sets up the animation timeline, which is then
+ scoped out of the defining element by scope/navbar. The navigation bar is
+ controlled by this timeline with the timeline/navbar utility. Utility class{' '}
+ range-on-exit is set to limit the timeline duration.
+
+
+ {rangeDemo}
+ {rangeKeyframes}
+ >
+ )
+}
+
+export default Demo
diff --git a/docs/src/partials/Footer.tsx b/docs/src/partials/Footer.tsx
new file mode 100644
index 0000000..c56ece4
--- /dev/null
+++ b/docs/src/partials/Footer.tsx
@@ -0,0 +1,23 @@
+import Link from '../components/Link'
+
+const Footer = () => {
+ return (
+
+ )
+}
+
+export default Footer
diff --git a/docs/src/partials/Intro.tsx b/docs/src/partials/Intro.tsx
new file mode 100644
index 0000000..2889c91
--- /dev/null
+++ b/docs/src/partials/Intro.tsx
@@ -0,0 +1,25 @@
+import { CornerRightDown } from 'lucide-react'
+import Link from '../components/Link'
+import Paragraph from '../components/Paragraph'
+
+const Intro = () => {
+ return (
+ <>
+
+ I remember being yelled at by a senior Java developer when I proudly integrated some atomic
+ classes into our dinosaur project. It was back in 2018, I didn’t back out and our collection
+ of Tailwind CSS classes has been growing every day.
+
+
+ One of many stand-out features of Tailwind CSS is how it guides developers to utilize the
+ edge CSS features simply by exploring its documentation.{' '}
+
+ Scroll-driven animations
+ {' '}
+ you plan to introduce are no exception to this.
+
+ >
+ )
+}
+
+export default Intro
diff --git a/docs/src/partials/MainTitle.tsx b/docs/src/partials/MainTitle.tsx
new file mode 100644
index 0000000..fd7e7ee
--- /dev/null
+++ b/docs/src/partials/MainTitle.tsx
@@ -0,0 +1,19 @@
+import Heading from '../components/Heading.tsx'
+import Link from '../components/Link.tsx'
+import Paragraph from '../components/Paragraph.tsx'
+
+const MainTitle = () => {
+ return (
+ <>
+ Scroll-driven Animations for Tailwind CSS
+
+ Bratislava, Slovakia,{' '}
+
+ adamplesnik.com
+
+
+ >
+ )
+}
+
+export default MainTitle
diff --git a/docs/src/partials/Me.tsx b/docs/src/partials/Me.tsx
new file mode 100644
index 0000000..4ef6bb7
--- /dev/null
+++ b/docs/src/partials/Me.tsx
@@ -0,0 +1,32 @@
+import { TrendingUp } from 'lucide-react'
+import Heading from '../components/Heading.tsx'
+import Link from '../components/Link.tsx'
+import Paragraph from '../components/Paragraph.tsx'
+
+const Me = () => {
+ return (
+ <>
+
+ Me
+
+
+ I am married, 38 years old, father of two kids, living in Bratislava, Slovakia. While my
+ passion for coding is obvious, I also enjoy mountain biking, traveling, and spending quality
+ time with my family.
+
+
+ I speak English and French fluently, and because I love Portugal, I'm also learning
+ Portuguese.
+
+
+ Learn more about me at my{' '}
+
+ personal page
+
+ .
+
+ >
+ )
+}
+
+export default Me
diff --git a/docs/src/partials/Nav.tsx b/docs/src/partials/Nav.tsx
new file mode 100644
index 0000000..b0eb7e9
--- /dev/null
+++ b/docs/src/partials/Nav.tsx
@@ -0,0 +1,25 @@
+import { Github } from 'lucide-react'
+import DarkModeSwitch from '../components/DarkModeSwitch.tsx'
+import HeaderNavAnchor from '../components/HeaderNavAnchor.tsx'
+
+const Nav = () => {
+ return (
+