Esc

Try “tilt”, “reveal”, “drag”, or “reduced motion”.

Guide

Working with CSS

Tokens, calc(), media queries, :hover, themes, and the variables Kinesis writes back.

Kinesis properties are CSS variables registered with @property and a type, such as <length> or <angle>. The browser computes their values the way it computes width, so anything valid in CSS works in them.

Tokens, units, and calc()

:root {
  --space: 8px;
  --brand: oklch(62% 0.2 265);
}

.layer {
  --k-parallax: calc(var(--space) * 4);   /* 32px */
  --k-radius: 12vw;
}

.title {
  font-size: 40px;
  --k-parallax: 1em;                       /* 40px: this element's font size */
}

.chip {
  --k-when: hover;
  --k-background: var(--brand);
}

A value that doesn't match a property's type is ignored, like any invalid CSS.

Media queries and container queries

.hero__art { --k-parallax: 40px; }

@media (max-width: 700px) {
  .hero__art { --k-parallax: 12px; }
}

@media (pointer: coarse) {
  .card { --k-tilt: 0deg; }
}

Kinesis re-reads configuration after a window resize, so media queries apply on their own.

, , and classes

Kinesis picks up changes from classes, :hover, and :focus:

.card { --k-tilt: 4deg; }
.card:hover { --k-tilt: 12deg; }
.card.is-selected { --k-motion: bouncy; }

For hover effects, the hover reaction is usually better. It springs, and it doesn't flicker when the element moves away from the pointer.

Themes and stylesheets

Kinesis re-reads the page when a class or data-theme changes on <html> or <body>, and when a stylesheet is added. For changes it can't see, like :has() reacting to a distant element or a stylesheet edited through the CSSOM, call refresh():

import { refresh } from "@amineyarman/kinesis"

refresh()

What inherits

Interaction properties apply only to the element that declares them. --k-tilt on a card doesn't tilt its children.

Three context properties inherit, so you can set them once on a scope: --k-motion, --k-intensity, and --k-perspective.

Reaction progress

Every element with --k-when gets --k-progress, from 0 to 1, moving with the reaction's spring. Use it in any CSS property to animate what Kinesis has no output for:

.button {
  --k-when: press;
  box-shadow: 0 calc((1 - var(--k-progress)) * 6px) 0 #1a4fd6;
}

.route {
  --k-when: scroll;
}

.route__dot {
  offset-path: path("M 20 180 C 110 20, 290 20, 380 180");
  offset-distance: calc(var(--k-progress) * 100%);
}
Scroll the pageEdit

The dot reads its parent's progress. For scroll and view, measure an element that stays put. An element moved by its own progress measures a moving target.

--k-progress inherits, so children can use their parent's:

.card { --k-when: hover; }
.card .arrow { translate: calc(var(--k-progress) * 6px) 0; }

Pointer position

--k-track: pointer writes the pointer's position over the element to --k-pointer-x and --k-pointer-y, from 0 at the left and top to 1 at the right and bottom. With --k-progress, that makes a spotlight:

.spot-card {
  --k-track: pointer;
  --k-when: hover;
  background:
    radial-gradient(
      260px circle at calc(var(--k-pointer-x) * 100%) calc(var(--k-pointer-y) * 100%),
      rgb(120 160 255 / calc(var(--k-progress) * 0.35)),
      transparent 65%
    ),
    #0c1324;
}

Fast

Nothing runs while nothing moves.

Yours

Your transforms stay untouched.

Small

About 14 KB, gzipped.

Hover the cardsEdit

Transforms and transitions

Kinesis writes translate, rotate, and scale, never transform, so your own transform keeps working. If you set translate, rotate, or scale yourself, Kinesis adds to your value. See How it works.

Don't put CSS transitions on the properties Kinesis animates. transition: all is the usual cause. The browser smooths every update a second time and the motion lags.

Debugging

initKinesis({ debug: true }) warns about inline elements that can't be transformed, conflicting transitions, unknown keywords, and 3D flattened by overflow or filter.

In DevTools, Kinesis properties appear in the Styles panel like any CSS, and the inline style shows what Kinesis writes.

Edit this page on GitHub