Vitus Labs
Kinetic

API Reference

Complete API reference for Kinetic components, hooks, and the chainable factory.

Transition

The core animation primitive. Handles enter/leave lifecycle for a single child element.

import { Transition } from '@vitus-labs/kinetic'

<Transition
  show={isVisible}
  appear
  enterStyle={{ opacity: 0, transform: 'scale(0.95)' }}
  enterToStyle={{ opacity: 1, transform: 'scale(1)' }}
  enterTransition="all 300ms ease-out"
  leaveStyle={{ opacity: 1, transform: 'scale(1)' }}
  leaveToStyle={{ opacity: 0, transform: 'scale(0.95)' }}
  leaveTransition="all 200ms ease-in"
  onEnter={() => console.log('entering')}
  onAfterEnter={() => console.log('entered')}
  onLeave={() => console.log('leaving')}
  onAfterLeave={() => console.log('left')}
>
  <div>Animated content</div>
</Transition>

Props

PropTypeDefaultDescription
showbooleanControls visibility. true = enter, false = leave + unmount
appearbooleanfalseAnimate on initial mount
unmountbooleantrueUnmount when hidden. If false, keeps with display: none
timeoutnumber5000Safety timeout in ms — completes transition if transitionend never fires
childrenReactElementSingle child element (must accept className, style, and ref)

Style-Based Animation Props

PropTypeDescription
enterStyleCSSPropertiesInline styles for enter start state
enterToStyleCSSPropertiesInline styles for enter end state
enterTransitionstringCSS transition shorthand for enter (e.g. "all 300ms ease-out")
leaveStyleCSSPropertiesInline styles for leave start state
leaveToStyleCSSPropertiesInline styles for leave end state
leaveTransitionstringCSS transition shorthand for leave

Class-Based Animation Props

PropTypeDescription
enterstringClasses applied during the entire enter phase
enterFromstringClasses applied on first frame, removed on next frame
enterTostringClasses applied on second frame, kept until complete
leavestringClasses applied during the entire leave phase
leaveFromstringClasses applied on first frame of leave
leaveTostringClasses applied on second frame, kept until complete

Lifecycle Callbacks

CallbackDescription
onEnterCalled immediately when entering begins
onAfterEnterCalled when enter animation completes
onLeaveCalled immediately when leaving begins
onAfterLeaveCalled when leave animation completes

How It Works

  1. Enter: Sets enterStyle/enterFrom classes on mount, then on the next animation frame applies enterToStyle/enterTo classes. Listens for transitionend/animationend to complete.
  2. Leave: Sets leaveStyle/leaveFrom, then applies leaveToStyle/leaveTo. After completion, unmounts (or hides).
  3. Safety timeout: If the browser event never fires (no transition defined, element removed), the safety timeout completes the transition.

Collapse

Height-based expand/collapse with smooth animation. Measures content height automatically.

import { Collapse } from '@vitus-labs/kinetic'

<Collapse
  show={isExpanded}
  transition="height 300ms ease"
  appear
>
  <div style={{ padding: 16 }}>
    <p>Collapsible content of any height.</p>
    <p>Height is measured automatically.</p>
  </div>
</Collapse>

Props

PropTypeDefaultDescription
showbooleanControls expanded/collapsed state
transitionstring"height 300ms ease"CSS transition for height animation
appearbooleanfalseAnimate on initial mount
timeoutnumber5000Safety timeout in ms
childrenReactElementContent to collapse

Plus all lifecycle callbacks.

How It Works

  1. Wraps children in a div with overflow: hidden
  2. On enter: measures content height via scrollHeight, animates from 0 to measured height
  3. On leave: animates from current height to 0
  4. Respects prefers-reduced-motion — skips animation and applies changes instantly

Stagger

Sequentially animates a list of children with configurable delay between each.

import { Stagger } from '@vitus-labs/kinetic'
import { slideUp } from '@vitus-labs/kinetic'

<Stagger show={show} interval={60} reverseLeave {...slideUp}>
  <div key="a">First</div>
  <div key="b">Second</div>
  <div key="c">Third</div>
</Stagger>

Props

PropTypeDefaultDescription
showbooleanControls visibility of all children
intervalnumber50Delay in ms between each child's animation start
reverseLeavebooleanfalseReverse stagger order on leave (last exits first)
appearbooleanfalseAnimate on initial mount
timeoutnumber5000Safety timeout in ms
childrenReactElement[]Children to stagger

Plus all style-based, class-based, and lifecycle callback props.

CSS Variables

Each staggered child receives CSS custom properties:

VariableValue
--stagger-indexThe child's stagger index (0-based)
--stagger-intervalThe interval as a CSS time value (e.g. "50ms")

You can use these in CSS for additional effects:

.stagger-item {
  animation-delay: calc(var(--stagger-index) * var(--stagger-interval));
}

TransitionGroup

Manages enter/exit animations for dynamic keyed lists. Items animate in when added and out when removed.

import { TransitionGroup } from '@vitus-labs/kinetic'
import { fade } from '@vitus-labs/kinetic'

<TransitionGroup {...fade} appear>
  {items.map((item) => (
    <div key={item.id}>{item.name}</div>
  ))}
</TransitionGroup>

Props

PropTypeDefaultDescription
appearbooleanfalseAnimate children present on initial mount
timeoutnumber5000Safety timeout in ms
childrenReactElement[]Keyed children (must have unique key props)

Plus all style-based, class-based, and lifecycle callback props.

How It Works

  1. Tracks children by key across renders
  2. New keys trigger an enter animation
  3. Removed keys trigger a leave animation before unmounting
  4. Reappearing keys cancel the leave and re-enter
  5. Children present on first render only animate if appear={true}

kinetic() Factory

Creates reusable animated components with an immutable chaining API. Each chain method returns a new component — the original is never mutated.

import { kinetic } from '@vitus-labs/kinetic'

const component = kinetic('div')

Chain Methods

MethodSignatureDescription
.preset(p)(preset: Preset) => KineticComponentApply a preset (style + class config)
.enter(styles)(styles: CSSProperties) => KineticComponentSet enter start styles
.enterTo(styles)(styles: CSSProperties) => KineticComponentSet enter end styles
.enterTransition(v)(value: string) => KineticComponentSet enter CSS transition
.leave(styles)(styles: CSSProperties) => KineticComponentSet leave start styles
.leaveTo(styles)(styles: CSSProperties) => KineticComponentSet leave end styles
.leaveTransition(v)(value: string) => KineticComponentSet leave CSS transition
.enterClass(opts)({ active?, from?, to? }) => KineticComponentSet enter CSS classes
.leaveClass(opts)({ active?, from?, to? }) => KineticComponentSet leave CSS classes
.config(opts)(opts: ConfigOpts) => KineticComponentSet mode-specific options (appear, timeout, etc.)
.on(cbs)(callbacks: TransitionCallbacks) => KineticComponentAttach lifecycle callbacks
.collapse(opts?)({ transition? }?) => KineticComponent<'collapse'>Switch to collapse mode
.stagger(opts?)({ interval?, reverseLeave? }?) => KineticComponent<'stagger'>Switch to stagger mode
.group()() => KineticComponent<'group'>Switch to group mode

Mode-Specific Props

The props accepted by the rendered component depend on the mode:

Transition Mode (default)

const FadeDiv = kinetic('div').preset(fade)

<FadeDiv show={true} appear unmount timeout={3000} className="card">
  content
</FadeDiv>
PropTypeDefaultDescription
showbooleanRequired. Controls visibility
appearbooleanfalseAnimate on mount
unmountbooleantrueUnmount when hidden
timeoutnumber5000Safety timeout

Plus all standard HTML attributes for the tag.

Collapse Mode

const Accordion = kinetic('div').collapse({ transition: 'height 400ms ease' })

<Accordion show={true} className="panel">
  content
</Accordion>
PropTypeDefaultDescription
showbooleanRequired. Controls expanded state
appearbooleanfalseAnimate on mount
timeoutnumber5000Safety timeout
transitionstring"height 300ms ease"CSS transition

Stagger Mode

const StaggerList = kinetic('ul').preset(slideUp).stagger({ interval: 60 })

<StaggerList show={true}>
  <li key="a">First</li>
  <li key="b">Second</li>
</StaggerList>
PropTypeDefaultDescription
showbooleanRequired. Controls visibility
appearbooleanfalseAnimate on mount
timeoutnumber5000Safety timeout
intervalnumber50Delay between each child
reverseLeavebooleanfalseReverse order on leave

Group Mode

const AnimatedList = kinetic('div').preset(fade).group()

<AnimatedList appear>
  {items.map(item => <div key={item.id}>{item.name}</div>)}
</AnimatedList>
PropTypeDefaultDescription
appearbooleanfalseAnimate initial children
timeoutnumber5000Safety timeout

useTransitionState

Low-level hook for building custom transition components. Manages the four-stage lifecycle state machine.

import { useTransitionState } from '@vitus-labs/kinetic'

function CustomTransition({ show, children }) {
  const { stage, ref, shouldMount, complete } = useTransitionState({
    show,
    appear: true,
  })

  if (!shouldMount) return null

  return (
    <div
      ref={ref}
      onTransitionEnd={complete}
      style={{
        opacity: stage === 'entering' || stage === 'entered' ? 1 : 0,
        transition: 'opacity 300ms',
      }}
    >
      {children}
    </div>
  )
}

Parameters

ParameterTypeDefaultDescription
showbooleanControls visibility
appearbooleanfalseRun enter animation on initial mount

Returns

PropertyTypeDescription
stageTransitionStageCurrent stage: 'hidden' | 'entering' | 'entered' | 'leaving'
refRefObject<HTMLElement>Ref to attach to the animating element
shouldMountbooleanWhether the element should be rendered (false when hidden)
complete() => voidCall when the animation finishes to advance the state machine

useAnimationEnd

Low-level hook that listens for transitionend and animationend events on an element, with a safety timeout.

import { useAnimationEnd } from '@vitus-labs/kinetic'

useAnimationEnd({
  ref: elementRef,
  onEnd: () => console.log('animation finished'),
  active: isAnimating,
  timeout: 5000,
})

Parameters

ParameterTypeDefaultDescription
refRefObject<HTMLElement>Ref to the animating element
onEnd() => voidCalled when animation completes (or timeout fires)
activebooleanOnly listens when true
timeoutnumber5000Safety timeout if browser events never fire

Behavior

  • Listens for both transitionend and animationend
  • Ignores events bubbled from child elements (checks event.target === element)
  • Calls onEnd exactly once per active cycle
  • Cleans up listeners on deactivation or unmount

Preset Type

Both @vitus-labs/kinetic and @vitus-labs/kinetic-presets use this type:

type Preset = {
  // Style-based
  enterStyle?: CSSProperties
  enterToStyle?: CSSProperties
  enterTransition?: string
  leaveStyle?: CSSProperties
  leaveToStyle?: CSSProperties
  leaveTransition?: string

  // Class-based
  enter?: string
  enterFrom?: string
  enterTo?: string
  leave?: string
  leaveFrom?: string
  leaveTo?: string
}

Presets are spread onto <Transition> or passed to .preset():

// Spread onto Transition
<Transition show={show} {...myPreset}>
  <div>content</div>
</Transition>

// Or use with kinetic factory
const AnimatedDiv = kinetic('div').preset(myPreset)

On this page