Guide
Reactions
Animate between two states when the pointer comes near, an element is hovered or pressed, it scrolls into view, or the page scrolls.
A reaction has a trigger and outputs. --k-when sets the trigger, and outputs set what changes. Kinesis turns the trigger into a progress value from 0 to 1, springs it, and moves each output with it.
.card {
--k-when: hover; /* trigger */
--k-y: -6px; /* outputs */
--k-scale: 1.02;
}
A dock is near with --k-scale. A pressable button is press with --k-scale. A reveal is view with --k-opacity.
Outputs
One value goes from rest to that value. Two values go from the first to the second.
| Output | Rest | Example |
|---|---|---|
--k-x, --k-y | 0 | --k-y: 24px 0 rises into place |
--k-rotate | 0deg | --k-rotate: -8deg 8deg |
--k-scale | 1 | --k-scale: 1.1 grows a little |
--k-opacity | 1 | --k-opacity: 0 1 fades in |
--k-blur | 0 | --k-blur: 8px 0 sharpens |
--k-color | current color | --k-color: tomato |
--k-background | current background | --k-background: #0c1324 #2f6bff |
Colors accept any CSS color. The browser mixes them in Oklab, which keeps transitions from passing through gray.
Transform outputs add to pointer effects on the same element and respect transform-origin. A dock icon with transform-origin: bottom grows upward.
near
The pointer's closeness: 0 at --k-radius, 1 at the element's center.
.dock__icon {
--k-when: near;
--k-radius: 120px;
--k-axis: x;
--k-scale: 1.65;
--k-y: -14px;
transform-origin: 50% 100%;
}
.dock-scene {
display: grid;
place-items: end center;
height: 100%;
padding-bottom: 36px;
}
.dock {
display: flex;
gap: 8px;
padding: 8px;
border-radius: 22px;
background: rgb(255 255 255 / 0.7);
box-shadow: 0 20px 40px -20px rgb(12 19 36 / 0.35);
}
.dock__icon {
--k-when: near;
--k-axis: x;
--k-radius: 120px;
--k-scale: 1.65;
--k-y: -14px;
--k-motion: snappy;
width: 40px;
height: 40px;
border-radius: 12px;
background: linear-gradient(145deg, hsl(var(--hue) 90% 66%), hsl(calc(var(--hue) + 40) 85% 52%));
transform-origin: 50% 100%;
}--k-axis: x measures horizontal distance only, so the icons follow the pointer along the dock.
hover
1 while the pointer is over the element. Keyboard focus also turns it on.
Kinesis tests hover against the element's resting position. An element that shrinks or tilts away from the pointer doesn't flicker at its edges, as it would with CSS :hover.
.card {
--k-when: hover;
--k-y: -6px;
}
Touch never turns hover on.
press
1 while the element is pressed, by mouse, finger, or pen, or by Enter or Space when it has focus.
.button {
--k-when: press;
--k-scale: 0.9;
--k-motion: bouncy;
}
.presses {
display: flex;
flex-wrap: wrap;
gap: 14px;
justify-content: center;
align-items: center;
height: 100%;
}
.press {
--k-when: press;
--k-motion: bouncy;
padding: 14px 26px;
border: 0;
border-radius: 14px;
background: #0c1324;
color: #fff;
font-size: 15px;
font-weight: 600;
cursor: pointer;
}
.press--squish {
--k-scale: 0.88;
}
.press--sink {
--k-y: 5px;
box-shadow: 0 calc((1 - var(--k-progress)) * 5px) 0 #2f6bff;
}
.press--glow {
--k-scale: 0.97;
--k-background: #2f6bff;
}view
1 once the element is a tenth of the way into the viewport, 0 when it leaves. view once stays at 1 after the first time.
.feature {
--k-when: view once;
--k-opacity: 0 1;
--k-y: 28px 0;
}
.reveal {
display: grid;
gap: 10px;
align-content: center;
height: 100%;
padding: 28px;
}
.reveal__item {
--k-when: view once;
--k-opacity: 0 1;
--k-y: 28px 0;
--k-stagger: 90ms;
--k-motion: soft;
display: flex;
gap: 14px;
align-items: center;
padding: 14px 16px;
border-radius: 14px;
background: #fff;
box-shadow: 0 8px 24px -16px rgb(12 19 36 / 0.4);
}
.reveal__item b {
width: 28px;
height: 28px;
border-radius: 8px;
background: var(--c);
}
.reveal__item span {
flex: 1;
height: 10px;
border-radius: 5px;
background: #e3e8f2;
}scroll and page
These follow scrolling continuously.
scrollis the element's trip through the viewport: 0 when its top enters at the bottom, 1 when its bottom leaves at the top.pageis the whole document: 0 at the top, 1 at the bottom.
.tile {
--k-when: scroll;
--k-rotate: -120deg 120deg;
}
.reading-progress {
--k-when: page;
--k-motion: instant;
scale: var(--k-progress) 1;
transform-origin: left;
}
.scrolly {
display: grid;
place-items: center;
height: 100%;
}
.scrolly__bar {
--k-when: page;
--k-motion: instant;
position: absolute;
inset: 0 0 auto 0;
height: 6px;
background: #2f6bff;
scale: var(--k-progress) 1;
transform-origin: left;
}
.scrolly__tile {
--k-when: scroll;
--k-rotate: -120deg 120deg;
--k-scale: 0.7 1.15;
width: 120px;
height: 120px;
border-radius: 30px;
background: conic-gradient(from 45deg, #2f6bff, #7c5cff, #ff5d6c, #2f6bff);
}The progress bar reads --k-progress in its own CSS. See Reaction progress.
Stagger and delay
--k-delay waits before a hover, press, or view reaction starts. --k-stagger adds that delay once per sibling, so a list reacts in order.
.list > li {
--k-when: view once;
--k-opacity: 0 1;
--k-y: 20px 0;
--k-stagger: 60ms;
}
The order is the element's position among its Kinesis siblings. --k-index sets it yourself. Text splitting sets it on every character and word.
Motion
Reactions use the element's --k-motion. Springs can overshoot. With bouncy, --k-scale: 1.2 passes 1.2 briefly and settles back. Opacity stays between 0 and 1.